streamops-twitch-connector 0.1.5 → 0.2.1

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
@@ -76,6 +76,27 @@ await twitch.connectEventSub();
76
76
 
77
77
  - No dependency on NestJS or Express.
78
78
  - OAuth tokens are persisted through a caller-provided token store.
79
+ - Tokens are validated when first used and then every 50 minutes, within
80
+ Twitch's hourly validation requirement. They are refreshed five minutes
81
+ before their recorded expiry. Helix `401` responses trigger a single
82
+ refresh-and-retry attempt, following Twitch's recommendation to handle
83
+ invalid tokens reactively.
84
+ See Twitch's [token validation](https://dev.twitch.tv/docs/authentication/validate-tokens/)
85
+ and [refresh](https://dev.twitch.tv/docs/authentication/refresh-tokens/)
86
+ guidance.
87
+ - `twitch.tokens.refresh()` is available to request a refresh explicitly.
88
+ Listen to `twitch.tokens.onAuthError()` for background validation failures;
89
+ a `reauthentication_required` error means the user must authorize again.
90
+ The connector cannot complete that interactive step itself: the consuming
91
+ app should subscribe to `onAuthError()` and prompt the user to run the
92
+ existing `startLogin()` / `completeLogin()` flow. Restarting the app is not
93
+ required. Foreground API calls also reject with the auth error.
94
+ - Refresh tokens may rotate, so token stores must persist every `save()` call.
95
+ - Twitch documents silent refresh for Authorization Code Grant user tokens.
96
+ The built-in Device Code flow may therefore require interactive login again
97
+ when Twitch rejects its refresh token; the library reports this explicitly.
98
+ - A `clientSecret` may be provided for confidential applications only. Never
99
+ ship it in a desktop or other public client.
79
100
  - Helix requests automatically include the Client ID and access token.
80
101
  - Chat messages are received through EventSub and sent through Helix.
81
102
  - EventSub subscriptions use the active WebSocket session ID.
@@ -1 +1 @@
1
- {"version":3,"file":"twitch-api-client.d.ts","sourceRoot":"","sources":["../../src/api/twitch-api-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAKxD,qBAAa,eAAe;IAExB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAIvB,OAAO,CAAC,QAAQ,CAAC,YAAY;gBAJZ,MAAM,EAAE,IAAI,CAC3B,qBAAqB,EACrB,UAAU,GAAG,OAAO,CACrB,EACgB,YAAY,EAAE,YAAY;IAG7C,OAAO,KAAK,OAAO,GAElB;IAEK,OAAO,CAAC,CAAC,EACb,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,WAAgB,GACrB,OAAO,CAAC,CAAC,CAAC;IAqCb,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAIhC,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;IAOhD,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;CAKpC"}
1
+ {"version":3,"file":"twitch-api-client.d.ts","sourceRoot":"","sources":["../../src/api/twitch-api-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAKxD,qBAAa,eAAe;IAExB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAIvB,OAAO,CAAC,QAAQ,CAAC,YAAY;gBAJZ,MAAM,EAAE,IAAI,CAC3B,qBAAqB,EACrB,UAAU,GAAG,OAAO,CACrB,EACgB,YAAY,EAAE,YAAY;IAG7C,OAAO,KAAK,OAAO,GAElB;IAEK,OAAO,CAAC,CAAC,EACb,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,WAAgB,GACrB,OAAO,CAAC,CAAC,CAAC;IA+Cb,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAIhC,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;IAOhD,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;CAKpC"}
@@ -11,18 +11,26 @@ export class TwitchApiClient {
11
11
  return this.config.fetch ?? fetch;
12
12
  }
13
13
  async request(path, init = {}) {
14
- const accessToken = await this.tokenManager.getAccessToken();
14
+ let accessToken = await this.tokenManager.getAccessToken();
15
15
  const url = new URL(path.replace(/^\/+/, ''), `${HELIX_BASE_URL}/`);
16
- const headers = new Headers(init.headers);
17
- headers.set('Client-Id', this.config.clientId);
18
- headers.set('Authorization', `Bearer ${accessToken}`);
19
- if (init.body && !headers.has('Content-Type')) {
20
- headers.set('Content-Type', 'application/json');
16
+ const send = (token) => {
17
+ const headers = new Headers(init.headers);
18
+ headers.set('Client-Id', this.config.clientId);
19
+ headers.set('Authorization', `Bearer ${token}`);
20
+ if (init.body && !headers.has('Content-Type')) {
21
+ headers.set('Content-Type', 'application/json');
22
+ }
23
+ return this.fetcher(url, {
24
+ ...init,
25
+ headers,
26
+ });
27
+ };
28
+ let response = await send(accessToken);
29
+ if (response.status === 401 && isReplayableBody(init.body)) {
30
+ accessToken =
31
+ await this.tokenManager.refreshAfterUnauthorized(accessToken);
32
+ response = await send(accessToken);
21
33
  }
22
- const response = await this.fetcher(url, {
23
- ...init,
24
- headers,
25
- });
26
34
  if (!response.ok) {
27
35
  const responseText = await response.text().catch(() => '');
28
36
  throw new TwitchError(`Twitch Helix request failed (${response.status})${responseText ? `: ${responseText}` : ''}`, response.status);
@@ -47,4 +55,9 @@ export class TwitchApiClient {
47
55
  });
48
56
  }
49
57
  }
58
+ function isReplayableBody(body) {
59
+ return (body == null ||
60
+ typeof body !== 'object' ||
61
+ !('getReader' in body));
62
+ }
50
63
  //# sourceMappingURL=twitch-api-client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"twitch-api-client.js","sourceRoot":"","sources":["../../src/api/twitch-api-client.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAExD,MAAM,cAAc,GAAG,6BAA6B,CAAC;AAErD,MAAM,OAAO,eAAe;IAEP;IAIA;IALnB,YACmB,MAGhB,EACgB,YAA0B;QAJ1B,WAAM,GAAN,MAAM,CAGtB;QACgB,iBAAY,GAAZ,YAAY,CAAc;IAC1C,CAAC;IAEJ,IAAY,OAAO;QACjB,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,KAAK,CAAC;IACpC,CAAC;IAED,KAAK,CAAC,OAAO,CACX,IAAY,EACZ,OAAoB,EAAE;QAEtB,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,cAAc,EAAE,CAAC;QAC7D,MAAM,GAAG,GAAG,IAAI,GAAG,CACjB,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,EACxB,GAAG,cAAc,GAAG,CACrB,CAAC;QAEF,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC1C,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC/C,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,UAAU,WAAW,EAAE,CAAC,CAAC;QAEtD,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,CAAC;YAC9C,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC;QAClD,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE;YACvC,GAAG,IAAI;YACP,OAAO;SACR,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,YAAY,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YAC3D,MAAM,IAAI,WAAW,CACnB,gCAAgC,QAAQ,CAAC,MAAM,IAC7C,YAAY,CAAC,CAAC,CAAC,KAAK,YAAY,EAAE,CAAC,CAAC,CAAC,EACvC,EAAE,EACF,QAAQ,CAAC,MAAM,CAChB,CAAC;QACJ,CAAC;QAED,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAC5B,OAAO,SAAc,CAAC;QACxB,CAAC;QAED,OAAO,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAM,CAAC;IACtC,CAAC;IAED,GAAG,CAAI,IAAY;QACjB,OAAO,IAAI,CAAC,OAAO,CAAI,IAAI,CAAC,CAAC;IAC/B,CAAC;IAED,IAAI,CAAI,IAAY,EAAE,IAAa;QACjC,OAAO,IAAI,CAAC,OAAO,CAAI,IAAI,EAAE;YAC3B,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;SAC3B,CAAC,CAAC;IACL,CAAC;IAED,MAAM,CAAI,IAAY;QACpB,OAAO,IAAI,CAAC,OAAO,CAAI,IAAI,EAAE;YAC3B,MAAM,EAAE,QAAQ;SACjB,CAAC,CAAC;IACL,CAAC;CACF"}
1
+ {"version":3,"file":"twitch-api-client.js","sourceRoot":"","sources":["../../src/api/twitch-api-client.ts"],"names":[],"mappings":"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"}
@@ -1,15 +1,17 @@
1
1
  import type { TwitchTokenStore } from '../auth/token-store.js';
2
2
  export interface TwitchConnectorConfig {
3
3
  clientId: string;
4
+ /** Only provide this for a confidential Twitch application. Never embed it in a desktop client. */
5
+ clientSecret?: string;
4
6
  scopes: string[];
5
7
  tokenStore: TwitchTokenStore;
6
8
  fetch?: typeof fetch;
7
9
  }
8
10
  export interface TwitchValidatedToken {
9
11
  client_id: string;
10
- login: string;
12
+ login: string | null;
11
13
  scopes: string[];
12
- user_id: string;
14
+ user_id: string | null;
13
15
  expires_in: number;
14
16
  }
15
17
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/api/types.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/D,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/api/types.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/D,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,mGAAmG;IACnG,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;CACpB"}
@@ -6,14 +6,36 @@ export declare class TokenManager {
6
6
  private readonly tokenStore;
7
7
  private readonly deviceCodeFlow;
8
8
  private refreshPromise;
9
- constructor(config: Pick<TwitchConnectorConfig, 'clientId' | 'scopes' | 'fetch'>, tokenStore: TwitchTokenStore, deviceCodeFlow: DeviceCodeFlow);
9
+ private validationPromise;
10
+ private validationTimer;
11
+ private lastValidatedAt;
12
+ private lastAuthError;
13
+ private validationMonitoring;
14
+ private readonly authErrorHandlers;
15
+ constructor(config: Pick<TwitchConnectorConfig, 'clientId' | 'clientSecret' | 'scopes' | 'fetch'>, tokenStore: TwitchTokenStore, deviceCodeFlow: DeviceCodeFlow);
10
16
  private get fetcher();
17
+ /** Loads persisted token metadata without attempting an expiry-based refresh. */
11
18
  getTokens(): Promise<TwitchTokens>;
12
19
  getAccessToken(): Promise<string>;
13
20
  getValidatedToken(): Promise<TwitchValidatedToken>;
21
+ /** Force a refresh without requiring the consuming application to restart. */
22
+ refresh(): Promise<TwitchTokens>;
23
+ /**
24
+ * Refresh after Helix rejects a token. Concurrent requests reuse a refresh
25
+ * already performed by another request when the store has the newer token.
26
+ */
27
+ refreshAfterUnauthorized(rejectedAccessToken: string): Promise<string>;
14
28
  startLogin(): Promise<import("./device-code-flow.js").DeviceCodeLogin>;
15
29
  completeLogin(signal?: AbortSignal): Promise<TwitchTokens>;
16
30
  logout(): Promise<void>;
31
+ onAuthError(handler: (error: Error) => void): () => void;
32
+ get authError(): Error | null;
33
+ dispose(): void;
34
+ private validateIfDue;
35
+ private validateCurrentTokens;
36
+ private validate;
37
+ private scheduleValidation;
38
+ private stopValidationTimer;
17
39
  private refreshTokens;
18
40
  private performRefresh;
19
41
  }
@@ -1 +1 @@
1
- {"version":3,"file":"token-manager.d.ts","sourceRoot":"","sources":["../../src/auth/token-manager.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,qBAAqB,EACrB,oBAAoB,EACrB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAavE,qBAAa,YAAY;IAIrB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAIvB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,cAAc;IARjC,OAAO,CAAC,cAAc,CAAsC;gBAGzC,MAAM,EAAE,IAAI,CAC3B,qBAAqB,EACrB,UAAU,GAAG,QAAQ,GAAG,OAAO,CAChC,EACgB,UAAU,EAAE,gBAAgB,EAC5B,cAAc,EAAE,cAAc;IAGjD,OAAO,KAAK,OAAO,GAElB;IAEK,SAAS,IAAI,OAAO,CAAC,YAAY,CAAC;IAclC,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC;IAIjC,iBAAiB,IAAI,OAAO,CAAC,oBAAoB,CAAC;IAmBlD,UAAU;IAIV,aAAa,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IAc1D,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAI7B,OAAO,CAAC,aAAa;YAYP,cAAc;CA4C7B"}
1
+ {"version":3,"file":"token-manager.d.ts","sourceRoot":"","sources":["../../src/auth/token-manager.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,qBAAqB,EACrB,oBAAoB,EACrB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAgBvE,qBAAa,YAAY;IAUrB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAIvB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,cAAc;IAdjC,OAAO,CAAC,cAAc,CAAsC;IAC5D,OAAO,CAAC,iBAAiB,CAA8C;IACvE,OAAO,CAAC,eAAe,CAA8C;IACrE,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,aAAa,CAAsB;IAC3C,OAAO,CAAC,oBAAoB,CAAQ;IACpC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAqC;gBAGpD,MAAM,EAAE,IAAI,CAC3B,qBAAqB,EACrB,UAAU,GAAG,cAAc,GAAG,QAAQ,GAAG,OAAO,CACjD,EACgB,UAAU,EAAE,gBAAgB,EAC5B,cAAc,EAAE,cAAc;IAGjD,OAAO,KAAK,OAAO,GAElB;IAED,iFAAiF;IAC3E,SAAS,IAAI,OAAO,CAAC,YAAY,CAAC;IAUlC,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC;IAKjC,iBAAiB,IAAI,OAAO,CAAC,oBAAoB,CAAC;IAIxD,8EAA8E;IACxE,OAAO,IAAI,OAAO,CAAC,YAAY,CAAC;IAUtC;;;OAGG;IACG,wBAAwB,CAC5B,mBAAmB,EAAE,MAAM,GAC1B,OAAO,CAAC,MAAM,CAAC;IAeZ,UAAU;IAIV,aAAa,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IAgB1D,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ7B,WAAW,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,MAAM,IAAI;IAKxD,IAAI,SAAS,IAAI,KAAK,GAAG,IAAI,CAE5B;IAED,OAAO,IAAI,IAAI;YAKD,aAAa;YA2Bb,qBAAqB;IAsDnC,OAAO,CAAC,QAAQ;IAQhB,OAAO,CAAC,kBAAkB;IAqC1B,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,aAAa;YAYP,cAAc;CAqD7B"}
@@ -1,11 +1,19 @@
1
1
  import { TwitchError } from '../errors/twitch-error.js';
2
2
  const TWITCH_VALIDATE_URL = 'https://id.twitch.tv/oauth2/validate';
3
3
  const TWITCH_TOKEN_URL = 'https://id.twitch.tv/oauth2/token';
4
+ const TOKEN_VALIDATION_INTERVAL_MS = 50 * 60 * 1000;
5
+ const TOKEN_REFRESH_BUFFER_MS = 5 * 60 * 1000;
4
6
  export class TokenManager {
5
7
  config;
6
8
  tokenStore;
7
9
  deviceCodeFlow;
8
10
  refreshPromise = null;
11
+ validationPromise = null;
12
+ validationTimer = null;
13
+ lastValidatedAt = 0;
14
+ lastAuthError = null;
15
+ validationMonitoring = true;
16
+ authErrorHandlers = new Set();
9
17
  constructor(config, tokenStore, deviceCodeFlow) {
10
18
  this.config = config;
11
19
  this.tokenStore = tokenStore;
@@ -14,30 +22,42 @@ export class TokenManager {
14
22
  get fetcher() {
15
23
  return this.config.fetch ?? fetch;
16
24
  }
25
+ /** Loads persisted token metadata without attempting an expiry-based refresh. */
17
26
  async getTokens() {
18
27
  const tokens = await this.tokenStore.load();
19
28
  if (!tokens) {
20
29
  throw new TwitchError('Not authenticated with Twitch.');
21
30
  }
22
- if (tokens.expiresAt > Date.now() + 60_000) {
23
- return tokens;
24
- }
25
- return this.refreshTokens(tokens);
31
+ return tokens;
26
32
  }
27
33
  async getAccessToken() {
34
+ await this.validateIfDue();
28
35
  return (await this.getTokens()).accessToken;
29
36
  }
30
37
  async getValidatedToken() {
31
- const tokens = await this.getTokens();
32
- const response = await this.fetcher(TWITCH_VALIDATE_URL, {
33
- headers: {
34
- Authorization: `OAuth ${tokens.accessToken}`,
35
- },
36
- });
37
- if (!response.ok) {
38
- throw new TwitchError('Twitch token validation failed.', response.status);
38
+ return this.validateCurrentTokens();
39
+ }
40
+ /** Force a refresh without requiring the consuming application to restart. */
41
+ async refresh() {
42
+ const tokens = await this.tokenStore.load();
43
+ if (!tokens) {
44
+ throw new TwitchError('Not authenticated with Twitch.');
45
+ }
46
+ return this.refreshTokens(tokens);
47
+ }
48
+ /**
49
+ * Refresh after Helix rejects a token. Concurrent requests reuse a refresh
50
+ * already performed by another request when the store has the newer token.
51
+ */
52
+ async refreshAfterUnauthorized(rejectedAccessToken) {
53
+ const tokens = await this.tokenStore.load();
54
+ if (!tokens) {
55
+ throw new TwitchError('Not authenticated with Twitch.');
39
56
  }
40
- return (await response.json());
57
+ const refreshed = tokens.accessToken === rejectedAccessToken
58
+ ? await this.refreshTokens(tokens)
59
+ : tokens;
60
+ return refreshed.accessToken;
41
61
  }
42
62
  async startLogin() {
43
63
  return this.deviceCodeFlow.startLogin();
@@ -51,11 +71,115 @@ export class TokenManager {
51
71
  scopes: response.scope,
52
72
  };
53
73
  await this.tokenStore.save(tokens);
74
+ this.lastValidatedAt = 0;
75
+ this.validationMonitoring = true;
54
76
  return tokens;
55
77
  }
56
78
  async logout() {
79
+ this.validationMonitoring = false;
80
+ this.stopValidationTimer();
81
+ this.lastValidatedAt = 0;
82
+ this.lastAuthError = null;
57
83
  await this.tokenStore.clear();
58
84
  }
85
+ onAuthError(handler) {
86
+ this.authErrorHandlers.add(handler);
87
+ return () => this.authErrorHandlers.delete(handler);
88
+ }
89
+ get authError() {
90
+ return this.lastAuthError;
91
+ }
92
+ dispose() {
93
+ this.validationMonitoring = false;
94
+ this.stopValidationTimer();
95
+ }
96
+ async validateIfDue() {
97
+ const tokens = await this.getTokens();
98
+ const isNearExpiry = tokens.expiresAt <= Date.now() + TOKEN_REFRESH_BUFFER_MS;
99
+ if (!isNearExpiry &&
100
+ this.lastValidatedAt > 0 &&
101
+ Date.now() - this.lastValidatedAt < TOKEN_VALIDATION_INTERVAL_MS) {
102
+ return;
103
+ }
104
+ if (this.validationPromise) {
105
+ await this.validationPromise;
106
+ return;
107
+ }
108
+ this.validationPromise = this.validateCurrentTokens().finally(() => {
109
+ this.validationPromise = null;
110
+ });
111
+ await this.validationPromise;
112
+ }
113
+ async validateCurrentTokens() {
114
+ let tokens = await this.getTokens();
115
+ const refreshedBeforeValidation = tokens.expiresAt <= Date.now() + TOKEN_REFRESH_BUFFER_MS;
116
+ if (refreshedBeforeValidation) {
117
+ tokens = await this.refreshTokens(tokens);
118
+ }
119
+ let response = await this.validate(tokens.accessToken);
120
+ if (response.status === 401 && !refreshedBeforeValidation) {
121
+ const refreshed = await this.refreshTokens(tokens);
122
+ response = await this.validate(refreshed.accessToken);
123
+ }
124
+ if (!response.ok) {
125
+ throw new TwitchError('Twitch token validation failed.', response.status, response.status === 401 ? 'reauthentication_required' : undefined);
126
+ }
127
+ const validated = (await response.json());
128
+ if (validated.client_id !== this.config.clientId ||
129
+ !Number.isFinite(validated.expires_in) ||
130
+ validated.expires_in <= 0) {
131
+ throw new TwitchError('Twitch token validation returned unexpected token details.', response.status, 'invalid_validated_token');
132
+ }
133
+ const expiresAt = Date.now() + validated.expires_in * 1000;
134
+ const currentTokens = await this.tokenStore.load();
135
+ if (currentTokens?.accessToken === tokens.accessToken) {
136
+ await this.tokenStore.save({
137
+ ...currentTokens,
138
+ expiresAt,
139
+ });
140
+ }
141
+ this.lastValidatedAt = Date.now();
142
+ this.lastAuthError = null;
143
+ this.scheduleValidation(expiresAt);
144
+ return validated;
145
+ }
146
+ validate(accessToken) {
147
+ return this.fetcher(TWITCH_VALIDATE_URL, {
148
+ headers: {
149
+ Authorization: `OAuth ${accessToken}`,
150
+ },
151
+ });
152
+ }
153
+ scheduleValidation(expiresAt) {
154
+ if (!this.validationMonitoring) {
155
+ return;
156
+ }
157
+ this.stopValidationTimer();
158
+ const untilExpiry = expiresAt - Date.now() - TOKEN_REFRESH_BUFFER_MS;
159
+ const delay = Math.max(0, Math.min(TOKEN_VALIDATION_INTERVAL_MS, untilExpiry));
160
+ this.validationTimer = setTimeout(() => {
161
+ this.validationTimer = null;
162
+ void this.validateCurrentTokens()
163
+ .catch((error) => {
164
+ const authError = error instanceof Error ? error : new Error(String(error));
165
+ this.lastAuthError = authError;
166
+ for (const handler of this.authErrorHandlers) {
167
+ handler(authError);
168
+ }
169
+ this.scheduleValidation(Date.now() +
170
+ TOKEN_VALIDATION_INTERVAL_MS +
171
+ TOKEN_REFRESH_BUFFER_MS);
172
+ });
173
+ }, delay);
174
+ const timer = this.validationTimer;
175
+ timer.unref?.();
176
+ }
177
+ stopValidationTimer() {
178
+ if (this.validationTimer) {
179
+ clearTimeout(this.validationTimer);
180
+ this.validationTimer = null;
181
+ }
182
+ }
59
183
  refreshTokens(current) {
60
184
  if (this.refreshPromise) {
61
185
  return this.refreshPromise;
@@ -71,6 +195,9 @@ export class TokenManager {
71
195
  refresh_token: current.refreshToken,
72
196
  client_id: this.config.clientId,
73
197
  });
198
+ if (this.config.clientSecret) {
199
+ body.set('client_secret', this.config.clientSecret);
200
+ }
74
201
  const response = await this.fetcher(TWITCH_TOKEN_URL, {
75
202
  method: 'POST',
76
203
  headers: {
@@ -79,21 +206,25 @@ export class TokenManager {
79
206
  body,
80
207
  });
81
208
  if (!response.ok) {
82
- throw new TwitchError('Twitch token refresh failed. You may need to log in again.', response.status);
209
+ throw new TwitchError('Twitch token refresh failed. You may need to log in again.', response.status, response.status === 400 || response.status === 401
210
+ ? 'reauthentication_required'
211
+ : 'token_refresh_failed');
83
212
  }
84
213
  const result = (await response.json());
85
214
  if (!result.access_token ||
86
215
  !result.refresh_token ||
87
- !Number.isFinite(result.expires_in)) {
216
+ !Number.isFinite(result.expires_in) ||
217
+ result.expires_in <= 0) {
88
218
  throw new TwitchError('Twitch returned an invalid refresh response.');
89
219
  }
90
220
  const tokens = {
91
221
  accessToken: result.access_token,
92
222
  refreshToken: result.refresh_token,
93
223
  expiresAt: Date.now() + result.expires_in * 1000,
94
- scopes: result.scope ?? current.scopes,
224
+ scopes: Array.isArray(result.scope) ? result.scope : current.scopes,
95
225
  };
96
226
  await this.tokenStore.save(tokens);
227
+ this.lastValidatedAt = 0;
97
228
  return tokens;
98
229
  }
99
230
  }
@@ -1 +1 @@
1
- {"version":3,"file":"token-manager.js","sourceRoot":"","sources":["../../src/auth/token-manager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"token-manager.js","sourceRoot":"","sources":["../../src/auth/token-manager.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAIxD,MAAM,mBAAmB,GAAG,sCAAsC,CAAC;AACnE,MAAM,gBAAgB,GAAG,mCAAmC,CAAC;AAU7D,MAAM,4BAA4B,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AACpD,MAAM,uBAAuB,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;AAE9C,MAAM,OAAO,YAAY;IAUJ;IAIA;IACA;IAdX,cAAc,GAAiC,IAAI,CAAC;IACpD,iBAAiB,GAAyC,IAAI,CAAC;IAC/D,eAAe,GAAyC,IAAI,CAAC;IAC7D,eAAe,GAAG,CAAC,CAAC;IACpB,aAAa,GAAiB,IAAI,CAAC;IACnC,oBAAoB,GAAG,IAAI,CAAC;IACnB,iBAAiB,GAAG,IAAI,GAAG,EAA0B,CAAC;IAEvE,YACmB,MAGhB,EACgB,UAA4B,EAC5B,cAA8B;QAL9B,WAAM,GAAN,MAAM,CAGtB;QACgB,eAAU,GAAV,UAAU,CAAkB;QAC5B,mBAAc,GAAd,cAAc,CAAgB;IAC9C,CAAC;IAEJ,IAAY,OAAO;QACjB,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,KAAK,CAAC;IACpC,CAAC;IAED,iFAAiF;IACjF,KAAK,CAAC,SAAS;QACb,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;QAE5C,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,WAAW,CAAC,gCAAgC,CAAC,CAAC;QAC1D,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,KAAK,CAAC,cAAc;QAClB,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;QAC3B,OAAO,CAAC,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,WAAW,CAAC;IAC9C,CAAC;IAED,KAAK,CAAC,iBAAiB;QACrB,OAAO,IAAI,CAAC,qBAAqB,EAAE,CAAC;IACtC,CAAC;IAED,8EAA8E;IAC9E,KAAK,CAAC,OAAO;QACX,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;QAE5C,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,WAAW,CAAC,gCAAgC,CAAC,CAAC;QAC1D,CAAC;QAED,OAAO,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;IACpC,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,wBAAwB,CAC5B,mBAA2B;QAE3B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;QAE5C,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,WAAW,CAAC,gCAAgC,CAAC,CAAC;QAC1D,CAAC;QAED,MAAM,SAAS,GACb,MAAM,CAAC,WAAW,KAAK,mBAAmB;YACxC,CAAC,CAAC,MAAM,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC;YAClC,CAAC,CAAC,MAAM,CAAC;QAEb,OAAO,SAAS,CAAC,WAAW,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,UAAU;QACd,OAAO,IAAI,CAAC,cAAc,CAAC,UAAU,EAAE,CAAC;IAC1C,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,MAAoB;QACtC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC;QAExE,MAAM,MAAM,GAAiB;YAC3B,WAAW,EAAE,QAAQ,CAAC,YAAY;YAClC,YAAY,EAAE,QAAQ,CAAC,aAAa;YACpC,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC,UAAU,GAAG,IAAI;YAClD,MAAM,EAAE,QAAQ,CAAC,KAAK;SACvB,CAAC;QAEF,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnC,IAAI,CAAC,eAAe,GAAG,CAAC,CAAC;QACzB,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAC;QACjC,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,KAAK,CAAC,MAAM;QACV,IAAI,CAAC,oBAAoB,GAAG,KAAK,CAAC;QAClC,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC3B,IAAI,CAAC,eAAe,GAAG,CAAC,CAAC;QACzB,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;QAC1B,MAAM,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;IAChC,CAAC;IAED,WAAW,CAAC,OAA+B;QACzC,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACpC,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACtD,CAAC;IAED,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,aAAa,CAAC;IAC5B,CAAC;IAED,OAAO;QACL,IAAI,CAAC,oBAAoB,GAAG,KAAK,CAAC;QAClC,IAAI,CAAC,mBAAmB,EAAE,CAAC;IAC7B,CAAC;IAEO,KAAK,CAAC,aAAa;QACzB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;QACtC,MAAM,YAAY,GAChB,MAAM,CAAC,SAAS,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,uBAAuB,CAAC;QAE3D,IACE,CAAC,YAAY;YACb,IAAI,CAAC,eAAe,GAAG,CAAC;YACxB,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,eAAe,GAAG,4BAA4B,EAChE,CAAC;YACD,OAAO;QACT,CAAC;QAED,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAC3B,MAAM,IAAI,CAAC,iBAAiB,CAAC;YAC7B,OAAO;QACT,CAAC;QAED,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC,qBAAqB,EAAE,CAAC,OAAO,CAC3D,GAAG,EAAE;YACH,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;QAChC,CAAC,CACF,CAAC;QAEF,MAAM,IAAI,CAAC,iBAAiB,CAAC;IAC/B,CAAC;IAEO,KAAK,CAAC,qBAAqB;QACjC,IAAI,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;QACpC,MAAM,yBAAyB,GAC7B,MAAM,CAAC,SAAS,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,uBAAuB,CAAC;QAE3D,IAAI,yBAAyB,EAAE,CAAC;YAC9B,MAAM,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QAC5C,CAAC;QAED,IAAI,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QAEvD,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,IAAI,CAAC,yBAAyB,EAAE,CAAC;YAC1D,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;YACnD,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QACxD,CAAC;QAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,WAAW,CACnB,iCAAiC,EACjC,QAAQ,CAAC,MAAM,EACf,QAAQ,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,2BAA2B,CAAC,CAAC,CAAC,SAAS,CAClE,CAAC;QACJ,CAAC;QAED,MAAM,SAAS,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAyB,CAAC;QAElE,IACE,SAAS,CAAC,SAAS,KAAK,IAAI,CAAC,MAAM,CAAC,QAAQ;YAC5C,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,UAAU,CAAC;YACtC,SAAS,CAAC,UAAU,IAAI,CAAC,EACzB,CAAC;YACD,MAAM,IAAI,WAAW,CACnB,4DAA4D,EAC5D,QAAQ,CAAC,MAAM,EACf,yBAAyB,CAC1B,CAAC;QACJ,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,UAAU,GAAG,IAAI,CAAC;QAC3D,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;QAEnD,IAAI,aAAa,EAAE,WAAW,KAAK,MAAM,CAAC,WAAW,EAAE,CAAC;YACtD,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;gBACzB,GAAG,aAAa;gBAChB,SAAS;aACV,CAAC,CAAC;QACL,CAAC;QAED,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAClC,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;QAC1B,IAAI,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC;QACnC,OAAO,SAAS,CAAC;IACnB,CAAC;IAEO,QAAQ,CAAC,WAAmB;QAClC,OAAO,IAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE;YACvC,OAAO,EAAE;gBACP,aAAa,EAAE,SAAS,WAAW,EAAE;aACtC;SACF,CAAC,CAAC;IACL,CAAC;IAEO,kBAAkB,CAAC,SAAiB;QAC1C,IAAI,CAAC,IAAI,CAAC,oBAAoB,EAAE,CAAC;YAC/B,OAAO;QACT,CAAC;QAED,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC3B,MAAM,WAAW,GAAG,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,uBAAuB,CAAC;QACrE,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CACpB,CAAC,EACD,IAAI,CAAC,GAAG,CAAC,4BAA4B,EAAE,WAAW,CAAC,CACpD,CAAC;QAEF,IAAI,CAAC,eAAe,GAAG,UAAU,CAAC,GAAG,EAAE;YACrC,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;YAC5B,KAAK,IAAI,CAAC,qBAAqB,EAAE;iBAC9B,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;gBACxB,MAAM,SAAS,GACb,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;gBAC5D,IAAI,CAAC,aAAa,GAAG,SAAS,CAAC;gBAE/B,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;oBAC7C,OAAO,CAAC,SAAS,CAAC,CAAC;gBACrB,CAAC;gBACD,IAAI,CAAC,kBAAkB,CACrB,IAAI,CAAC,GAAG,EAAE;oBACR,4BAA4B;oBAC5B,uBAAuB,CAC1B,CAAC;YACJ,CAAC,CAAC,CAAC;QACP,CAAC,EAAE,KAAK,CAAC,CAAC;QAEV,MAAM,KAAK,GAAG,IAAI,CAAC,eAElB,CAAC;QACF,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;IAClB,CAAC;IAEO,mBAAmB;QACzB,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACzB,YAAY,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;YACnC,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;QAC9B,CAAC;IACH,CAAC;IAEO,aAAa,CAAC,OAAqB;QACzC,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,OAAO,IAAI,CAAC,cAAc,CAAC;QAC7B,CAAC;QAED,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE;YAC9D,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAC7B,CAAC,CAAC,CAAC;QAEH,OAAO,IAAI,CAAC,cAAc,CAAC;IAC7B,CAAC;IAEO,KAAK,CAAC,cAAc,CAC1B,OAAqB;QAErB,MAAM,IAAI,GAAG,IAAI,eAAe,CAAC;YAC/B,UAAU,EAAE,eAAe;YAC3B,aAAa,EAAE,OAAO,CAAC,YAAY;YACnC,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ;SAChC,CAAC,CAAC;QAEH,IAAI,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;YAC7B,IAAI,CAAC,GAAG,CAAC,eAAe,EAAE,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;QACtD,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE;YACpD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,mCAAmC;aACpD;YACD,IAAI;SACL,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,WAAW,CACnB,4DAA4D,EAC5D,QAAQ,CAAC,MAAM,EACf,QAAQ,CAAC,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG;gBAChD,CAAC,CAAC,2BAA2B;gBAC7B,CAAC,CAAC,sBAAsB,CAC3B,CAAC;QACJ,CAAC;QAED,MAAM,MAAM,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAkB,CAAC;QAExD,IACE,CAAC,MAAM,CAAC,YAAY;YACpB,CAAC,MAAM,CAAC,aAAa;YACrB,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC;YACnC,MAAM,CAAC,UAAU,IAAI,CAAC,EACtB,CAAC;YACD,MAAM,IAAI,WAAW,CAAC,8CAA8C,CAAC,CAAC;QACxE,CAAC;QAED,MAAM,MAAM,GAAiB;YAC3B,WAAW,EAAE,MAAM,CAAC,YAAY;YAChC,YAAY,EAAE,MAAM,CAAC,aAAa;YAClC,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,UAAU,GAAG,IAAI;YAChD,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM;SACpE,CAAC;QAEF,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnC,IAAI,CAAC,eAAe,GAAG,CAAC,CAAC;QACzB,OAAO,MAAM,CAAC;IAChB,CAAC;CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"twitch-client.d.ts","sourceRoot":"","sources":["../../src/twitch/twitch-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAE9D,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAExD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAChE,OAAO,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAC;AAE7E,qBAAa,YAAY;IACvB,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,eAAe,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC;gBAElC,MAAM,EAAE,qBAAqB;IA+BnC,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAIhC,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;CAG1C"}
1
+ {"version":3,"file":"twitch-client.d.ts","sourceRoot":"","sources":["../../src/twitch/twitch-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAE9D,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAExD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAChE,OAAO,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAC;AAE7E,qBAAa,YAAY;IACvB,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,eAAe,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC;gBAElC,MAAM,EAAE,qBAAqB;IA+BnC,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAKhC,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;CAG1C"}
@@ -32,6 +32,7 @@ export class TwitchClient {
32
32
  });
33
33
  }
34
34
  async connectEventSub() {
35
+ await this.tokens.getAccessToken();
35
36
  await this.eventSub.connect();
36
37
  }
37
38
  async disconnectEventSub() {
@@ -1 +1 @@
1
- {"version":3,"file":"twitch-client.js","sourceRoot":"","sources":["../../src/twitch/twitch-client.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAExD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAChE,OAAO,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAC;AAE7E,MAAM,OAAO,YAAY;IACd,MAAM,CAAe;IACrB,GAAG,CAAkB;IACrB,IAAI,CAAmB;IACvB,QAAQ,CAAiB;IACzB,aAAa,CAAwB;IAE9C,YAAY,MAA6B;QACvC,MAAM,UAAU,GAAqB,MAAM,CAAC,UAAU,CAAC;QAEvD,MAAM,cAAc,GAAG,IAAI,cAAc,CAAC;YACxC,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,KAAK,EAAE,MAAM,CAAC,KAAK;SACpB,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,GAAG,IAAI,YAAY,CAC5B,MAAM,EACN,UAAU,EACV,cAAc,CACf,CAAC;QAEF,IAAI,CAAC,GAAG,GAAG,IAAI,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QACpD,IAAI,CAAC,IAAI,GAAG,IAAI,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC3C,IAAI,CAAC,QAAQ,GAAG,IAAI,cAAc,EAAE,CAAC;QACrC,IAAI,CAAC,aAAa,GAAG,IAAI,qBAAqB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAEzD,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE;YAC3D,IAAI,IAAI,KAAK,sBAAsB,EAAE,CAAC;gBACpC,MAAM,IAAI,CAAC,IAAI,CAAC,qBAAqB,CAAC,KAAK,CAAC,CAAC;YAC/C,CAAC;YAED,4DAA4D;YAC5D,mCAAmC;YACnC,KAAK,QAAQ,CAAC;QAChB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;IAChC,CAAC;IAED,KAAK,CAAC,kBAAkB;QACtB,MAAM,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC;IACnC,CAAC;CACF"}
1
+ {"version":3,"file":"twitch-client.js","sourceRoot":"","sources":["../../src/twitch/twitch-client.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAExD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAChE,OAAO,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAC;AAE7E,MAAM,OAAO,YAAY;IACd,MAAM,CAAe;IACrB,GAAG,CAAkB;IACrB,IAAI,CAAmB;IACvB,QAAQ,CAAiB;IACzB,aAAa,CAAwB;IAE9C,YAAY,MAA6B;QACvC,MAAM,UAAU,GAAqB,MAAM,CAAC,UAAU,CAAC;QAEvD,MAAM,cAAc,GAAG,IAAI,cAAc,CAAC;YACxC,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,KAAK,EAAE,MAAM,CAAC,KAAK;SACpB,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,GAAG,IAAI,YAAY,CAC5B,MAAM,EACN,UAAU,EACV,cAAc,CACf,CAAC;QAEF,IAAI,CAAC,GAAG,GAAG,IAAI,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QACpD,IAAI,CAAC,IAAI,GAAG,IAAI,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC3C,IAAI,CAAC,QAAQ,GAAG,IAAI,cAAc,EAAE,CAAC;QACrC,IAAI,CAAC,aAAa,GAAG,IAAI,qBAAqB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAEzD,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE;YAC3D,IAAI,IAAI,KAAK,sBAAsB,EAAE,CAAC;gBACpC,MAAM,IAAI,CAAC,IAAI,CAAC,qBAAqB,CAAC,KAAK,CAAC,CAAC;YAC/C,CAAC;YAED,4DAA4D;YAC5D,mCAAmC;YACnC,KAAK,QAAQ,CAAC;QAChB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,MAAM,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;QACnC,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;IAChC,CAAC;IAED,KAAK,CAAC,kBAAkB;QACtB,MAAM,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC;IACnC,CAAC;CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "streamops-twitch-connector",
3
- "version": "0.1.5",
3
+ "version": "0.2.1",
4
4
  "description": "Framework-independent Twitch OAuth, EventSub, and chat connector",
5
5
  "license": "MIT",
6
6
  "type": "module",