streamops-twitch-connector 0.1.4 → 0.2.0
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 +17 -0
- package/dist/api/twitch-api-client.d.ts.map +1 -1
- package/dist/api/twitch-api-client.js +23 -10
- package/dist/api/twitch-api-client.js.map +1 -1
- package/dist/api/types.d.ts +4 -2
- package/dist/api/types.d.ts.map +1 -1
- package/dist/auth/token-manager.d.ts +23 -1
- package/dist/auth/token-manager.d.ts.map +1 -1
- package/dist/auth/token-manager.js +133 -16
- package/dist/auth/token-manager.js.map +1 -1
- package/dist/twitch/twitch-client.d.ts.map +1 -1
- package/dist/twitch/twitch-client.js +1 -0
- package/dist/twitch/twitch-client.js.map +1 -1
- package/package.json +1 -4
package/README.md
CHANGED
|
@@ -76,6 +76,23 @@ 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 hourly, as required by
|
|
80
|
+
Twitch. Helix `401` responses trigger a single refresh-and-retry attempt,
|
|
81
|
+
following Twitch's recommendation to handle invalid tokens reactively.
|
|
82
|
+
See Twitch's [token validation](https://dev.twitch.tv/docs/authentication/validate-tokens/)
|
|
83
|
+
and [refresh](https://dev.twitch.tv/docs/authentication/refresh-tokens/)
|
|
84
|
+
guidance.
|
|
85
|
+
- `twitch.tokens.refresh()` is available to request a refresh explicitly.
|
|
86
|
+
Listen to `twitch.tokens.onAuthError()` for background validation failures;
|
|
87
|
+
a `reauthentication_required` error means the user must authorize again.
|
|
88
|
+
The existing `startLogin()` / `completeLogin()` flow can be run in-process;
|
|
89
|
+
restarting the app is not required.
|
|
90
|
+
- Refresh tokens may rotate, so token stores must persist every `save()` call.
|
|
91
|
+
- Twitch documents silent refresh for Authorization Code Grant user tokens.
|
|
92
|
+
The built-in Device Code flow may therefore require interactive login again
|
|
93
|
+
when Twitch rejects its refresh token; the library reports this explicitly.
|
|
94
|
+
- A `clientSecret` may be provided for confidential applications only. Never
|
|
95
|
+
ship it in a desktop or other public client.
|
|
79
96
|
- Helix requests automatically include the Client ID and access token.
|
|
80
97
|
- Chat messages are received through EventSub and sent through Helix.
|
|
81
98
|
- 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":"
|
|
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
|
-
|
|
14
|
+
let accessToken = await this.tokenManager.getAccessToken();
|
|
15
15
|
const url = new URL(path.replace(/^\/+/, ''), `${HELIX_BASE_URL}/`);
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
headers.
|
|
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":"
|
|
1
|
+
{"version":3,"file":"twitch-api-client.js","sourceRoot":"","sources":["../../src/api/twitch-api-client.ts"],"names":[],"mappings":"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"}
|
package/dist/api/types.d.ts
CHANGED
|
@@ -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
|
package/dist/api/types.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
-
|
|
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;
|
|
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;AAevE,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;IAKf,OAAO,CAAC,aAAa;YAqBP,qBAAqB;IA8CnC,OAAO,CAAC,QAAQ;IAQhB,OAAO,CAAC,kBAAkB;IA2B1B,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,aAAa;YAYP,cAAc;CAqD7B"}
|
|
@@ -1,11 +1,18 @@
|
|
|
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 = 60 * 60 * 1000;
|
|
4
5
|
export class TokenManager {
|
|
5
6
|
config;
|
|
6
7
|
tokenStore;
|
|
7
8
|
deviceCodeFlow;
|
|
8
9
|
refreshPromise = null;
|
|
10
|
+
validationPromise = null;
|
|
11
|
+
validationTimer = null;
|
|
12
|
+
lastValidatedAt = 0;
|
|
13
|
+
lastAuthError = null;
|
|
14
|
+
validationMonitoring = true;
|
|
15
|
+
authErrorHandlers = new Set();
|
|
9
16
|
constructor(config, tokenStore, deviceCodeFlow) {
|
|
10
17
|
this.config = config;
|
|
11
18
|
this.tokenStore = tokenStore;
|
|
@@ -14,30 +21,42 @@ export class TokenManager {
|
|
|
14
21
|
get fetcher() {
|
|
15
22
|
return this.config.fetch ?? fetch;
|
|
16
23
|
}
|
|
24
|
+
/** Loads persisted token metadata without attempting an expiry-based refresh. */
|
|
17
25
|
async getTokens() {
|
|
18
26
|
const tokens = await this.tokenStore.load();
|
|
19
27
|
if (!tokens) {
|
|
20
28
|
throw new TwitchError('Not authenticated with Twitch.');
|
|
21
29
|
}
|
|
22
|
-
|
|
23
|
-
return tokens;
|
|
24
|
-
}
|
|
25
|
-
return this.refreshTokens(tokens);
|
|
30
|
+
return tokens;
|
|
26
31
|
}
|
|
27
32
|
async getAccessToken() {
|
|
33
|
+
await this.validateIfDue();
|
|
28
34
|
return (await this.getTokens()).accessToken;
|
|
29
35
|
}
|
|
30
36
|
async getValidatedToken() {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
return this.validateCurrentTokens();
|
|
38
|
+
}
|
|
39
|
+
/** Force a refresh without requiring the consuming application to restart. */
|
|
40
|
+
async refresh() {
|
|
41
|
+
const tokens = await this.tokenStore.load();
|
|
42
|
+
if (!tokens) {
|
|
43
|
+
throw new TwitchError('Not authenticated with Twitch.');
|
|
44
|
+
}
|
|
45
|
+
return this.refreshTokens(tokens);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Refresh after Helix rejects a token. Concurrent requests reuse a refresh
|
|
49
|
+
* already performed by another request when the store has the newer token.
|
|
50
|
+
*/
|
|
51
|
+
async refreshAfterUnauthorized(rejectedAccessToken) {
|
|
52
|
+
const tokens = await this.tokenStore.load();
|
|
53
|
+
if (!tokens) {
|
|
54
|
+
throw new TwitchError('Not authenticated with Twitch.');
|
|
39
55
|
}
|
|
40
|
-
|
|
56
|
+
const refreshed = tokens.accessToken === rejectedAccessToken
|
|
57
|
+
? await this.refreshTokens(tokens)
|
|
58
|
+
: tokens;
|
|
59
|
+
return refreshed.accessToken;
|
|
41
60
|
}
|
|
42
61
|
async startLogin() {
|
|
43
62
|
return this.deviceCodeFlow.startLogin();
|
|
@@ -51,11 +70,102 @@ export class TokenManager {
|
|
|
51
70
|
scopes: response.scope,
|
|
52
71
|
};
|
|
53
72
|
await this.tokenStore.save(tokens);
|
|
73
|
+
this.lastValidatedAt = 0;
|
|
74
|
+
this.validationMonitoring = true;
|
|
54
75
|
return tokens;
|
|
55
76
|
}
|
|
56
77
|
async logout() {
|
|
78
|
+
this.validationMonitoring = false;
|
|
79
|
+
this.stopValidationTimer();
|
|
80
|
+
this.lastValidatedAt = 0;
|
|
81
|
+
this.lastAuthError = null;
|
|
57
82
|
await this.tokenStore.clear();
|
|
58
83
|
}
|
|
84
|
+
onAuthError(handler) {
|
|
85
|
+
this.authErrorHandlers.add(handler);
|
|
86
|
+
return () => this.authErrorHandlers.delete(handler);
|
|
87
|
+
}
|
|
88
|
+
get authError() {
|
|
89
|
+
return this.lastAuthError;
|
|
90
|
+
}
|
|
91
|
+
dispose() {
|
|
92
|
+
this.validationMonitoring = false;
|
|
93
|
+
this.stopValidationTimer();
|
|
94
|
+
}
|
|
95
|
+
validateIfDue() {
|
|
96
|
+
if (this.lastValidatedAt > 0 &&
|
|
97
|
+
Date.now() - this.lastValidatedAt < TOKEN_VALIDATION_INTERVAL_MS) {
|
|
98
|
+
return Promise.resolve();
|
|
99
|
+
}
|
|
100
|
+
if (this.validationPromise) {
|
|
101
|
+
return this.validationPromise.then(() => undefined);
|
|
102
|
+
}
|
|
103
|
+
this.validationPromise = this.validateCurrentTokens().finally(() => {
|
|
104
|
+
this.validationPromise = null;
|
|
105
|
+
});
|
|
106
|
+
return this.validationPromise.then(() => undefined);
|
|
107
|
+
}
|
|
108
|
+
async validateCurrentTokens() {
|
|
109
|
+
const tokens = await this.getTokens();
|
|
110
|
+
let response = await this.validate(tokens.accessToken);
|
|
111
|
+
if (response.status === 401) {
|
|
112
|
+
const refreshed = await this.refreshTokens(tokens);
|
|
113
|
+
response = await this.validate(refreshed.accessToken);
|
|
114
|
+
}
|
|
115
|
+
if (!response.ok) {
|
|
116
|
+
throw new TwitchError('Twitch token validation failed.', response.status, response.status === 401 ? 'reauthentication_required' : undefined);
|
|
117
|
+
}
|
|
118
|
+
const validated = (await response.json());
|
|
119
|
+
if (validated.client_id !== this.config.clientId ||
|
|
120
|
+
!Number.isFinite(validated.expires_in) ||
|
|
121
|
+
validated.expires_in <= 0) {
|
|
122
|
+
throw new TwitchError('Twitch token validation returned unexpected token details.', response.status, 'invalid_validated_token');
|
|
123
|
+
}
|
|
124
|
+
const currentTokens = await this.tokenStore.load();
|
|
125
|
+
if (currentTokens?.accessToken === tokens.accessToken) {
|
|
126
|
+
await this.tokenStore.save({
|
|
127
|
+
...currentTokens,
|
|
128
|
+
expiresAt: Date.now() + validated.expires_in * 1000,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
this.lastValidatedAt = Date.now();
|
|
132
|
+
this.lastAuthError = null;
|
|
133
|
+
this.scheduleValidation();
|
|
134
|
+
return validated;
|
|
135
|
+
}
|
|
136
|
+
validate(accessToken) {
|
|
137
|
+
return this.fetcher(TWITCH_VALIDATE_URL, {
|
|
138
|
+
headers: {
|
|
139
|
+
Authorization: `OAuth ${accessToken}`,
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
scheduleValidation() {
|
|
144
|
+
if (!this.validationMonitoring) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
this.stopValidationTimer();
|
|
148
|
+
this.validationTimer = setTimeout(() => {
|
|
149
|
+
this.validationTimer = null;
|
|
150
|
+
void this.validateCurrentTokens()
|
|
151
|
+
.catch((error) => {
|
|
152
|
+
const authError = error instanceof Error ? error : new Error(String(error));
|
|
153
|
+
this.lastAuthError = authError;
|
|
154
|
+
for (const handler of this.authErrorHandlers) {
|
|
155
|
+
handler(authError);
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
.finally(() => this.scheduleValidation());
|
|
159
|
+
}, TOKEN_VALIDATION_INTERVAL_MS);
|
|
160
|
+
const timer = this.validationTimer;
|
|
161
|
+
timer.unref?.();
|
|
162
|
+
}
|
|
163
|
+
stopValidationTimer() {
|
|
164
|
+
if (this.validationTimer) {
|
|
165
|
+
clearTimeout(this.validationTimer);
|
|
166
|
+
this.validationTimer = null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
59
169
|
refreshTokens(current) {
|
|
60
170
|
if (this.refreshPromise) {
|
|
61
171
|
return this.refreshPromise;
|
|
@@ -71,6 +181,9 @@ export class TokenManager {
|
|
|
71
181
|
refresh_token: current.refreshToken,
|
|
72
182
|
client_id: this.config.clientId,
|
|
73
183
|
});
|
|
184
|
+
if (this.config.clientSecret) {
|
|
185
|
+
body.set('client_secret', this.config.clientSecret);
|
|
186
|
+
}
|
|
74
187
|
const response = await this.fetcher(TWITCH_TOKEN_URL, {
|
|
75
188
|
method: 'POST',
|
|
76
189
|
headers: {
|
|
@@ -79,21 +192,25 @@ export class TokenManager {
|
|
|
79
192
|
body,
|
|
80
193
|
});
|
|
81
194
|
if (!response.ok) {
|
|
82
|
-
throw new TwitchError('Twitch token refresh failed. You may need to log in again.', response.status
|
|
195
|
+
throw new TwitchError('Twitch token refresh failed. You may need to log in again.', response.status, response.status === 400 || response.status === 401
|
|
196
|
+
? 'reauthentication_required'
|
|
197
|
+
: 'token_refresh_failed');
|
|
83
198
|
}
|
|
84
199
|
const result = (await response.json());
|
|
85
200
|
if (!result.access_token ||
|
|
86
201
|
!result.refresh_token ||
|
|
87
|
-
!Number.isFinite(result.expires_in)
|
|
202
|
+
!Number.isFinite(result.expires_in) ||
|
|
203
|
+
result.expires_in <= 0) {
|
|
88
204
|
throw new TwitchError('Twitch returned an invalid refresh response.');
|
|
89
205
|
}
|
|
90
206
|
const tokens = {
|
|
91
207
|
accessToken: result.access_token,
|
|
92
208
|
refreshToken: result.refresh_token,
|
|
93
209
|
expiresAt: Date.now() + result.expires_in * 1000,
|
|
94
|
-
scopes: result.scope
|
|
210
|
+
scopes: Array.isArray(result.scope) ? result.scope : current.scopes,
|
|
95
211
|
};
|
|
96
212
|
await this.tokenStore.save(tokens);
|
|
213
|
+
this.lastValidatedAt = 0;
|
|
97
214
|
return tokens;
|
|
98
215
|
}
|
|
99
216
|
}
|
|
@@ -1 +1 @@
|
|
|
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,OAAO,YAAY;
|
|
1
|
+
{"version":3,"file":"token-manager.js","sourceRoot":"","sources":["../../src/auth/token-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -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;
|
|
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"}
|
|
@@ -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.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Framework-independent Twitch OAuth, EventSub, and chat connector",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -27,8 +27,5 @@
|
|
|
27
27
|
"@types/node": "^24.0.0",
|
|
28
28
|
"typescript": "^6.0.2",
|
|
29
29
|
"vitest": "^4.1.2"
|
|
30
|
-
},
|
|
31
|
-
"dependencies": {
|
|
32
|
-
"streamops-twitch-connector": "file:streamops-twitch-connector-0.1.2.tgz"
|
|
33
30
|
}
|
|
34
31
|
}
|