Как-то неожиданно для меня подключение своего nodejs-приложения к Google Calendar API оказалось довольно нетривиальной задачей. Несмотря на подробное описание вариантов подключения на русском языке пришлось продираться через лес различных настроек и конфигураций. В статье подробно изложены шаги, которые приходится совершить, чтобы интеграция завершилась успехом.
Цель интеграции - дать возможность nodejs-приложению публиковать события в определённый календарь. В данном примере использовался обычная, персональная учётная запись в Google.
Создание календаря
Для начала нужно создать календарь, в который будем публиковать события. Идём в Google Calendar и нажимаем на кнопку "+" рядом с "Другие календари", затем выбираем пункт "Создать календарь":

заполняем форму и опять жмём "Создать календарь", но уже синюю кнопку:

Гугл довольно долго шуршит мозгами, после чего радостно сообщает, что новый календарь готов. Доступ к настройкам нового календаря:

В настройках нас прежде всего интересует пункт "Интеграция календаря":

в котором самое полезное - это "Идентификатор календар��"
c093hr4fqjuj5k9e6uvvac73ac@group.calendar.google.com
Этот значение наше nodejs-приложение будет использовать при подключении к API.
Регистрация приложения в Google
Управление приложениями находится в "Консоли API".
Создать новое приложение можно через селектор текущего приложения - "Выберите проект":


После создания проект становится доступным в списке проектов - "Habr Demo":

Теперь нужно дать доступ этому проекту к соответствующим API Google'а:

Так как интерфейсов Google наплодил свыше 3 сотен, используем фильтр для поиска нужных:

Фильтрация по ключу "calend" оставляет всего два вариант, один из которых нам и нужен:

Заходим в "Google Calendar API" и включаем его для использования в нашем проекте:

После чего проваливаемся в dashboard использования этого API в нашем проекте (https://console.developers.google.com/apis/api/calendar-json.googleapis.com/overview?project=habr-demo-293107&supportedpurview=project), где нам сообщают, что для использования API мы должны создать учётные данные:

Жмём "СОЗДАТЬ УЧЁТНЫЕ ДАННЫЕ" и заполняем анкету, определяющую, какой тип учётных данных нам нужен для доступа к API:

вся анкета не влезла в один скрин, вот остаток:

По результатам анкетирования Google предлагает создать закрытый ключ в формате JSON, при помощи которого наше приложение будет подключаться к Google Calendar API:

Самую большую трудность для меня создал пункт "Роль". Похоже, что ролей у Google'а даже больше, чем API, к которым можно подключаться:

В общем, если выбрать "Проект / Владелец", то обещают предоставить полный доступ ко всем ресурсам. Жмём на кнопку "Продолжить" и получаем JSON-файл в каталог "Downloads" на своём компрьютере:

Содержимое JSON-файла примерно такое (ключ я вырезал):
{ "type": "service_account", "project_id": "habr-demo-293107", "private_key_id": "4ec17ea5f8b606e0535a0623a110111123fd3c33", "private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n", "client_email": "nodejs-app@habr-demo-293107.iam.gserviceaccount.com", "client_id": "102219376121816220804", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://oauth2.googleapis.com/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/nodejs-app%40habr-demo-293107.iam.gserviceaccount.com" }
В консоли API у приложения "Habr Demo" появляется новый сервис с email'ом "nodejs-app@habr-demo-293107.iam.gserviceaccount.com":

Код
Для подключения к API из nodejs-приложения Google предлагает библиотеку googleapis и обзор её использования. В обзоре применяется OAuth2-аутентификация и получение из календаря списка событий, а нам нужна аутентификация по ключу в соответствующих scope'ах и добавление события в календарь. Поэтому код получается примерно такой:
const fs = require('fs'); const {google} = require('googleapis'); const CALENDAR_ID = 'c093hr4fqjuj5k9e6uvvac73ac@group.calendar.google.com'; const KEYFILE = 'Habr Demo-4ec17ea5f8b6.json'; // path to JSON with private key been downloaded from Google const SCOPE_CALENDAR = 'https://www.googleapis.com/auth/calendar'; // authorization scopes const SCOPE_EVENTS = 'https://www.googleapis.com/auth/calendar.events'; (async function run() { // INNER FUNCTIONS async function readPrivateKey() { const content = fs.readFileSync(KEYFILE); return JSON.parse(content.toString()); } async function authenticate(key) { const jwtClient = new google.auth.JWT( key.client_email, null, key.private_key, [SCOPE_CALENDAR, SCOPE_EVENTS] ); await jwtClient.authorize(); return jwtClient; } async function createEvent(auth) { const event = { 'summary': 'Habr Post Demo', 'description': 'Тест для демонстрации интеграции nodejs-приложения с Google Calendar API.', 'start': { 'dateTime': '2020-10-20T16:00:00+02:00', 'timeZone': 'Europe/Riga', }, 'end': { 'dateTime': '2020-10-20T18:00:00+02:00', 'timeZone': 'Europe/Riga', } }; let calendar = google.calendar('v3'); await calendar.events.insert({ auth: auth, calendarId: CALENDAR_ID, resource: event, }); } // MAIN try { const key = await readPrivateKey(); const auth = await authenticate(key); await createEvent(auth); } catch (e) { console.log('Error: ' + e); } })();
Запускаем код на выполнение и получаем ответ от Calendar API:
{ ... "status": 404, "statusText": "Not Found", ... }
что и логично - наш новый календарь ничего не знает про наш новый сервис.
Настройка доступа сервиса к календарю
Заходим в наш календарь, созданный ранее, и в настройках жмём "Доступ для отдельных пользователей", затем "Добавить пользователей" и вводим email-адрес нашего сервиса, который будет создавать события в нашем календаре:

После предоставления сервису доступа к календарю нужно настроить права:

Выбираем "Внесение изменений и предоставление доступа", иначе будем иметь ошибку "You need to have writer access to this calendar." при обращении к API:

Запускаем наш скрипт на выполнение ещё раз и фиксируем результат в календаре:

В коде я задавал время старта события в 16:00:
'start': { 'dateTime': '2020-10-20T16:00:00+02:00', 'timeZone': 'Europe/Riga', }
a по факту событие запланировано на 17:00, что вполне соответствует духу IT:
В программировании существует лишь два характерных затруднения: инвалидация кеша, наименование сущностей и ошибка на единицу
Резюме
Вот и всё, квест пройден. Хэппи, как говорится, кодинг.
