@ts-core/oauth 3.1.16 → 3.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1 +1,445 @@
1
- Библиотека предназначена для реализации процесса авторизации пользователей через различные OAuth-сервисы, включая популярные платформы, такие как Google, VK, Яндекс и т.д. Библиотека облегчает интеграцию OAuth аутентификации в веб-приложениях, предоставляя общедоступный интерфейс для работы с различными провайдерами. Она настраиваемая и предоставляет пользователям обширный функционал для работы с различными провайдерами аутентификации, что делает её универсальным инструментом для разработчиков.
1
+ # @ts-core/oauth
2
+
3
+ [![npm version](https://badge.fury.io/js/%40ts-core%2Foauth.svg)](https://www.npmjs.com/package/@ts-core/oauth)
4
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0-blue.svg)](https://www.typescriptlang.org/)
5
+ [![License: ISC](https://img.shields.io/badge/License-ISC-green.svg)](https://opensource.org/licenses/ISC)
6
+
7
+ TypeScript-библиотека для OAuth-аутентификации через различные провайдеры. Поддерживает браузеры, Cordova и Telegram Web Apps.
8
+
9
+ ## Возможности
10
+
11
+ - **6 провайдеров**: Google, VK, Яндекс, Mail.ru, Keycloak, Telegram
12
+ - **3 платформы**: Browser (pop-up), Cordova (плагины), Telegram Web App
13
+ - **Унифицированный API**: единый интерфейс для всех провайдеров
14
+ - **TypeScript**: полная типизация, дженерики, интерфейсы
15
+ - **Минимум зависимостей**: только `@ts-core/common`
16
+
17
+ ---
18
+
19
+ ## Установка
20
+
21
+ ```bash
22
+ npm install @ts-core/oauth
23
+ ```
24
+
25
+ ---
26
+
27
+ ## Быстрый старт
28
+
29
+ ### Google OAuth
30
+
31
+ ```typescript
32
+ import { GoAuth, GoUser } from '@ts-core/oauth';
33
+ import { NullLogger } from '@ts-core/common';
34
+
35
+ const auth = new GoAuth(new NullLogger(), 'YOUR_CLIENT_ID');
36
+
37
+ // Получение токена через pop-up
38
+ const { codeOrToken } = await auth.getToken();
39
+
40
+ // Получение профиля пользователя
41
+ const user: GoUser = await auth.getProfile(codeOrToken);
42
+ console.log(user.name, user.email, user.picture);
43
+
44
+ // Не забудьте освободить ресурсы
45
+ auth.destroy();
46
+ ```
47
+
48
+ ### VK OAuth
49
+
50
+ ```typescript
51
+ import { VkAuth, VkUser } from '@ts-core/oauth';
52
+ import { NullLogger } from '@ts-core/common';
53
+
54
+ const auth = new VkAuth(new NullLogger(), 'YOUR_APP_ID');
55
+
56
+ // Получение кода авторизации
57
+ const { codeOrToken, redirectUri } = await auth.getCode();
58
+
59
+ // Обмен кода на токен (на сервере)
60
+ const token = await auth.getTokenByCode({ codeOrToken, redirectUri }, 'YOUR_SECRET');
61
+
62
+ // Получение профиля
63
+ const user: VkUser = await auth.getProfile(token.accessToken);
64
+ console.log(user.name, user.email, user.city);
65
+ ```
66
+
67
+ ### Яндекс OAuth
68
+
69
+ ```typescript
70
+ import { YaAuth, YaUser } from '@ts-core/oauth';
71
+ import { NullLogger } from '@ts-core/common';
72
+
73
+ const auth = new YaAuth(new NullLogger(), 'YOUR_CLIENT_ID');
74
+ const { codeOrToken } = await auth.getToken();
75
+ const user: YaUser = await auth.getProfile(codeOrToken);
76
+ ```
77
+
78
+ ### Mail.ru OAuth
79
+
80
+ ```typescript
81
+ import { MaAuth, MaUser } from '@ts-core/oauth';
82
+ import { NullLogger } from '@ts-core/common';
83
+
84
+ const auth = new MaAuth(new NullLogger(), 'YOUR_CLIENT_ID');
85
+ const { codeOrToken } = await auth.getToken();
86
+ const user: MaUser = await auth.getProfile(codeOrToken);
87
+ ```
88
+
89
+ ### Keycloak OAuth
90
+
91
+ ```typescript
92
+ import { KeycloakAuth, KeycloakUser, IKeycloakAuthSettings } from '@ts-core/oauth';
93
+ import { NullLogger } from '@ts-core/common';
94
+
95
+ const settings: IKeycloakAuthSettings = {
96
+ url: 'https://keycloak.example.com',
97
+ realm: 'my-realm',
98
+ clientId: 'my-client'
99
+ };
100
+
101
+ const auth = new KeycloakAuth(new NullLogger(), settings);
102
+ const { codeOrToken, redirectUri } = await auth.getCode();
103
+ const token = await auth.getTokenByCode({ codeOrToken, redirectUri }, 'YOUR_SECRET');
104
+ const user: KeycloakUser = await auth.getProfile(token.accessToken);
105
+
106
+ // Logout через pop-up окно
107
+ await auth.logout();
108
+ ```
109
+
110
+ > **Logout**: метод `logout()` открывает pop-up с Keycloak logout endpoint. Keycloak завершает SSO-сессию и перенаправляет на redirect URI. Когда redirect-страница вызывает `window.close()`, промис резолвится.
111
+
112
+ ### Telegram Web App
113
+
114
+ ```typescript
115
+ import { TgAuth, TgUser } from '@ts-core/oauth';
116
+
117
+ // Для Telegram Web Apps — данные из URL hash
118
+ const user: TgUser = TgAuth.getUser(window.location.hash);
119
+ console.log(user.id, user.name);
120
+
121
+ // Получение raw данных для верификации на сервере
122
+ const initData: string = TgAuth.getInitData(window.location.hash);
123
+ ```
124
+
125
+ ### Telegram Login Widget
126
+
127
+ ```typescript
128
+ import { TgAuth, TgApiLoader } from '@ts-core/oauth';
129
+ import { NullLogger } from '@ts-core/common';
130
+
131
+ const api = new TgApiLoader(new NullLogger());
132
+ const auth = new TgAuth(new NullLogger(), { api, botId: YOUR_BOT_ID });
133
+
134
+ const user = await auth.getUser();
135
+ console.log(user.name, user.telegram);
136
+ ```
137
+
138
+ ---
139
+
140
+ ## Архитектура
141
+
142
+ ```
143
+ ┌─────────────────────────────────────────────────────────────┐
144
+ │ PopUpBase<U> │
145
+ │ • Управление pop-up окном │
146
+ │ • Обработка событий postMessage │
147
+ │ • RxJS Observable для lifecycle событий │
148
+ └─────────────────────────────────────────────────────────────┘
149
+
150
+
151
+ ┌─────────────────────────────────────────────────────────────┐
152
+ │ OAuthBase<T> │
153
+ │ • OAuth flow: getCode(), getToken() │
154
+ │ • Абстрактные методы: getProfile(), getTokenByCode() │
155
+ │ • HTTP клиент, state management │
156
+ └─────────────────────────────────────────────────────────────┘
157
+
158
+ ┌───────────────────┼───────────────────┐
159
+ ▼ ▼ ▼
160
+ ┌─────────┐ ┌─────────┐ ┌───────────┐
161
+ │ GoAuth │ │ VkAuth │ │KeycloakAuth│
162
+ └─────────┘ └─────────┘ └───────────┘
163
+ │ │ │
164
+ ▼ ▼ ▼
165
+ ┌─────────┐ ┌─────────┐ ┌───────────┐
166
+ │ GoUser │ │ VkUser │ │KeycloakUser│
167
+ └─────────┘ └─────────┘ └───────────┘
168
+ ```
169
+
170
+ ### Провайдеры
171
+
172
+ | Провайдер | Auth класс | User класс | Особенности |
173
+ |-----------|------------|------------|-------------|
174
+ | Google | `GoAuth` | `GoUser` | People API для расширенных данных |
175
+ | VK | `VkAuth` | `VkUser` | Email из токена |
176
+ | Яндекс | `YaAuth` | `YaUser` | Стандартный OAuth 2.0 |
177
+ | Mail.ru | `MaAuth` | `MaUser` | Стандартный OAuth 2.0 |
178
+ | Keycloak | `KeycloakAuth` | `KeycloakUser` | Настраиваемый realm/URL, logout через pop-up |
179
+ | Telegram | `TgAuth` | `TgUser` | Web App + Login Widget |
180
+
181
+ ---
182
+
183
+ ## API Reference
184
+
185
+ ### OAuthBase<T>
186
+
187
+ Базовый класс для всех OAuth провайдеров.
188
+
189
+ ```typescript
190
+ class OAuthBase<T> extends PopUpBase<IOAuthDto> {
191
+ constructor(logger: ILogger, applicationId: string, window?: Window);
192
+
193
+ // Основные методы
194
+ getCode(): Promise<IOAuthDto>; // OAuth flow с response_type=code
195
+ getToken(): Promise<IOAuthDto>; // OAuth flow с response_type=token
196
+
197
+ // Абстрактные методы (реализуются в провайдерах)
198
+ abstract getProfile(token: string): Promise<T>;
199
+ abstract getTokenByCode(dto: IOAuthDto, secret: string): Promise<IOAuthToken>;
200
+ abstract popUpUrl(): string;
201
+
202
+ // Настройки
203
+ redirectUri: string; // Кастомный redirect URI
204
+ popUpOpener: IPopUpOpener; // Функция открытия pop-up (browser/cordova)
205
+
206
+ // Свойства
207
+ readonly state: string; // Случайный state для CSRF защиты
208
+ readonly http: TransportHttp; // HTTP клиент
209
+
210
+ destroy(): void; // Освобождение ресурсов
211
+ }
212
+
213
+ // Тип функции открытия pop-up (поддерживает опциональный url для logout и др.)
214
+ type IPopUpOpener = <T extends PopUpBase<U>, U>(popUp: T, window: Window, url?: string) => Window;
215
+
216
+ ```
217
+
218
+ ### OAuthUser
219
+
220
+ Базовый класс пользователя с унифицированными полями.
221
+
222
+ ```typescript
223
+ abstract class OAuthUser {
224
+ raw: any; // Оригинальный ответ API
225
+
226
+ id: string; // Уникальный ID пользователя
227
+ name: string; // Полное имя
228
+ email?: string; // Email
229
+ phone?: string; // Телефон
230
+ picture?: string; // URL аватара
231
+ nickname?: string; // Никнейм/username
232
+
233
+ isMale?: boolean; // Пол
234
+ birthday?: Date; // Дата рождения
235
+
236
+ city?: string; // Город
237
+ country?: string; // Страна
238
+ locale?: string; // Локаль
239
+
240
+ status?: string; // Статус
241
+ description?: string; // Описание/bio
242
+
243
+ // Социальные сети
244
+ vk?: string;
245
+ facebook?: string;
246
+ telegram?: string;
247
+ instagram?: string;
248
+
249
+ // Геттеры
250
+ readonly location: string; // "Country, City"
251
+ }
252
+ ```
253
+
254
+ ### KeycloakAuth
255
+
256
+ ```typescript
257
+ class KeycloakAuth<T extends KeycloakUser> extends OAuthBase<T> {
258
+ constructor(logger: ILogger, settings: IKeycloakAuthSettings, window?: Window);
259
+
260
+ // Logout через pop-up окно
261
+ // Открывает Keycloak OIDC logout endpoint, ждёт закрытия окна
262
+ logout(): Promise<void>;
263
+
264
+ // Реализация OAuthBase
265
+ getProfile(token: string): Promise<T>;
266
+ getTokenByCode(dto: IOAuthDto, secret: string): Promise<IOAuthToken>;
267
+
268
+ readonly settings: IKeycloakAuthSettings;
269
+ }
270
+ ```
271
+
272
+ ### Интерфейсы
273
+
274
+ ```typescript
275
+ // Результат OAuth flow
276
+ interface IOAuthDto {
277
+ codeOrToken: string; // Код или токен
278
+ redirectUri: string; // Использованный redirect URI
279
+ }
280
+
281
+ // Токен авторизации
282
+ interface IOAuthToken {
283
+ accessToken: string;
284
+ expiresIn: number;
285
+
286
+ state?: string;
287
+ scope?: string;
288
+ userId?: number;
289
+ idToken?: string;
290
+ tokenType?: string;
291
+ refreshToken?: string;
292
+ refreshExpiresIn?: string;
293
+ }
294
+
295
+ // Настройки Keycloak
296
+ interface IKeycloakAuthSettings {
297
+ url: string; // URL сервера Keycloak
298
+ realm: string; // Название realm
299
+ clientId: string; // ID клиента
300
+ }
301
+
302
+ // Настройки Telegram
303
+ interface ITgAuthSettings {
304
+ api: ITgApiLoader; // Загрузчик Telegram API
305
+ botId: number; // ID бота
306
+ }
307
+ ```
308
+
309
+ ---
310
+
311
+ ## Платформы
312
+
313
+ ### Browser (по умолчанию)
314
+
315
+ Работает из коробки. Использует `window.open()` для pop-up и `postMessage` для коммуникации.
316
+
317
+ **Требования к redirect странице:**
318
+
319
+ ```html
320
+ <!-- /oauth.html — обрабатывает и login callback, и logout redirect -->
321
+ <script>
322
+ const params = new URLSearchParams(window.location.search + window.location.hash.replace('#', '&'));
323
+ const code = params.get('code') || params.get('access_token');
324
+ if (code || params.get('error')) {
325
+ // Login callback — отправляем данные родительскому окну
326
+ window.opener.postMessage({
327
+ oAuthCodeOrToken: code,
328
+ oAuthError: params.get('error'),
329
+ oAuthErrorDescription: params.get('error_description')
330
+ }, '*');
331
+ }
332
+ // Закрываем pop-up (работает и для login, и для logout redirect)
333
+ window.close();
334
+ </script>
335
+ ```
336
+
337
+ ### Cordova OAuth Plugin
338
+
339
+ ```typescript
340
+ import { CordovaOAuthPluginPropertiesSet } from '@ts-core/oauth';
341
+
342
+ const auth = new GoAuth(logger, 'CLIENT_ID');
343
+ CordovaOAuthPluginPropertiesSet(auth);
344
+ ```
345
+
346
+ ### Cordova InAppBrowser
347
+
348
+ ```typescript
349
+ import { CordovaInAppBrowserPluginPropertiesSet } from '@ts-core/oauth';
350
+
351
+ const auth = new GoAuth(logger, 'CLIENT_ID');
352
+ CordovaInAppBrowserPluginPropertiesSet(auth, async (url) => {
353
+ // Ваша логика получения токена по URL
354
+ return { codeOrToken: 'token', redirectUri: 'uri' };
355
+ });
356
+ ```
357
+
358
+ ---
359
+
360
+ ## Расширенные возможности
361
+
362
+ ### Google People API
363
+
364
+ ```typescript
365
+ const auth = new GoAuth(logger, 'CLIENT_ID');
366
+ auth.personFields = 'genders,birthdays'; // Запросить дополнительные поля
367
+
368
+ const user = await auth.getProfile(token);
369
+ console.log(user.isMale, user.birthday);
370
+ ```
371
+
372
+ ### Кастомные scopes
373
+
374
+ ```typescript
375
+ const auth = new VkAuth(logger, 'APP_ID');
376
+ // Изменить scopes через params
377
+ auth.params.set('scope', 'friends,photos,email');
378
+ ```
379
+
380
+ ### Кастомный redirect URI
381
+
382
+ ```typescript
383
+ const auth = new GoAuth(logger, 'CLIENT_ID');
384
+ auth.redirectUri = 'https://myapp.com/callback';
385
+ ```
386
+
387
+ ### События lifecycle
388
+
389
+ ```typescript
390
+ import { PopUpBaseEvent } from '@ts-core/oauth';
391
+
392
+ auth.events.subscribe(event => {
393
+ if (event.type === PopUpBaseEvent.OPENED) {
394
+ console.log('Pop-up opened');
395
+ }
396
+ if (event.type === PopUpBaseEvent.CLOSED) {
397
+ console.log('Pop-up closed');
398
+ }
399
+ });
400
+ ```
401
+
402
+ ---
403
+
404
+ ## Структура проекта
405
+
406
+ ```
407
+ src/
408
+ ├── OAuthBase.ts # Базовый класс OAuth
409
+ ├── OAuthUser.ts # Базовый класс пользователя
410
+ ├── OAuthParser.ts # Парсер URL параметров
411
+ ├── PopUpBase.ts # Управление pop-up окнами
412
+ ├── public-api.ts # Единая точка экспорта
413
+ ├── external/ # Платформенные интеграции
414
+ │ ├── browser.ts
415
+ │ ├── cordovaOAuthPlugin.ts
416
+ │ └── cordovaInAppBrowserPlugin.ts
417
+ ├── go/ # Google
418
+ ├── vk/ # VK
419
+ ├── ya/ # Яндекс
420
+ ├── ma/ # Mail.ru
421
+ ├── keycloak/ # Keycloak
422
+ └── tg/ # Telegram
423
+ ```
424
+
425
+ ---
426
+
427
+ ## Зависимости
428
+
429
+ | Пакет | Версия | Назначение |
430
+ |-------|--------|------------|
431
+ | `@ts-core/common` | ~3.0.43 | Logger, HTTP, утилиты |
432
+
433
+ ---
434
+
435
+ ## Лицензия
436
+
437
+ ISC License
438
+
439
+ ---
440
+
441
+ ## Автор
442
+
443
+ **Renat Gubaev** — [renat.gubaev@gmail.com](mailto:renat.gubaev@gmail.com)
444
+
445
+ GitHub: [ManhattanDoctor/ts-core-oauth](https://github.com/ManhattanDoctor/ts-core-oauth)
@@ -8,8 +8,8 @@ export declare abstract class OAuthBase<T = any> extends PopUpBase<IOAuthDto> {
8
8
  constructor(logger: ILogger, applicationId: string, window?: Window);
9
9
  protected getRedirectUri(): string;
10
10
  protected getParams(): URLSearchParams;
11
- protected parseMessageData(item: any): IOAuthDto;
12
- protected isMessageError(item: any): boolean;
11
+ protected parseMessageData(item: IOAuthPopUpDto): IOAuthDto;
12
+ protected isMessageError(item: IOAuthPopUpDto): boolean;
13
13
  protected parseMessageError(item: any): ExtendedError;
14
14
  abstract getProfile(token: string, ...params: any[]): Promise<T>;
15
15
  abstract getTokenByCode(dto: IOAuthDto, secret: string): Promise<IOAuthToken>;
@@ -24,6 +24,8 @@ export interface IOAuthDto {
24
24
  codeOrToken: string;
25
25
  redirectUri: string;
26
26
  }
27
+ export interface IOAuthLogoutDto {
28
+ }
27
29
  export interface IOAuthPopUpDto {
28
30
  oAuthError?: string;
29
31
  oAuthCodeOrToken?: string;
@@ -38,8 +38,8 @@ export declare abstract class PopUpBase<U> extends LoggerWrapper {
38
38
  get closed(): Observable<Window>;
39
39
  get opened(): Observable<Window>;
40
40
  }
41
- export declare function popUpOpener<T extends PopUpBase<U>, U>(popUp: T, window: Window): Window;
42
- export type IPopUpOpener = <T extends PopUpBase<U>, U>(popUp: T, window: Window) => Window;
41
+ export declare function popUpOpener<T extends PopUpBase<U>, U>(popUp: T, window: Window, url?: string): Window;
42
+ export type IPopUpOpener = <T extends PopUpBase<U>, U>(popUp: T, window: Window, url?: string) => Window;
43
43
  export type IPopUpMessageEventParser = (event: MessageEvent) => any;
44
44
  export declare enum PopUpEvent {
45
45
  OPENED = "OPENED",
package/cjs/PopUpBase.js CHANGED
@@ -140,10 +140,13 @@ class PopUpBase extends common_1.LoggerWrapper {
140
140
  }
141
141
  exports.PopUpBase = PopUpBase;
142
142
  PopUpBase.ERROR_WINDOW_CLOSED = 'WINDOW_CLOSED';
143
- function popUpOpener(popUp, window) {
143
+ function popUpOpener(popUp, window, url) {
144
+ if (_.isNil(url)) {
145
+ url = popUp.popUpUrl();
146
+ }
144
147
  let top = (window.screen.height - popUp.popUpHeight) / 2;
145
148
  let left = (window.screen.width - popUp.popUpWidth) / 2;
146
- return window.open(popUp.popUpUrl(), popUp.popUpTarget, `scrollbars=yes,width=${popUp.popUpWidth},height=${popUp.popUpHeight},top=${top},left=${left}`);
149
+ return window.open(url, popUp.popUpTarget, `scrollbars=yes,width=${popUp.popUpWidth},height=${popUp.popUpHeight},top=${top},left=${left}`);
147
150
  }
148
151
  exports.popUpOpener = popUpOpener;
149
152
  var PopUpEvent;
@@ -0,0 +1,8 @@
1
+ import type { TgAuth } from '../tg/TgAuth';
2
+ export declare const TgAuthCordovaInAppBrowserPluginPropertiesSet: (item: TgAuth, options: ITgAuthCordovaInAppBrowserOptions) => void;
3
+ export interface ITgAuthCordovaInAppBrowserOptions {
4
+ origin: string;
5
+ returnUrl: string;
6
+ requestAccess?: 'write';
7
+ inAppBrowserOptions?: string;
8
+ }
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TgAuthCordovaInAppBrowserPluginPropertiesSet = void 0;
4
+ const common_1 = require("@ts-core/common");
5
+ const PopUpBase_1 = require("../PopUpBase");
6
+ const TgUser_1 = require("../tg/TgUser");
7
+ const _ = require("lodash");
8
+ const TgAuthCordovaInAppBrowserPluginPropertiesSet = (item, options) => {
9
+ let returnUrl = options.returnUrl;
10
+ let origin = options.origin;
11
+ let requestAccess = options.requestAccess;
12
+ let inAppBrowserOptions = !_.isNil(options.inAppBrowserOptions) ? options.inAppBrowserOptions : 'location=no,clearcache=yes,clearsessioncache=yes';
13
+ item.userProvider = (botId) => new Promise((resolve, reject) => {
14
+ let cordova = window['cordova'];
15
+ if (_.isNil(cordova) || _.isNil(cordova.InAppBrowser)) {
16
+ reject(new common_1.ExtendedError(`Cordova InAppBrowser undefined, please check installed plugins`));
17
+ return;
18
+ }
19
+ let url = `https://oauth.telegram.org/auth?bot_id=${botId}`
20
+ + `&origin=${encodeURIComponent(origin)}`
21
+ + `&return_to=${encodeURIComponent(returnUrl)}`;
22
+ if (!_.isNil(requestAccess)) {
23
+ url += `&request_access=${requestAccess}`;
24
+ }
25
+ let popUp = cordova.InAppBrowser.open(url, '_blank', inAppBrowserOptions);
26
+ let isSettled = false;
27
+ let cleanup = () => {
28
+ popUp.removeEventListener('loadstart', onLoad);
29
+ popUp.removeEventListener('loadstop', onLoad);
30
+ popUp.removeEventListener('exit', onExit);
31
+ };
32
+ let onLoad = (event) => {
33
+ if (isSettled || _.isNil(event) || _.isNil(event.url) || event.url.indexOf(returnUrl) !== 0) {
34
+ return;
35
+ }
36
+ let user = parseTgAuthResult(event.url);
37
+ if (_.isNil(user)) {
38
+ return;
39
+ }
40
+ isSettled = true;
41
+ cleanup();
42
+ popUp.close();
43
+ resolve(user);
44
+ };
45
+ let onExit = () => {
46
+ cleanup();
47
+ if (!isSettled) {
48
+ reject(new common_1.ExtendedError(PopUpBase_1.PopUpBase.ERROR_WINDOW_CLOSED, PopUpBase_1.PopUpBase.ERROR_WINDOW_CLOSED));
49
+ }
50
+ };
51
+ popUp.addEventListener('loadstart', onLoad, false);
52
+ popUp.addEventListener('loadstop', onLoad, false);
53
+ popUp.addEventListener('exit', onExit, false);
54
+ });
55
+ };
56
+ exports.TgAuthCordovaInAppBrowserPluginPropertiesSet = TgAuthCordovaInAppBrowserPluginPropertiesSet;
57
+ function parseTgAuthResult(url) {
58
+ let raw = null;
59
+ let hashIndex = url.indexOf('#');
60
+ if (hashIndex >= 0) {
61
+ let value = new URLSearchParams(url.substring(hashIndex + 1)).get('tgAuthResult');
62
+ if (!_.isEmpty(value)) {
63
+ try {
64
+ raw = base64UrlDecode(value);
65
+ }
66
+ catch (error) { }
67
+ }
68
+ }
69
+ let data = null;
70
+ if (!_.isNil(raw)) {
71
+ try {
72
+ data = JSON.parse(raw);
73
+ }
74
+ catch (error) {
75
+ return null;
76
+ }
77
+ }
78
+ else {
79
+ let qIndex = url.indexOf('?');
80
+ if (qIndex < 0) {
81
+ return null;
82
+ }
83
+ let query = new URLSearchParams(url.substring(qIndex + 1, hashIndex >= 0 ? hashIndex : undefined));
84
+ if (_.isEmpty(query.get('hash'))) {
85
+ return null;
86
+ }
87
+ data = {};
88
+ query.forEach((v, k) => data[k] = v);
89
+ }
90
+ if (_.isNil(data) || _.isNil(data.hash)) {
91
+ return null;
92
+ }
93
+ let user = new TgUser_1.TgUser();
94
+ user.parse(data);
95
+ return user;
96
+ }
97
+ function base64UrlDecode(value) {
98
+ let normalized = value.replace(/-/g, '+').replace(/_/g, '/');
99
+ let pad = normalized.length % 4;
100
+ if (pad !== 0) {
101
+ normalized += '='.repeat(4 - pad);
102
+ }
103
+ let binary = atob(normalized);
104
+ let bytes = Uint8Array.from(binary, c => c.charCodeAt(0));
105
+ return new TextDecoder().decode(bytes);
106
+ }
@@ -1,3 +1,4 @@
1
1
  export * from './browser';
2
2
  export * from './cordovaOAuthPlugin';
3
3
  export * from './cordovaInAppBrowserPlugin';
4
+ export * from './cordovaInAppBrowserTgPlugin';
@@ -17,3 +17,4 @@ Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./browser"), exports);
18
18
  __exportStar(require("./cordovaOAuthPlugin"), exports);
19
19
  __exportStar(require("./cordovaInAppBrowserPlugin"), exports);
20
+ __exportStar(require("./cordovaInAppBrowserTgPlugin"), exports);
@@ -5,9 +5,11 @@ export declare class KeycloakAuth<T extends KeycloakUser = KeycloakUser> extends
5
5
  protected _settings: IKeycloakAuthSettings;
6
6
  constructor(logger: ILogger, settings: IKeycloakAuthSettings, window?: Window);
7
7
  protected getBaseUrl(): string;
8
+ protected getLogoutRedirectUri(): string;
8
9
  popUpUrl(): string;
9
10
  getProfile(token: string): Promise<T>;
10
11
  getTokenByCode(dto: IOAuthDto, secret: string): Promise<IOAuthToken>;
12
+ logout(): Promise<void>;
11
13
  destroy(): void;
12
14
  get settings(): IKeycloakAuthSettings;
13
15
  }
@@ -25,6 +25,9 @@ class KeycloakAuth extends OAuthBase_1.OAuthBase {
25
25
  getBaseUrl() {
26
26
  return `${this.settings.url}/realms/${this.settings.realm}/protocol/openid-connect`;
27
27
  }
28
+ getLogoutRedirectUri() {
29
+ return this.getRedirectUri();
30
+ }
28
31
  popUpUrl() {
29
32
  return `${this.getBaseUrl()}/auth?${this.getParams().toString()}`;
30
33
  }
@@ -62,6 +65,23 @@ class KeycloakAuth extends OAuthBase_1.OAuthBase {
62
65
  };
63
66
  });
64
67
  }
68
+ logout() {
69
+ return __awaiter(this, void 0, void 0, function* () {
70
+ let params = new URLSearchParams();
71
+ params.append('client_id', this.applicationId);
72
+ params.append('post_logout_redirect_uri', this.getLogoutRedirectUri());
73
+ let url = `${this.getBaseUrl()}/logout?${params.toString()}`;
74
+ let popUp = this.popUpOpener(this, this.window, url);
75
+ return new Promise(resolve => {
76
+ let timer = setInterval(() => {
77
+ if (_.isNil(popUp) || popUp.closed) {
78
+ clearInterval(timer);
79
+ resolve();
80
+ }
81
+ }, common_1.DateUtil.MILLISECONDS_SECOND / 5);
82
+ });
83
+ });
84
+ }
65
85
  destroy() {
66
86
  if (this.isDestroyed) {
67
87
  return;
@@ -18,3 +18,4 @@ export * from './keycloak/KeycloakUser';
18
18
  export * from './external/browser';
19
19
  export * from './external/cordovaOAuthPlugin';
20
20
  export * from './external/cordovaInAppBrowserPlugin';
21
+ export * from './external/cordovaInAppBrowserTgPlugin';
package/cjs/public-api.js CHANGED
@@ -34,3 +34,4 @@ __exportStar(require("./keycloak/KeycloakUser"), exports);
34
34
  __exportStar(require("./external/browser"), exports);
35
35
  __exportStar(require("./external/cordovaOAuthPlugin"), exports);
36
36
  __exportStar(require("./external/cordovaInAppBrowserPlugin"), exports);
37
+ __exportStar(require("./external/cordovaInAppBrowserTgPlugin"), exports);
@@ -8,6 +8,7 @@ export declare class TgAuth extends LoggerWrapper {
8
8
  private static urlParseHashParams;
9
9
  private static urlParseQueryString;
10
10
  private static urlSafeDecode;
11
+ userProvider: ITgUserProvider;
11
12
  protected promise: PromiseHandler<TgUser, ExtendedError>;
12
13
  protected settings: ITgAuthSettings;
13
14
  constructor(logger: ILogger, settings: ITgAuthSettings);
@@ -18,3 +19,4 @@ export interface ITgAuthSettings {
18
19
  api: ITgApiLoader;
19
20
  botId: number;
20
21
  }
22
+ export type ITgUserProvider = (botId: number) => Promise<TgUser>;
package/cjs/tg/TgAuth.js CHANGED
@@ -85,6 +85,18 @@ class TgAuth extends common_1.LoggerWrapper {
85
85
  return this.promise.promise;
86
86
  }
87
87
  this.promise = common_1.PromiseHandler.create();
88
+ if (!_.isNil(this.userProvider)) {
89
+ this.userProvider(this.settings.botId)
90
+ .then(user => {
91
+ this.promise.resolve(user);
92
+ })
93
+ .catch(error => {
94
+ this.promise.reject(error instanceof common_1.ExtendedError ? error : new common_1.ExtendedError(error.message));
95
+ }).finally(() => {
96
+ this.promise = null;
97
+ });
98
+ return this.promise.promise;
99
+ }
88
100
  this.settings.api.getApi()
89
101
  .then(item => {
90
102
  item.Login.auth({ bot_id: this.settings.botId }, item => {
package/cjs/vk/VkAuth.js CHANGED
@@ -21,7 +21,7 @@ class VkAuth extends OAuthBase_1.OAuthBase {
21
21
  this.params.set('scope', 'status,email');
22
22
  }
23
23
  popUpUrl() {
24
- return `https://oauth.vk.com/authorize?${this.getParams().toString()}`;
24
+ return `https://id.vk.ru/authorize?${this.getParams().toString()}`;
25
25
  }
26
26
  getProfile(token, fields) {
27
27
  return __awaiter(this, void 0, void 0, function* () {
@@ -8,8 +8,8 @@ export declare abstract class OAuthBase<T = any> extends PopUpBase<IOAuthDto> {
8
8
  constructor(logger: ILogger, applicationId: string, window?: Window);
9
9
  protected getRedirectUri(): string;
10
10
  protected getParams(): URLSearchParams;
11
- protected parseMessageData(item: any): IOAuthDto;
12
- protected isMessageError(item: any): boolean;
11
+ protected parseMessageData(item: IOAuthPopUpDto): IOAuthDto;
12
+ protected isMessageError(item: IOAuthPopUpDto): boolean;
13
13
  protected parseMessageError(item: any): ExtendedError;
14
14
  abstract getProfile(token: string, ...params: any[]): Promise<T>;
15
15
  abstract getTokenByCode(dto: IOAuthDto, secret: string): Promise<IOAuthToken>;
@@ -24,6 +24,8 @@ export interface IOAuthDto {
24
24
  codeOrToken: string;
25
25
  redirectUri: string;
26
26
  }
27
+ export interface IOAuthLogoutDto {
28
+ }
27
29
  export interface IOAuthPopUpDto {
28
30
  oAuthError?: string;
29
31
  oAuthCodeOrToken?: string;
@@ -38,8 +38,8 @@ export declare abstract class PopUpBase<U> extends LoggerWrapper {
38
38
  get closed(): Observable<Window>;
39
39
  get opened(): Observable<Window>;
40
40
  }
41
- export declare function popUpOpener<T extends PopUpBase<U>, U>(popUp: T, window: Window): Window;
42
- export type IPopUpOpener = <T extends PopUpBase<U>, U>(popUp: T, window: Window) => Window;
41
+ export declare function popUpOpener<T extends PopUpBase<U>, U>(popUp: T, window: Window, url?: string): Window;
42
+ export type IPopUpOpener = <T extends PopUpBase<U>, U>(popUp: T, window: Window, url?: string) => Window;
43
43
  export type IPopUpMessageEventParser = (event: MessageEvent) => any;
44
44
  export declare enum PopUpEvent {
45
45
  OPENED = "OPENED",
package/esm/PopUpBase.js CHANGED
@@ -136,10 +136,13 @@ export class PopUpBase extends LoggerWrapper {
136
136
  }
137
137
  }
138
138
  PopUpBase.ERROR_WINDOW_CLOSED = 'WINDOW_CLOSED';
139
- export function popUpOpener(popUp, window) {
139
+ export function popUpOpener(popUp, window, url) {
140
+ if (_.isNil(url)) {
141
+ url = popUp.popUpUrl();
142
+ }
140
143
  let top = (window.screen.height - popUp.popUpHeight) / 2;
141
144
  let left = (window.screen.width - popUp.popUpWidth) / 2;
142
- return window.open(popUp.popUpUrl(), popUp.popUpTarget, `scrollbars=yes,width=${popUp.popUpWidth},height=${popUp.popUpHeight},top=${top},left=${left}`);
145
+ return window.open(url, popUp.popUpTarget, `scrollbars=yes,width=${popUp.popUpWidth},height=${popUp.popUpHeight},top=${top},left=${left}`);
143
146
  }
144
147
  export var PopUpEvent;
145
148
  (function (PopUpEvent) {
@@ -0,0 +1,8 @@
1
+ import type { TgAuth } from '../tg/TgAuth';
2
+ export declare const TgAuthCordovaInAppBrowserPluginPropertiesSet: (item: TgAuth, options: ITgAuthCordovaInAppBrowserOptions) => void;
3
+ export interface ITgAuthCordovaInAppBrowserOptions {
4
+ origin: string;
5
+ returnUrl: string;
6
+ requestAccess?: 'write';
7
+ inAppBrowserOptions?: string;
8
+ }
@@ -0,0 +1,102 @@
1
+ import { ExtendedError } from '@ts-core/common';
2
+ import { PopUpBase } from '../PopUpBase';
3
+ import { TgUser } from '../tg/TgUser';
4
+ import * as _ from 'lodash';
5
+ export const TgAuthCordovaInAppBrowserPluginPropertiesSet = (item, options) => {
6
+ let returnUrl = options.returnUrl;
7
+ let origin = options.origin;
8
+ let requestAccess = options.requestAccess;
9
+ let inAppBrowserOptions = !_.isNil(options.inAppBrowserOptions) ? options.inAppBrowserOptions : 'location=no,clearcache=yes,clearsessioncache=yes';
10
+ item.userProvider = (botId) => new Promise((resolve, reject) => {
11
+ let cordova = window['cordova'];
12
+ if (_.isNil(cordova) || _.isNil(cordova.InAppBrowser)) {
13
+ reject(new ExtendedError(`Cordova InAppBrowser undefined, please check installed plugins`));
14
+ return;
15
+ }
16
+ let url = `https://oauth.telegram.org/auth?bot_id=${botId}`
17
+ + `&origin=${encodeURIComponent(origin)}`
18
+ + `&return_to=${encodeURIComponent(returnUrl)}`;
19
+ if (!_.isNil(requestAccess)) {
20
+ url += `&request_access=${requestAccess}`;
21
+ }
22
+ let popUp = cordova.InAppBrowser.open(url, '_blank', inAppBrowserOptions);
23
+ let isSettled = false;
24
+ let cleanup = () => {
25
+ popUp.removeEventListener('loadstart', onLoad);
26
+ popUp.removeEventListener('loadstop', onLoad);
27
+ popUp.removeEventListener('exit', onExit);
28
+ };
29
+ let onLoad = (event) => {
30
+ if (isSettled || _.isNil(event) || _.isNil(event.url) || event.url.indexOf(returnUrl) !== 0) {
31
+ return;
32
+ }
33
+ let user = parseTgAuthResult(event.url);
34
+ if (_.isNil(user)) {
35
+ return;
36
+ }
37
+ isSettled = true;
38
+ cleanup();
39
+ popUp.close();
40
+ resolve(user);
41
+ };
42
+ let onExit = () => {
43
+ cleanup();
44
+ if (!isSettled) {
45
+ reject(new ExtendedError(PopUpBase.ERROR_WINDOW_CLOSED, PopUpBase.ERROR_WINDOW_CLOSED));
46
+ }
47
+ };
48
+ popUp.addEventListener('loadstart', onLoad, false);
49
+ popUp.addEventListener('loadstop', onLoad, false);
50
+ popUp.addEventListener('exit', onExit, false);
51
+ });
52
+ };
53
+ function parseTgAuthResult(url) {
54
+ let raw = null;
55
+ let hashIndex = url.indexOf('#');
56
+ if (hashIndex >= 0) {
57
+ let value = new URLSearchParams(url.substring(hashIndex + 1)).get('tgAuthResult');
58
+ if (!_.isEmpty(value)) {
59
+ try {
60
+ raw = base64UrlDecode(value);
61
+ }
62
+ catch (error) { }
63
+ }
64
+ }
65
+ let data = null;
66
+ if (!_.isNil(raw)) {
67
+ try {
68
+ data = JSON.parse(raw);
69
+ }
70
+ catch (error) {
71
+ return null;
72
+ }
73
+ }
74
+ else {
75
+ let qIndex = url.indexOf('?');
76
+ if (qIndex < 0) {
77
+ return null;
78
+ }
79
+ let query = new URLSearchParams(url.substring(qIndex + 1, hashIndex >= 0 ? hashIndex : undefined));
80
+ if (_.isEmpty(query.get('hash'))) {
81
+ return null;
82
+ }
83
+ data = {};
84
+ query.forEach((v, k) => data[k] = v);
85
+ }
86
+ if (_.isNil(data) || _.isNil(data.hash)) {
87
+ return null;
88
+ }
89
+ let user = new TgUser();
90
+ user.parse(data);
91
+ return user;
92
+ }
93
+ function base64UrlDecode(value) {
94
+ let normalized = value.replace(/-/g, '+').replace(/_/g, '/');
95
+ let pad = normalized.length % 4;
96
+ if (pad !== 0) {
97
+ normalized += '='.repeat(4 - pad);
98
+ }
99
+ let binary = atob(normalized);
100
+ let bytes = Uint8Array.from(binary, c => c.charCodeAt(0));
101
+ return new TextDecoder().decode(bytes);
102
+ }
@@ -1,3 +1,4 @@
1
1
  export * from './browser';
2
2
  export * from './cordovaOAuthPlugin';
3
3
  export * from './cordovaInAppBrowserPlugin';
4
+ export * from './cordovaInAppBrowserTgPlugin';
@@ -1,3 +1,4 @@
1
1
  export * from './browser';
2
2
  export * from './cordovaOAuthPlugin';
3
3
  export * from './cordovaInAppBrowserPlugin';
4
+ export * from './cordovaInAppBrowserTgPlugin';
@@ -5,9 +5,11 @@ export declare class KeycloakAuth<T extends KeycloakUser = KeycloakUser> extends
5
5
  protected _settings: IKeycloakAuthSettings;
6
6
  constructor(logger: ILogger, settings: IKeycloakAuthSettings, window?: Window);
7
7
  protected getBaseUrl(): string;
8
+ protected getLogoutRedirectUri(): string;
8
9
  popUpUrl(): string;
9
10
  getProfile(token: string): Promise<T>;
10
11
  getTokenByCode(dto: IOAuthDto, secret: string): Promise<IOAuthToken>;
12
+ logout(): Promise<void>;
11
13
  destroy(): void;
12
14
  get settings(): IKeycloakAuthSettings;
13
15
  }
@@ -7,7 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { ExtendedError, RandomUtil } from "@ts-core/common";
10
+ import { DateUtil, ExtendedError, RandomUtil } from "@ts-core/common";
11
11
  import { OAuthBase } from "../OAuthBase";
12
12
  import { KeycloakUser } from "./KeycloakUser";
13
13
  import * as _ from 'lodash';
@@ -22,6 +22,9 @@ export class KeycloakAuth extends OAuthBase {
22
22
  getBaseUrl() {
23
23
  return `${this.settings.url}/realms/${this.settings.realm}/protocol/openid-connect`;
24
24
  }
25
+ getLogoutRedirectUri() {
26
+ return this.getRedirectUri();
27
+ }
25
28
  popUpUrl() {
26
29
  return `${this.getBaseUrl()}/auth?${this.getParams().toString()}`;
27
30
  }
@@ -59,6 +62,23 @@ export class KeycloakAuth extends OAuthBase {
59
62
  };
60
63
  });
61
64
  }
65
+ logout() {
66
+ return __awaiter(this, void 0, void 0, function* () {
67
+ let params = new URLSearchParams();
68
+ params.append('client_id', this.applicationId);
69
+ params.append('post_logout_redirect_uri', this.getLogoutRedirectUri());
70
+ let url = `${this.getBaseUrl()}/logout?${params.toString()}`;
71
+ let popUp = this.popUpOpener(this, this.window, url);
72
+ return new Promise(resolve => {
73
+ let timer = setInterval(() => {
74
+ if (_.isNil(popUp) || popUp.closed) {
75
+ clearInterval(timer);
76
+ resolve();
77
+ }
78
+ }, DateUtil.MILLISECONDS_SECOND / 5);
79
+ });
80
+ });
81
+ }
62
82
  destroy() {
63
83
  if (this.isDestroyed) {
64
84
  return;
@@ -18,3 +18,4 @@ export * from './keycloak/KeycloakUser';
18
18
  export * from './external/browser';
19
19
  export * from './external/cordovaOAuthPlugin';
20
20
  export * from './external/cordovaInAppBrowserPlugin';
21
+ export * from './external/cordovaInAppBrowserTgPlugin';
package/esm/public-api.js CHANGED
@@ -18,3 +18,4 @@ export * from './keycloak/KeycloakUser';
18
18
  export * from './external/browser';
19
19
  export * from './external/cordovaOAuthPlugin';
20
20
  export * from './external/cordovaInAppBrowserPlugin';
21
+ export * from './external/cordovaInAppBrowserTgPlugin';
@@ -8,6 +8,7 @@ export declare class TgAuth extends LoggerWrapper {
8
8
  private static urlParseHashParams;
9
9
  private static urlParseQueryString;
10
10
  private static urlSafeDecode;
11
+ userProvider: ITgUserProvider;
11
12
  protected promise: PromiseHandler<TgUser, ExtendedError>;
12
13
  protected settings: ITgAuthSettings;
13
14
  constructor(logger: ILogger, settings: ITgAuthSettings);
@@ -18,3 +19,4 @@ export interface ITgAuthSettings {
18
19
  api: ITgApiLoader;
19
20
  botId: number;
20
21
  }
22
+ export type ITgUserProvider = (botId: number) => Promise<TgUser>;
package/esm/tg/TgAuth.js CHANGED
@@ -82,6 +82,18 @@ export class TgAuth extends LoggerWrapper {
82
82
  return this.promise.promise;
83
83
  }
84
84
  this.promise = PromiseHandler.create();
85
+ if (!_.isNil(this.userProvider)) {
86
+ this.userProvider(this.settings.botId)
87
+ .then(user => {
88
+ this.promise.resolve(user);
89
+ })
90
+ .catch(error => {
91
+ this.promise.reject(error instanceof ExtendedError ? error : new ExtendedError(error.message));
92
+ }).finally(() => {
93
+ this.promise = null;
94
+ });
95
+ return this.promise.promise;
96
+ }
85
97
  this.settings.api.getApi()
86
98
  .then(item => {
87
99
  item.Login.auth({ bot_id: this.settings.botId }, item => {
package/esm/vk/VkAuth.js CHANGED
@@ -18,7 +18,7 @@ export class VkAuth extends OAuthBase {
18
18
  this.params.set('scope', 'status,email');
19
19
  }
20
20
  popUpUrl() {
21
- return `https://oauth.vk.com/authorize?${this.getParams().toString()}`;
21
+ return `https://id.vk.ru/authorize?${this.getParams().toString()}`;
22
22
  }
23
23
  getProfile(token, fields) {
24
24
  return __awaiter(this, void 0, void 0, function* () {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ts-core/oauth",
3
- "version": "3.1.16",
3
+ "version": "3.1.18",
4
4
  "description": "Classes and utils for oauth",
5
5
  "main": "./cjs/public-api.js",
6
6
  "module": "./esm/public-api.js",