Angular

💡Мотивация

Задача: нужно получить доступ или отобразить события частного google-календаря.
Проблема: нельзя поместить частный календарь внутрь iframe или запросить события, используя ключ API.
Необходимые условия: совместимость с Angular, поддержка TypeScript (сервисные врапперы, классы и типы моделей данных)
Решение: google-api-nodejs-client, предоставляющий все, что нужно.

Google официально поддерживает клиентскую библиотеку для доступа к Google API; также включена поддержка авторизации и аутентификации с помощью OAuth 2.0, API-ключей и JWT .

Теперь разбираемся, как заменить часть 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 есть в браузере.

  1. Имитируем некоторые внутренние среды выполнения, такие как global и Buffer.
  2. Имитируем несколько библиотек, которые требуются при выполнении 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 = {};.

🔑Настройка доступа

  1. Создаем новый GCP проект или используем существующий. 
  2. В консоли GCP выделяем проект и переходим во вкладку Библиотека и разрешаем использование нужного API (в нашем случае Google Календарь и Аналитика).
  3. Настраиваем экран разрешений OAuth (имя, ограничения…) — он задает все нужные права доступа к API (календарь и аналитика).
  4. Создаем учётные данные клиента OAuth —они нужны при доступе к личным данным (таким как события в календаре или аналитика).
  5. Переходим к аутентификации и используем публичный и приватный ключи.

🔓Аутентификация

Большинство публичных данных из 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 — это совершенно другое дело в сравнении с необходимостью искать информацию в документации, особенно, модели данных. Аутентификационная часть проблемы тоже решена довольно удобно, поэтому не остановит людей, которые не знают или не хотят разбираться, как это работает. В целом гораздо проще создавать функции, когда среда проекта и инструменты настроены правильно.

Читайте также:


Перевод статьи Vojtech Mašek: Integrating Google APIs with Angular