?Мотивация
Задача: нужно получить доступ или отобразить события частного google-календаря.
Проблема: нельзя поместить частный календарь внутрь iframe или запросить события, используя ключ API.
Необходимые условия: совместимость с Angular, поддержка TypeScript (сервисные врапперы, классы и типы моделей данных)
Решение: google-api-nodejs-client, предоставляющий все, что нужно.
Теперь разбираемся, как заменить часть Node.js
на Angular
.
?️Интеграция
К сожалению, эта библиотека предназначена для NodeJS, и проблематично интегрировать ее внутрь приложений, созданных в Webpack, в том числе и Angular, поскольку она зависит от некоторых вещей в NodeJS, не существующих в браузере.
Но это не остановит нас, поскольку есть обходной путь (обсуждение на английском языке здесь).
Во-первых, нужно установить расширения для Webpack.
npm i -D @angular-builders/custom-webpack
Затем нужно заменить builder angular.json в architect
; в настройки также включаем путь к пользовательской конфигурации Webpack:
"architect": {
"build": {
"builder": "@angular-builders/custom-webpack:browser",
"options": {
"customWebpackConfig": {
"path": "./extra-webpack.config.js"
},
...
},
...
},
"serve": {
"builder": "@angular-builders/custom-webpack:dev-server",
...
},
}
Объяснение можно найти в документации по сборке Angular.
?Хак
Так в чем, собственно, хак? Мы притворимся, что все нужное этой библиотеке NodeJS есть в браузере.
- Имитируем некоторые внутренние среды выполнения, такие как
global
иBuffer
. - Имитируем несколько библиотек, которые требуются при выполнении
fs
,child_process
иhttps-proxy-agent
.
Предоставление замены для global
и Buffer
Добавьте следующий полифилл под импортом приложения в файл src/polyfills.ts
.
import * as process from 'process';
(window as any).process = process;
import { Buffer } from 'buffer';
(window as any).Buffer = Buffer;
Не забудьте установить эти пакеты с npm i -D process buffer
.
Поместите это в <head>
index.html
, чтобы избавиться от ошибок, связанных с доступом к global
, поскольку его заменит window
.
<script>
if (global === undefined) {
var global = window;
}"
</script>
Имитирование библиотек
const path = require('path');
module.exports = {
resolve: {
extensions: ['.js'],
alias: {
fs: path.resolve(__dirname, 'src/mocks/fs.mock.js'),
child_process: path.resolve(
__dirname,
'src/mocks/child_process.mock.js'
),
'https-proxy-agent': path.resolve(
__dirname,
'src/mocks/https-proxy-agent.mock.js',
),
},
},
};
Что же здесь происходит? Мы говорим WebPack заменить один файл импорта другим. Все заглушки мы кладем сюда src/mock
, чтобы нашим коллегам по проекту было проще понять, что это за файлы.
Код внутри этих заглушек довольно прост. Нужно добавить несколько методов, не обязательных к использованию, чтобы они могли “ничего” не делать.
И fs
и child_process
будут выглядеть так:
module.exports = {
readFileSync() {},
readFile() {},
};
С помощью https-proxy-agent
можно записать еще проще, как:module.exports = {};
.
?Настройка доступа
- Создаем новый GCP проект или используем существующий.
- В консоли GCP выделяем проект и переходим во вкладку Библиотека и разрешаем использование нужного API (в нашем случае Google Календарь и Аналитика).
- Настраиваем экран разрешений OAuth (имя, ограничения…) — он задает все нужные права доступа к API (календарь и аналитика).
- Создаем учётные данные клиента OAuth —они нужны при доступе к личным данным (таким как события в календаре или аналитика).
- Переходим к аутентификации и используем публичный и приватный ключи.
?Аутентификация
Большинство публичных данных из API легко получить при помощи API-ключа. Но если вам нужны приватные данные (приватный календарь, например), нужно проходить аутентификацию. Это можно сделать с помощью клиента OAuth.
В перечне провайдеров app.module.ts
укажите OAuth2Client
. Это должно выглядеть так:
{
provide: OAuth2Client,
useValue: new OAuth2Client(
// Вы получите это в учетных данных проекта GCP
environment.G_API_CLIENT_ID,
environment.G_API_CLIENT_SECRET,
// URL, где будет обрабатываться успешная аутентификация
environment.G_API_REDIRECT,
),
},
Мы будем использовать перенаправленную аутентификацию, поэтому следующим шагом станет генерация аутентификационного URL.
window.location.href = this.oauth2Client.generateAuthUrl({
// 'offline' также получает refresh_token
access_type: 'offline',
// поместите сюда необходимые области
scope: [
// в первом примере мы хотим прочитать события в календаре
'https://www.googleapis.com/auth/calendar.events.readonly',
'https://www.googleapis.com/auth/calendar.readonly',
// во втором примере мы читаем данные аналитики
'https://www.googleapis.com/auth/analytics.readonly',
],
});
Благодаря refresh_token
у OAuthClient будет возможность обрабатывать обмен токена даже после истечения его срока действия, поэтому нам не нужно будет проходить окно аутентификации google каждый час после истечения срока действия токена.
️⌨️Пример использования
Если вы любите изучать документацию, посетите страницу google-apis docs или посмотрите на Calendar, его мы используем в примере ниже.
?Использование службы календаря SDK
Права доступа
Убедитесь, что используемый вами аккаунт имеет доступ к календарю, события которого вы хотите прочесть.
Пример кода
Укажите для класса Calendar метод аутентификации по умолчанию, в нашем случае это OAuth. Добавьте к перечню провайдеров app.module.ts
следующее:
{
provide: calendar_v3.Calendar,
useFactory:
(auth: OAuth2Client) => new calendar_v3.Calendar({ auth }),
deps: [OAuth2Client],
},
Теперь у нас есть доступ к полному набору функций API календаря Google с полностью типизированным интерфейсом SDK.
Вы можете получить календарь как любой другой сервис Angular, использовать его как параметр конструктора. Его вам предоставит внедрение зависимости.
constructor(
private calendar: calendar_v3.Calendar,
) {}
Вот пример, как получить список событий определенного календаря. Мы также отфильтруем только события, помеченные значком “сегодня”, не удаленные и не отмененные.
this.calendar.events.list({
// обязательно; электронная почта или почта как id календаря
calendarId: CALENDAR_ID,
// опционально; аргументы, позволяющие фильтровать или выделять нужные события
timeMin: startOfDay(today).toISOString(),
timeMax: endOfDay(today).toISOString(),
showDeleted: false,
singleEvents: true,
}),
Вы также можете выполнять другие задачи, такие как создание событий, но не забывайте запрашивать подходящую область действия при аутентификации.
?Использование SDK аналитики
Установка прав доступа
⮕ Analytics Console ⮕ Admin ⮕ Account column ⮕ User Management ⮕
⮕ Select the user ⮕ Activate the "Read & Analyze" checkbox
Получение View ID
⮕ Analytics Console ⮕ Admin ⮕ View column ⮕ View Settings ⮕
⮕ Copy the "View ID" number
Пример кода
Как и в предыдущем примере, нам нужен провайдер. Укажите для класса аналитики метод аутентификации по умолчанию. Добавьте к перечню провайдеров app.module.ts
следующее:
{
provide: analytics_v3.Analytics,
useFactory:
(auth: OAuth2Client) => new analytics_v3.Analytics({ auth }),
deps: [OAuth2Client],
},
И снова вы получаете его готовым для вставки при помощи DI в любой позволяющий делать вставки класс.
constructor(
private analytics: analytics_v3.Analytics,
) {}
Этот пример получит определенные метрики для желаемого временного диапазона. В данном случае мы увидим общее количество просмотров страницы за 30 дней.
this.analytics.data.ga.get({
ids: 'ga:xxxxxxxxx', // замените xxxxxxxxx вашим view ID
'start-date': '30daysAgo',
'end-date': 'today',
metrics: 'ga:pageviews',
})
?Заключение
Иметь готовый полностью типизированный SDK для API — это совершенно другое дело в сравнении с необходимостью искать информацию в документации, особенно, модели данных. Аутентификационная часть проблемы тоже решена довольно удобно, поэтому не остановит людей, которые не знают или не хотят разбираться, как это работает. В целом гораздо проще создавать функции, когда среда проекта и инструменты настроены правильно.
Читайте также:
- Повесть об однонаправленном потоке данных в Angular
- Автоматизация обновления Angular
- Платформы Angular в деталях. Часть 1. Что такое платформы Angular?
Перевод статьи Vojtech Mašek: Integrating Google APIs with Angular