streamops-twitch-connector 0.2.1 → 0.4.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 +143 -78
- package/dist/api/types.d.ts +1 -1
- package/dist/api/types.d.ts.map +1 -1
- package/dist/auth/authorization-code-flow.d.ts +29 -0
- package/dist/auth/authorization-code-flow.d.ts.map +1 -0
- package/dist/auth/authorization-code-flow.js +97 -0
- package/dist/auth/authorization-code-flow.js.map +1 -0
- package/dist/auth/token-manager.d.ts +11 -1
- package/dist/auth/token-manager.d.ts.map +1 -1
- package/dist/auth/token-manager.js +53 -9
- package/dist/auth/token-manager.js.map +1 -1
- package/dist/eventsub/eventsub-client.d.ts +6 -0
- package/dist/eventsub/eventsub-client.d.ts.map +1 -1
- package/dist/eventsub/eventsub-client.js +69 -9
- package/dist/eventsub/eventsub-client.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/twitch/twitch-client.d.ts.map +1 -1
- package/dist/twitch/twitch-client.js +8 -1
- package/dist/twitch/twitch-client.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,110 +1,175 @@
|
|
|
1
|
-
# streamops-twitch-connector
|
|
2
|
-
|
|
3
1
|
# StreamOps Twitch Connector
|
|
4
2
|
|
|
5
|
-
A framework-independent TypeScript
|
|
6
|
-
|
|
3
|
+
A small, framework-independent TypeScript library for Twitch OAuth, Helix,
|
|
4
|
+
chat, and EventSub. The product owns its Twitch application, callback route,
|
|
5
|
+
and secure token storage; the connector handles Twitch requests and token
|
|
6
|
+
refreshing. It supports both Device Code Grant (DCF) and Authorization Code
|
|
7
|
+
Grant (ACF); either flow uses the same API, chat, EventSub, and token store.
|
|
7
8
|
|
|
8
9
|
## Requirements
|
|
9
10
|
|
|
10
11
|
- Node.js 22 or newer
|
|
11
|
-
- A Twitch application
|
|
12
|
-
- A
|
|
12
|
+
- A Twitch application registered for each product
|
|
13
|
+
- A backend that can keep the client secret private if using Authorization
|
|
14
|
+
Code Grant
|
|
15
|
+
- A token store supplied by the product
|
|
13
16
|
|
|
14
|
-
|
|
17
|
+
Never put a Twitch client secret or refresh token in a browser or desktop
|
|
18
|
+
client. Have those clients call your backend instead.
|
|
15
19
|
|
|
16
|
-
|
|
17
|
-
npm install
|
|
18
|
-
```
|
|
20
|
+
## Quick start
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
Install the package and create one `TwitchClient` in your backend. Use a
|
|
23
|
+
durable `TwitchTokenStore` implementation from your app (for example, your
|
|
24
|
+
current JSON-backed store):
|
|
21
25
|
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
```typescript
|
|
27
|
+
import { TwitchClient, type TwitchTokenStore } from 'streamops-twitch-connector';
|
|
28
|
+
import { twitchTokenStore } from './twitch-token-store.js';
|
|
25
29
|
|
|
26
|
-
|
|
30
|
+
const tokenStore: TwitchTokenStore = twitchTokenStore;
|
|
27
31
|
|
|
28
|
-
|
|
29
|
-
import {
|
|
30
|
-
TwitchClient,
|
|
31
|
-
type TwitchTokenStore,
|
|
32
|
-
type TwitchTokens,
|
|
33
|
-
} from 'streamops-twitch-connector';
|
|
34
|
-
|
|
35
|
-
const tokenStore: TwitchTokenStore = {
|
|
36
|
-
async load(): Promise<TwitchTokens | null> {
|
|
37
|
-
// Load tokens from your preferred secure storage.
|
|
38
|
-
return null;
|
|
39
|
-
},
|
|
40
|
-
|
|
41
|
-
async save(tokens: TwitchTokens): Promise<void> {
|
|
42
|
-
// Persist tokens securely.
|
|
43
|
-
},
|
|
44
|
-
|
|
45
|
-
async clear(): Promise<void> {
|
|
46
|
-
// Remove persisted tokens.
|
|
47
|
-
},
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const twitch = new TwitchClient({
|
|
32
|
+
export const twitch = new TwitchClient({
|
|
51
33
|
clientId: process.env.TWITCH_CLIENT_ID!,
|
|
34
|
+
clientSecret: process.env.TWITCH_CLIENT_SECRET!,
|
|
52
35
|
scopes: ['user:read:chat', 'user:write:chat'],
|
|
53
36
|
tokenStore,
|
|
54
37
|
});
|
|
38
|
+
```
|
|
55
39
|
|
|
56
|
-
|
|
40
|
+
`TwitchTokenStore` has three methods: `load()` returns saved tokens or `null`,
|
|
41
|
+
`save(tokens)` persists the complete token set, and `clear()` removes it.
|
|
42
|
+
Persist every `save()` call because Twitch can rotate the refresh token.
|
|
43
|
+
Protect the file or database with appropriate server-side access controls.
|
|
44
|
+
|
|
45
|
+
## Choose an authorization flow
|
|
46
|
+
|
|
47
|
+
Use Device Code Grant for clients that cannot safely keep a client secret. It
|
|
48
|
+
preserves the original `0.3.0` login API:
|
|
57
49
|
|
|
50
|
+
```typescript
|
|
51
|
+
const login = await twitch.tokens.startLogin();
|
|
58
52
|
console.log('Open:', login.verificationUri);
|
|
59
53
|
console.log('Enter code:', login.userCode);
|
|
60
54
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
await twitch.tokens.completeLogin();
|
|
55
|
+
const tokens = await twitch.tokens.completeLogin();
|
|
56
|
+
```
|
|
64
57
|
|
|
65
|
-
|
|
58
|
+
DCF refresh behavior depends on Twitch's token policy. If the refresh token
|
|
59
|
+
is rejected, the user will need to authorize again.
|
|
60
|
+
|
|
61
|
+
Use Authorization Code Grant when your product has a trusted backend. The
|
|
62
|
+
backend must keep its app's client secret private. It creates an authorization
|
|
63
|
+
URL and associates the returned `state` with the user's server-side session.
|
|
64
|
+
Redirect the user's browser to the URL:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
const callbackUri = 'https://your-product.example/api/twitch/callback';
|
|
68
|
+
const request = twitch.tokens.createAuthorizationRequest(callbackUri);
|
|
69
|
+
|
|
70
|
+
await session.set('twitchOAuthState', request.state);
|
|
71
|
+
return redirect(request.authorizationUrl);
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The callback route receives `code` and `state` from Twitch. Retrieve the
|
|
75
|
+
expected state from that same user's session and pass both values to the
|
|
76
|
+
connector. The connector checks the state before exchanging the code and
|
|
77
|
+
saving the tokens:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
const expectedState = await session.get('twitchOAuthState');
|
|
81
|
+
const tokens = await twitch.tokens.completeAuthorization({
|
|
82
|
+
code: callbackQuery.code,
|
|
83
|
+
state: callbackQuery.state,
|
|
84
|
+
expectedState,
|
|
85
|
+
redirectUri: callbackUri,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
await session.delete('twitchOAuthState');
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Use the exact same registered `redirectUri` when creating the URL and
|
|
92
|
+
exchanging the code. Handle Twitch's `error` callback parameters in the
|
|
93
|
+
product's callback route and show an appropriate message to the user.
|
|
94
|
+
Keep the client secret on the backend; Authorization Code Grant requires it
|
|
95
|
+
for this confidential-server setup.
|
|
96
|
+
|
|
97
|
+
After authorization, normal API calls use the saved tokens:
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
const user = await twitch.api.get('/users');
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The connector validates tokens, refreshes them after expiry or an API `401`,
|
|
104
|
+
and persists the new access and refresh tokens. If Twitch rejects a refresh
|
|
105
|
+
token, listen for `reauthentication_required` and send the user through the
|
|
106
|
+
connect flow again:
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
twitch.tokens.onAuthError((error) => {
|
|
110
|
+
if ('errorCode' in error && error.errorCode === 'reauthentication_required') {
|
|
111
|
+
// Notify the product that this user must connect Twitch again.
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Receive and send chat messages
|
|
117
|
+
|
|
118
|
+
After connecting EventSub, subscribe to chat messages. The callback receives
|
|
119
|
+
a plain typed message object; log it, map it to your own model, or publish it
|
|
120
|
+
to another part of your app:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
twitch.chat.onMessage((message) => {
|
|
66
124
|
console.log(`${message.chatterName}: ${message.text}`);
|
|
125
|
+
|
|
126
|
+
const productMessage = {
|
|
127
|
+
user: message.chatterName,
|
|
128
|
+
text: message.text,
|
|
129
|
+
receivedAt: message.sentAt,
|
|
130
|
+
};
|
|
131
|
+
// Pass productMessage to your own event bus or application logic.
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
twitch.eventSub.onSession(async (sessionId) => {
|
|
135
|
+
await twitch.subscriptions.subscribeToChatMessages(
|
|
136
|
+
broadcasterUserId,
|
|
137
|
+
userId,
|
|
138
|
+
sessionId,
|
|
139
|
+
);
|
|
67
140
|
});
|
|
68
141
|
|
|
69
142
|
await twitch.connectEventSub();
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Send a chat message with:
|
|
70
146
|
|
|
71
|
-
|
|
72
|
-
|
|
147
|
+
```typescript
|
|
148
|
+
await twitch.chat.sendMessage({
|
|
149
|
+
broadcasterId,
|
|
150
|
+
message: 'Hello from my app!',
|
|
151
|
+
});
|
|
73
152
|
```
|
|
74
153
|
|
|
75
|
-
## Design
|
|
76
|
-
|
|
77
|
-
- No
|
|
78
|
-
|
|
79
|
-
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
- `twitch.tokens.
|
|
88
|
-
|
|
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.
|
|
100
|
-
- Helix requests automatically include the Client ID and access token.
|
|
154
|
+
## Design and limitations
|
|
155
|
+
|
|
156
|
+
- No NestJS, Express, or other framework dependency. Products implement their
|
|
157
|
+
own routes, session/state storage, and token storage.
|
|
158
|
+
- Each product registers its own Twitch application and configures its own
|
|
159
|
+
Client ID, scopes, and token store. ACF additionally requires a private
|
|
160
|
+
client secret and registered redirect URI.
|
|
161
|
+
- The connector currently supports one authenticated Twitch account per
|
|
162
|
+
`TwitchClient` / token store. Products supporting multiple Twitch users
|
|
163
|
+
should create isolated clients and token stores per account.
|
|
164
|
+
- Tokens are checked with Twitch periodically and refreshed as needed. Helix
|
|
165
|
+
`401` responses trigger one refresh-and-retry attempt.
|
|
166
|
+
- Call `twitch.tokens.dispose()` when the client is no longer needed to stop
|
|
167
|
+
background token checks.
|
|
101
168
|
- Chat messages are received through EventSub and sent through Helix.
|
|
102
|
-
- EventSub subscriptions use the active WebSocket session ID.
|
|
103
|
-
|
|
104
|
-
## Notes
|
|
105
169
|
|
|
106
|
-
|
|
107
|
-
|
|
170
|
+
Treat access and refresh tokens as sensitive. Do not commit them to source
|
|
171
|
+
control.
|
|
108
172
|
|
|
109
|
-
|
|
110
|
-
|
|
173
|
+
The original Device Code `startLogin()` / `completeLogin()` API remains
|
|
174
|
+
available for existing products. ACF is opt-in through
|
|
175
|
+
`createAuthorizationRequest()` / `completeAuthorization()`.
|
package/dist/api/types.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { TwitchTokenStore } from '../auth/token-store.js';
|
|
2
2
|
export interface TwitchConnectorConfig {
|
|
3
3
|
clientId: string;
|
|
4
|
-
/**
|
|
4
|
+
/** Required for Authorization Code Grant. Keep it on a trusted server; never expose it to clients. */
|
|
5
5
|
clientSecret?: string;
|
|
6
6
|
scopes: string[];
|
|
7
7
|
tokenStore: TwitchTokenStore;
|
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,
|
|
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,sGAAsG;IACtG,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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { TwitchConnectorConfig } from '../api/types.js';
|
|
2
|
+
export interface TwitchAuthorizationRequest {
|
|
3
|
+
redirectUri: string;
|
|
4
|
+
state: string;
|
|
5
|
+
}
|
|
6
|
+
export interface TwitchAuthorizationRequestResult {
|
|
7
|
+
authorizationUrl: string;
|
|
8
|
+
state: string;
|
|
9
|
+
}
|
|
10
|
+
export interface TwitchAuthorizationCallback {
|
|
11
|
+
code: string;
|
|
12
|
+
redirectUri: string;
|
|
13
|
+
state: string;
|
|
14
|
+
expectedState: string;
|
|
15
|
+
}
|
|
16
|
+
export interface AuthorizationCodeTokenResponse {
|
|
17
|
+
access_token: string;
|
|
18
|
+
refresh_token: string;
|
|
19
|
+
expires_in: number;
|
|
20
|
+
scope: string[];
|
|
21
|
+
}
|
|
22
|
+
export declare class AuthorizationCodeFlow {
|
|
23
|
+
private readonly config;
|
|
24
|
+
constructor(config: Pick<TwitchConnectorConfig, 'clientId' | 'clientSecret' | 'scopes' | 'fetch'>);
|
|
25
|
+
createAuthorizationRequest(redirectUri: string): TwitchAuthorizationRequestResult;
|
|
26
|
+
createAuthorizationUrl(request: TwitchAuthorizationRequest): string;
|
|
27
|
+
exchangeCode(callback: TwitchAuthorizationCallback): Promise<AuthorizationCodeTokenResponse>;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=authorization-code-flow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authorization-code-flow.d.ts","sourceRoot":"","sources":["../../src/auth/authorization-code-flow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAM7D,MAAM,WAAW,0BAA0B;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gCAAgC;IAC/C,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,8BAA8B;IAC7C,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,qBAAa,qBAAqB;IAE9B,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,IAAI,CAC3B,qBAAqB,EACrB,UAAU,GAAG,cAAc,GAAG,QAAQ,GAAG,OAAO,CACjD;IAGH,0BAA0B,CACxB,WAAW,EAAE,MAAM,GAClB,gCAAgC;IA4BnC,sBAAsB,CAAC,OAAO,EAAE,0BAA0B,GAAG,MAAM;IAqB7D,YAAY,CAChB,QAAQ,EAAE,2BAA2B,GACpC,OAAO,CAAC,8BAA8B,CAAC;CAyG3C"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { TwitchError } from '../errors/twitch-error.js';
|
|
2
|
+
const TWITCH_AUTHORIZE_URL = 'https://id.twitch.tv/oauth2/authorize';
|
|
3
|
+
const TWITCH_TOKEN_URL = 'https://id.twitch.tv/oauth2/token';
|
|
4
|
+
export class AuthorizationCodeFlow {
|
|
5
|
+
config;
|
|
6
|
+
constructor(config) {
|
|
7
|
+
this.config = config;
|
|
8
|
+
}
|
|
9
|
+
createAuthorizationRequest(redirectUri) {
|
|
10
|
+
if (!redirectUri) {
|
|
11
|
+
throw new TwitchError('A redirect URI is required.', undefined, 'invalid_authorization_request');
|
|
12
|
+
}
|
|
13
|
+
if (!globalThis.crypto?.getRandomValues) {
|
|
14
|
+
throw new TwitchError('A secure random source is required to start Twitch authorization.', undefined, 'secure_random_unavailable');
|
|
15
|
+
}
|
|
16
|
+
const bytes = globalThis.crypto.getRandomValues(new Uint8Array(32));
|
|
17
|
+
const state = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
18
|
+
return {
|
|
19
|
+
authorizationUrl: this.createAuthorizationUrl({ redirectUri, state }),
|
|
20
|
+
state,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
createAuthorizationUrl(request) {
|
|
24
|
+
if (!request.redirectUri || !request.state) {
|
|
25
|
+
throw new TwitchError('A redirect URI and OAuth state are required.', undefined, 'invalid_authorization_request');
|
|
26
|
+
}
|
|
27
|
+
const url = new URL(TWITCH_AUTHORIZE_URL);
|
|
28
|
+
url.search = new URLSearchParams({
|
|
29
|
+
client_id: this.config.clientId,
|
|
30
|
+
redirect_uri: request.redirectUri,
|
|
31
|
+
response_type: 'code',
|
|
32
|
+
scope: this.config.scopes.join(' '),
|
|
33
|
+
state: request.state,
|
|
34
|
+
}).toString();
|
|
35
|
+
return url.toString();
|
|
36
|
+
}
|
|
37
|
+
async exchangeCode(callback) {
|
|
38
|
+
if (!this.config.clientSecret) {
|
|
39
|
+
throw new TwitchError('A client secret is required for Authorization Code Grant. Keep it on a trusted server.', undefined, 'client_secret_required');
|
|
40
|
+
}
|
|
41
|
+
if (!callback.code || !callback.redirectUri) {
|
|
42
|
+
throw new TwitchError('An authorization code and redirect URI are required.', undefined, 'invalid_authorization_callback');
|
|
43
|
+
}
|
|
44
|
+
if (!callback.expectedState || callback.state !== callback.expectedState) {
|
|
45
|
+
throw new TwitchError('Twitch authorization state did not match. Start login again.', undefined, 'authorization_state_mismatch');
|
|
46
|
+
}
|
|
47
|
+
const body = new URLSearchParams({
|
|
48
|
+
client_id: this.config.clientId,
|
|
49
|
+
client_secret: this.config.clientSecret,
|
|
50
|
+
code: callback.code,
|
|
51
|
+
grant_type: 'authorization_code',
|
|
52
|
+
redirect_uri: callback.redirectUri,
|
|
53
|
+
});
|
|
54
|
+
let response;
|
|
55
|
+
try {
|
|
56
|
+
response = await (this.config.fetch ?? fetch)(TWITCH_TOKEN_URL, {
|
|
57
|
+
method: 'POST',
|
|
58
|
+
headers: {
|
|
59
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
60
|
+
},
|
|
61
|
+
body,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch (cause) {
|
|
65
|
+
throw new TwitchError('Unable to contact Twitch to exchange the authorization code.', undefined, 'authorization_exchange_network_error', { cause });
|
|
66
|
+
}
|
|
67
|
+
let data;
|
|
68
|
+
try {
|
|
69
|
+
data = await response.json();
|
|
70
|
+
}
|
|
71
|
+
catch (cause) {
|
|
72
|
+
throw new TwitchError('Twitch returned an invalid authorization response.', response.status, 'invalid_oauth_response', { cause });
|
|
73
|
+
}
|
|
74
|
+
if (!response.ok) {
|
|
75
|
+
throw new TwitchError('Twitch authorization code exchange failed.', response.status, response.status === 400 || response.status === 401
|
|
76
|
+
? 'reauthentication_required'
|
|
77
|
+
: 'authorization_exchange_failed');
|
|
78
|
+
}
|
|
79
|
+
if (!data || typeof data !== 'object') {
|
|
80
|
+
throw new TwitchError('Twitch returned an invalid authorization token response.', response.status, 'invalid_token_response');
|
|
81
|
+
}
|
|
82
|
+
const token = data;
|
|
83
|
+
if (typeof token.access_token !== 'string' ||
|
|
84
|
+
!token.access_token ||
|
|
85
|
+
typeof token.refresh_token !== 'string' ||
|
|
86
|
+
!token.refresh_token ||
|
|
87
|
+
typeof token.expires_in !== 'number' ||
|
|
88
|
+
!Number.isFinite(token.expires_in) ||
|
|
89
|
+
token.expires_in <= 0 ||
|
|
90
|
+
!Array.isArray(token.scope) ||
|
|
91
|
+
!token.scope.every((scope) => typeof scope === 'string')) {
|
|
92
|
+
throw new TwitchError('Twitch returned an invalid authorization token response.', response.status, 'invalid_token_response');
|
|
93
|
+
}
|
|
94
|
+
return token;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=authorization-code-flow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authorization-code-flow.js","sourceRoot":"","sources":["../../src/auth/authorization-code-flow.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
import type { TwitchConnectorConfig, TwitchValidatedToken } from '../api/types.js';
|
|
2
2
|
import type { DeviceCodeFlow } from './device-code-flow.js';
|
|
3
|
+
import { AuthorizationCodeFlow } from './authorization-code-flow.js';
|
|
4
|
+
import type { TwitchAuthorizationCallback, TwitchAuthorizationRequest, TwitchAuthorizationRequestResult } from './authorization-code-flow.js';
|
|
3
5
|
import type { TwitchTokenStore, TwitchTokens } from './token-store.js';
|
|
4
6
|
export declare class TokenManager {
|
|
5
7
|
private readonly config;
|
|
6
8
|
private readonly tokenStore;
|
|
7
9
|
private readonly deviceCodeFlow;
|
|
10
|
+
private readonly authorizationCodeFlow;
|
|
8
11
|
private refreshPromise;
|
|
9
12
|
private validationPromise;
|
|
10
13
|
private validationTimer;
|
|
11
14
|
private lastValidatedAt;
|
|
12
15
|
private lastAuthError;
|
|
16
|
+
private validationFailureCount;
|
|
13
17
|
private validationMonitoring;
|
|
14
18
|
private readonly authErrorHandlers;
|
|
15
|
-
constructor(config: Pick<TwitchConnectorConfig, 'clientId' | 'clientSecret' | 'scopes' | 'fetch'>, tokenStore: TwitchTokenStore, deviceCodeFlow: DeviceCodeFlow);
|
|
19
|
+
constructor(config: Pick<TwitchConnectorConfig, 'clientId' | 'clientSecret' | 'scopes' | 'fetch'>, tokenStore: TwitchTokenStore, deviceCodeFlow: DeviceCodeFlow, authorizationCodeFlow?: AuthorizationCodeFlow);
|
|
16
20
|
private get fetcher();
|
|
17
21
|
/** Loads persisted token metadata without attempting an expiry-based refresh. */
|
|
18
22
|
getTokens(): Promise<TwitchTokens>;
|
|
@@ -25,8 +29,12 @@ export declare class TokenManager {
|
|
|
25
29
|
* already performed by another request when the store has the newer token.
|
|
26
30
|
*/
|
|
27
31
|
refreshAfterUnauthorized(rejectedAccessToken: string): Promise<string>;
|
|
32
|
+
createAuthorizationUrl(request: TwitchAuthorizationRequest): string;
|
|
33
|
+
createAuthorizationRequest(redirectUri: string): TwitchAuthorizationRequestResult;
|
|
28
34
|
startLogin(): Promise<import("./device-code-flow.js").DeviceCodeLogin>;
|
|
29
35
|
completeLogin(signal?: AbortSignal): Promise<TwitchTokens>;
|
|
36
|
+
completeAuthorization(callback: TwitchAuthorizationCallback): Promise<TwitchTokens>;
|
|
37
|
+
private saveLoginTokens;
|
|
30
38
|
logout(): Promise<void>;
|
|
31
39
|
onAuthError(handler: (error: Error) => void): () => void;
|
|
32
40
|
get authError(): Error | null;
|
|
@@ -35,6 +43,8 @@ export declare class TokenManager {
|
|
|
35
43
|
private validateCurrentTokens;
|
|
36
44
|
private validate;
|
|
37
45
|
private scheduleValidation;
|
|
46
|
+
private notifyAuthError;
|
|
47
|
+
private scheduleValidationRetry;
|
|
38
48
|
private stopValidationTimer;
|
|
39
49
|
private refreshTokens;
|
|
40
50
|
private performRefresh;
|
|
@@ -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,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,KAAK,EACV,2BAA2B,EAC3B,0BAA0B,EAC1B,gCAAgC,EACjC,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAkBvE,qBAAa,YAAY;IAYrB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAIvB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,cAAc;IAhBjC,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAwB;IAC9D,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,sBAAsB,CAAK;IACnC,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,EAC/C,qBAAqB,CAAC,EAAE,qBAAqB;IAM/C,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;IAelB,sBAAsB,CAAC,OAAO,EAAE,0BAA0B,GAAG,MAAM;IAInE,0BAA0B,CACxB,WAAW,EAAE,MAAM,GAClB,gCAAgC;IAI7B,UAAU;IAIV,aAAa,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IAW1D,qBAAqB,CACzB,QAAQ,EAAE,2BAA2B,GACpC,OAAO,CAAC,YAAY,CAAC;YAWV,eAAe;IAUvB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAS7B,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;IAuDnC,OAAO,CAAC,QAAQ;IAQhB,OAAO,CAAC,kBAAkB;IA+B1B,OAAO,CAAC,eAAe;IAevB,OAAO,CAAC,uBAAuB;IAS/B,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,aAAa;YAYP,cAAc;CAqD7B"}
|
|
@@ -1,23 +1,30 @@
|
|
|
1
1
|
import { TwitchError } from '../errors/twitch-error.js';
|
|
2
|
+
import { AuthorizationCodeFlow } from './authorization-code-flow.js';
|
|
2
3
|
const TWITCH_VALIDATE_URL = 'https://id.twitch.tv/oauth2/validate';
|
|
3
4
|
const TWITCH_TOKEN_URL = 'https://id.twitch.tv/oauth2/token';
|
|
4
5
|
const TOKEN_VALIDATION_INTERVAL_MS = 50 * 60 * 1000;
|
|
5
6
|
const TOKEN_REFRESH_BUFFER_MS = 5 * 60 * 1000;
|
|
7
|
+
const INITIAL_VALIDATION_RETRY_MS = 60 * 1000;
|
|
8
|
+
const MAX_VALIDATION_RETRY_MS = 5 * 60 * 1000;
|
|
6
9
|
export class TokenManager {
|
|
7
10
|
config;
|
|
8
11
|
tokenStore;
|
|
9
12
|
deviceCodeFlow;
|
|
13
|
+
authorizationCodeFlow;
|
|
10
14
|
refreshPromise = null;
|
|
11
15
|
validationPromise = null;
|
|
12
16
|
validationTimer = null;
|
|
13
17
|
lastValidatedAt = 0;
|
|
14
18
|
lastAuthError = null;
|
|
19
|
+
validationFailureCount = 0;
|
|
15
20
|
validationMonitoring = true;
|
|
16
21
|
authErrorHandlers = new Set();
|
|
17
|
-
constructor(config, tokenStore, deviceCodeFlow) {
|
|
22
|
+
constructor(config, tokenStore, deviceCodeFlow, authorizationCodeFlow) {
|
|
18
23
|
this.config = config;
|
|
19
24
|
this.tokenStore = tokenStore;
|
|
20
25
|
this.deviceCodeFlow = deviceCodeFlow;
|
|
26
|
+
this.authorizationCodeFlow =
|
|
27
|
+
authorizationCodeFlow ?? new AuthorizationCodeFlow(config);
|
|
21
28
|
}
|
|
22
29
|
get fetcher() {
|
|
23
30
|
return this.config.fetch ?? fetch;
|
|
@@ -59,19 +66,39 @@ export class TokenManager {
|
|
|
59
66
|
: tokens;
|
|
60
67
|
return refreshed.accessToken;
|
|
61
68
|
}
|
|
69
|
+
createAuthorizationUrl(request) {
|
|
70
|
+
return this.authorizationCodeFlow.createAuthorizationUrl(request);
|
|
71
|
+
}
|
|
72
|
+
createAuthorizationRequest(redirectUri) {
|
|
73
|
+
return this.authorizationCodeFlow.createAuthorizationRequest(redirectUri);
|
|
74
|
+
}
|
|
62
75
|
async startLogin() {
|
|
63
76
|
return this.deviceCodeFlow.startLogin();
|
|
64
77
|
}
|
|
65
78
|
async completeLogin(signal) {
|
|
66
79
|
const response = await this.deviceCodeFlow.waitForAuthorization(signal);
|
|
67
|
-
|
|
80
|
+
return this.saveLoginTokens({
|
|
68
81
|
accessToken: response.access_token,
|
|
69
82
|
refreshToken: response.refresh_token,
|
|
70
83
|
expiresAt: Date.now() + response.expires_in * 1000,
|
|
71
84
|
scopes: response.scope,
|
|
72
|
-
};
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
async completeAuthorization(callback) {
|
|
88
|
+
const response = await this.authorizationCodeFlow.exchangeCode(callback);
|
|
89
|
+
return this.saveLoginTokens({
|
|
90
|
+
accessToken: response.access_token,
|
|
91
|
+
refreshToken: response.refresh_token,
|
|
92
|
+
expiresAt: Date.now() + response.expires_in * 1000,
|
|
93
|
+
scopes: response.scope,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
async saveLoginTokens(tokens) {
|
|
73
97
|
await this.tokenStore.save(tokens);
|
|
98
|
+
this.stopValidationTimer();
|
|
74
99
|
this.lastValidatedAt = 0;
|
|
100
|
+
this.lastAuthError = null;
|
|
101
|
+
this.validationFailureCount = 0;
|
|
75
102
|
this.validationMonitoring = true;
|
|
76
103
|
return tokens;
|
|
77
104
|
}
|
|
@@ -80,6 +107,7 @@ export class TokenManager {
|
|
|
80
107
|
this.stopValidationTimer();
|
|
81
108
|
this.lastValidatedAt = 0;
|
|
82
109
|
this.lastAuthError = null;
|
|
110
|
+
this.validationFailureCount = 0;
|
|
83
111
|
await this.tokenStore.clear();
|
|
84
112
|
}
|
|
85
113
|
onAuthError(handler) {
|
|
@@ -140,6 +168,7 @@ export class TokenManager {
|
|
|
140
168
|
}
|
|
141
169
|
this.lastValidatedAt = Date.now();
|
|
142
170
|
this.lastAuthError = null;
|
|
171
|
+
this.validationFailureCount = 0;
|
|
143
172
|
this.scheduleValidation(expiresAt);
|
|
144
173
|
return validated;
|
|
145
174
|
}
|
|
@@ -163,17 +192,32 @@ export class TokenManager {
|
|
|
163
192
|
.catch((error) => {
|
|
164
193
|
const authError = error instanceof Error ? error : new Error(String(error));
|
|
165
194
|
this.lastAuthError = authError;
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
this.scheduleValidation(Date.now() +
|
|
170
|
-
TOKEN_VALIDATION_INTERVAL_MS +
|
|
171
|
-
TOKEN_REFRESH_BUFFER_MS);
|
|
195
|
+
this.notifyAuthError(authError);
|
|
196
|
+
this.scheduleValidationRetry();
|
|
172
197
|
});
|
|
173
198
|
}, delay);
|
|
174
199
|
const timer = this.validationTimer;
|
|
175
200
|
timer.unref?.();
|
|
176
201
|
}
|
|
202
|
+
notifyAuthError(error) {
|
|
203
|
+
if (this.authErrorHandlers.size === 0) {
|
|
204
|
+
console.error('[Twitch] Background token validation failed:', error);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
for (const handler of this.authErrorHandlers) {
|
|
208
|
+
try {
|
|
209
|
+
handler(error);
|
|
210
|
+
}
|
|
211
|
+
catch (handlerError) {
|
|
212
|
+
console.error('[Twitch] Auth error handler failed:', handlerError);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
scheduleValidationRetry() {
|
|
217
|
+
const delay = Math.min(INITIAL_VALIDATION_RETRY_MS * 2 ** this.validationFailureCount, MAX_VALIDATION_RETRY_MS);
|
|
218
|
+
this.validationFailureCount += 1;
|
|
219
|
+
this.scheduleValidation(Date.now() + delay + TOKEN_REFRESH_BUFFER_MS);
|
|
220
|
+
}
|
|
177
221
|
stopValidationTimer() {
|
|
178
222
|
if (this.validationTimer) {
|
|
179
223
|
clearTimeout(this.validationTimer);
|
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"token-manager.js","sourceRoot":"","sources":["../../src/auth/token-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { EventSubConnectionState, EventSubNotificationHandler } from './types.js';
|
|
2
2
|
export type EventSubSessionHandler = (sessionId: string, isReconnectHandover: boolean) => Promise<void> | void;
|
|
3
|
+
export type EventSubErrorHandler = (error: Error) => void;
|
|
3
4
|
export declare class EventSubClient {
|
|
4
5
|
private readonly onStateChange?;
|
|
5
6
|
private socket;
|
|
@@ -8,21 +9,26 @@ export declare class EventSubClient {
|
|
|
8
9
|
private keepaliveTimeoutSeconds;
|
|
9
10
|
private keepaliveTimer;
|
|
10
11
|
private reconnectTimer;
|
|
12
|
+
private reconnectAttempts;
|
|
11
13
|
private intentionalClose;
|
|
12
14
|
private state;
|
|
13
15
|
private readonly handlers;
|
|
14
16
|
private readonly sessionHandlers;
|
|
17
|
+
private readonly errorHandlers;
|
|
15
18
|
constructor(onStateChange?: ((state: EventSubConnectionState) => void) | undefined);
|
|
16
19
|
get connectionState(): EventSubConnectionState;
|
|
17
20
|
get currentSessionId(): string | null;
|
|
18
21
|
onNotification(handler: EventSubNotificationHandler): () => void;
|
|
19
22
|
onSession(handler: EventSubSessionHandler): () => void;
|
|
23
|
+
onError(handler: EventSubErrorHandler): () => void;
|
|
20
24
|
connect(): Promise<void>;
|
|
21
25
|
disconnect(): Promise<void>;
|
|
22
26
|
private openSocket;
|
|
23
27
|
private handleMessage;
|
|
24
28
|
private resetKeepaliveTimer;
|
|
25
29
|
private scheduleReconnect;
|
|
30
|
+
private reconnectAfterSessionError;
|
|
31
|
+
private reportError;
|
|
26
32
|
private setState;
|
|
27
33
|
private clearKeepaliveTimer;
|
|
28
34
|
private clearReconnectTimer;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eventsub-client.d.ts","sourceRoot":"","sources":["../../src/eventsub/eventsub-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,uBAAuB,EAEvB,2BAA2B,EAC5B,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"eventsub-client.d.ts","sourceRoot":"","sources":["../../src/eventsub/eventsub-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,uBAAuB,EAEvB,2BAA2B,EAC5B,MAAM,YAAY,CAAC;AAOpB,MAAM,MAAM,sBAAsB,GAAG,CACnC,SAAS,EAAE,MAAM,EACjB,mBAAmB,EAAE,OAAO,KACzB,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAE1B,MAAM,MAAM,oBAAoB,GAAG,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;AAE1D,qBAAa,cAAc;IAgBvB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC;IAfjC,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,uBAAuB,CAAM;IACrC,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,KAAK,CAA2C;IAExD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA0C;IACnE,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAqC;IACrE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAmC;gBAG9C,aAAa,CAAC,GAAE,CAC/B,KAAK,EAAE,uBAAuB,KAC3B,IAAI,aAAA;IAGX,IAAI,eAAe,IAAI,uBAAuB,CAE7C;IAED,IAAI,gBAAgB,IAAI,MAAM,GAAG,IAAI,CAEpC;IAED,cAAc,CAAC,OAAO,EAAE,2BAA2B,GAAG,MAAM,IAAI;IAKhE,SAAS,CAAC,OAAO,EAAE,sBAAsB,GAAG,MAAM,IAAI;IAKtD,OAAO,CAAC,OAAO,EAAE,oBAAoB,GAAG,MAAM,IAAI;IAK5C,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAgBxB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAgBjC,OAAO,CAAC,UAAU;YAsFJ,aAAa;IAkH3B,OAAO,CAAC,mBAAmB;IAsB3B,OAAO,CAAC,iBAAiB;IA2BzB,OAAO,CAAC,0BAA0B;IAYlC,OAAO,CAAC,WAAW;IAkBnB,OAAO,CAAC,QAAQ;IAKhB,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,WAAW;CAIpB"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { TwitchError } from '../errors/twitch-error.js';
|
|
2
2
|
const EVENTSUB_URL = 'wss://eventsub.wss.twitch.tv/ws';
|
|
3
|
+
const INITIAL_RECONNECT_DELAY_MS = 2_000;
|
|
4
|
+
const MAX_RECONNECT_DELAY_MS = 30_000;
|
|
3
5
|
export class EventSubClient {
|
|
4
6
|
onStateChange;
|
|
5
7
|
socket = null;
|
|
@@ -8,10 +10,12 @@ export class EventSubClient {
|
|
|
8
10
|
keepaliveTimeoutSeconds = 10;
|
|
9
11
|
keepaliveTimer = null;
|
|
10
12
|
reconnectTimer = null;
|
|
13
|
+
reconnectAttempts = 0;
|
|
11
14
|
intentionalClose = false;
|
|
12
15
|
state = 'disconnected';
|
|
13
16
|
handlers = new Set();
|
|
14
17
|
sessionHandlers = new Set();
|
|
18
|
+
errorHandlers = new Set();
|
|
15
19
|
constructor(onStateChange) {
|
|
16
20
|
this.onStateChange = onStateChange;
|
|
17
21
|
}
|
|
@@ -29,6 +33,10 @@ export class EventSubClient {
|
|
|
29
33
|
this.sessionHandlers.add(handler);
|
|
30
34
|
return () => this.sessionHandlers.delete(handler);
|
|
31
35
|
}
|
|
36
|
+
onError(handler) {
|
|
37
|
+
this.errorHandlers.add(handler);
|
|
38
|
+
return () => this.errorHandlers.delete(handler);
|
|
39
|
+
}
|
|
32
40
|
async connect() {
|
|
33
41
|
this.intentionalClose = false;
|
|
34
42
|
this.clearReconnectTimer();
|
|
@@ -64,6 +72,7 @@ export class EventSubClient {
|
|
|
64
72
|
}
|
|
65
73
|
const previousSocket = this.socket;
|
|
66
74
|
this.socket = socket;
|
|
75
|
+
let socketErrorReported = false;
|
|
67
76
|
// Logging suppressed; retain original connection behavior.
|
|
68
77
|
void previousSocket;
|
|
69
78
|
socket.addEventListener('open', () => {
|
|
@@ -77,21 +86,27 @@ export class EventSubClient {
|
|
|
77
86
|
return;
|
|
78
87
|
}
|
|
79
88
|
const raw = String(message.data);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
// EventSub processing errors suppressed.
|
|
89
|
+
void this.handleMessage(raw, isReconnectHandover).catch((error) => {
|
|
90
|
+
this.reportError(error);
|
|
83
91
|
});
|
|
84
92
|
});
|
|
85
93
|
socket.addEventListener('error', () => {
|
|
86
|
-
|
|
94
|
+
if (this.socket !== socket) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
socketErrorReported = true;
|
|
98
|
+
this.reportError(new TwitchError('Twitch EventSub WebSocket encountered a connection error.'));
|
|
87
99
|
});
|
|
88
|
-
socket.addEventListener('close', (
|
|
100
|
+
socket.addEventListener('close', (event) => {
|
|
89
101
|
if (this.socket !== socket) {
|
|
90
102
|
return;
|
|
91
103
|
}
|
|
92
104
|
this.socket = null;
|
|
93
105
|
this.clearKeepaliveTimer();
|
|
94
106
|
if (!this.intentionalClose) {
|
|
107
|
+
if (!socketErrorReported) {
|
|
108
|
+
this.reportError(new TwitchError(`Twitch EventSub WebSocket closed unexpectedly (code: ${event.code}${event.reason ? `, reason: ${event.reason}` : ''}).`));
|
|
109
|
+
}
|
|
95
110
|
this.setState('reconnecting');
|
|
96
111
|
this.scheduleReconnect();
|
|
97
112
|
}
|
|
@@ -118,8 +133,16 @@ export class EventSubClient {
|
|
|
118
133
|
this.reconnectUrl = null;
|
|
119
134
|
this.resetKeepaliveTimer();
|
|
120
135
|
for (const handler of this.sessionHandlers) {
|
|
121
|
-
|
|
136
|
+
try {
|
|
137
|
+
await handler(session.id, isReconnectHandover);
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
this.reportError(error);
|
|
141
|
+
this.reconnectAfterSessionError();
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
122
144
|
}
|
|
145
|
+
this.reconnectAttempts = 0;
|
|
123
146
|
break;
|
|
124
147
|
}
|
|
125
148
|
case 'session_keepalive':
|
|
@@ -128,6 +151,7 @@ export class EventSubClient {
|
|
|
128
151
|
case 'session_reconnect': {
|
|
129
152
|
const reconnectUrl = envelope.payload.session?.reconnect_url;
|
|
130
153
|
if (!reconnectUrl) {
|
|
154
|
+
this.reportError(new TwitchError('Twitch EventSub reconnect message did not include a reconnect URL.'));
|
|
131
155
|
return;
|
|
132
156
|
}
|
|
133
157
|
this.reconnectUrl = reconnectUrl;
|
|
@@ -149,11 +173,14 @@ export class EventSubClient {
|
|
|
149
173
|
break;
|
|
150
174
|
}
|
|
151
175
|
case 'session_reconnect_failed':
|
|
176
|
+
this.reportError(new TwitchError('Twitch EventSub session handover failed.'));
|
|
152
177
|
this.reconnectUrl = null;
|
|
153
178
|
this.scheduleReconnect();
|
|
154
179
|
break;
|
|
155
180
|
case 'revocation':
|
|
156
|
-
|
|
181
|
+
this.reportError(new TwitchError(`Twitch revoked the EventSub subscription${envelope.payload.subscription?.type
|
|
182
|
+
? ` "${envelope.payload.subscription.type}"`
|
|
183
|
+
: ''} (status: ${envelope.payload.subscription?.status ?? 'unknown'}).`));
|
|
157
184
|
break;
|
|
158
185
|
default:
|
|
159
186
|
// Unknown message logging suppressed.
|
|
@@ -165,6 +192,7 @@ export class EventSubClient {
|
|
|
165
192
|
this.keepaliveTimer = setTimeout(() => {
|
|
166
193
|
const socket = this.socket;
|
|
167
194
|
this.socket = null;
|
|
195
|
+
this.reportError(new TwitchError(`Twitch EventSub did not receive a keepalive or notification within ${this.keepaliveTimeoutSeconds} seconds.`));
|
|
168
196
|
if (socket && socket.readyState !== WebSocket.CLOSED) {
|
|
169
197
|
socket.close();
|
|
170
198
|
}
|
|
@@ -178,13 +206,45 @@ export class EventSubClient {
|
|
|
178
206
|
return;
|
|
179
207
|
}
|
|
180
208
|
this.setState('reconnecting');
|
|
209
|
+
const delay = Math.min(INITIAL_RECONNECT_DELAY_MS * 2 ** this.reconnectAttempts, MAX_RECONNECT_DELAY_MS);
|
|
210
|
+
this.reconnectAttempts += 1;
|
|
181
211
|
this.reconnectTimer = setTimeout(() => {
|
|
182
212
|
this.reconnectTimer = null;
|
|
183
213
|
this.sessionId = null;
|
|
184
214
|
if (!this.intentionalClose) {
|
|
185
|
-
|
|
215
|
+
try {
|
|
216
|
+
this.openSocket(this.reconnectUrl ?? EVENTSUB_URL);
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
this.reportError(error);
|
|
220
|
+
}
|
|
186
221
|
}
|
|
187
|
-
},
|
|
222
|
+
}, delay);
|
|
223
|
+
}
|
|
224
|
+
reconnectAfterSessionError() {
|
|
225
|
+
const socket = this.socket;
|
|
226
|
+
if (!socket || this.intentionalClose) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
this.socket = null;
|
|
230
|
+
this.clearKeepaliveTimer();
|
|
231
|
+
socket.close();
|
|
232
|
+
this.scheduleReconnect();
|
|
233
|
+
}
|
|
234
|
+
reportError(error) {
|
|
235
|
+
const normalizedError = error instanceof Error ? error : new Error(String(error));
|
|
236
|
+
if (this.errorHandlers.size === 0) {
|
|
237
|
+
console.error('[EventSub] Error:', normalizedError);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
for (const handler of this.errorHandlers) {
|
|
241
|
+
try {
|
|
242
|
+
handler(normalizedError);
|
|
243
|
+
}
|
|
244
|
+
catch (handlerError) {
|
|
245
|
+
console.error('[EventSub] Error handler failed:', handlerError);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
188
248
|
}
|
|
189
249
|
setState(state) {
|
|
190
250
|
this.state = state;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eventsub-client.js","sourceRoot":"","sources":["../../src/eventsub/eventsub-client.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAExD,MAAM,YAAY,GAAG,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"eventsub-client.js","sourceRoot":"","sources":["../../src/eventsub/eventsub-client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,10 +2,11 @@ export { TwitchClient } from './twitch/twitch-client.js';
|
|
|
2
2
|
export { TwitchApiClient } from './api/twitch-api-client.js';
|
|
3
3
|
export type { TwitchConnectorConfig, TwitchValidatedToken, } from './api/types.js';
|
|
4
4
|
export { TokenManager } from './auth/token-manager.js';
|
|
5
|
+
export type { TwitchAuthorizationCallback, TwitchAuthorizationRequest, TwitchAuthorizationRequestResult, } from './auth/authorization-code-flow.js';
|
|
5
6
|
export type { TwitchTokenStore, TwitchTokens, } from './auth/token-store.js';
|
|
6
7
|
export { TwitchChatClient } from './chat/twitch-chat-client.js';
|
|
7
8
|
export type { SendChatMessageOptions, SendChatMessageResult, TwitchChatMessage, TwitchChatMessageHandler, } from './chat/types.js';
|
|
8
|
-
export { EventSubClient } from './eventsub/eventsub-client.js';
|
|
9
|
+
export { EventSubClient, type EventSubErrorHandler, } from './eventsub/eventsub-client.js';
|
|
9
10
|
export { EventSubSubscriptions } from './eventsub/eventsub-subscription.js';
|
|
10
11
|
export type { EventSubConnectionState, EventSubEnvelope, EventSubNotificationHandler, } from './eventsub/types.js';
|
|
11
12
|
export { TwitchError } from './errors/twitch-error.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzD,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,YAAY,EACV,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,YAAY,EACV,gBAAgB,EAChB,YAAY,GACb,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,YAAY,EACV,sBAAsB,EACtB,qBAAqB,EACrB,iBAAiB,EACjB,wBAAwB,GACzB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzD,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,YAAY,EACV,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,YAAY,EACV,2BAA2B,EAC3B,0BAA0B,EAC1B,gCAAgC,GACjC,MAAM,mCAAmC,CAAC;AAC3C,YAAY,EACV,gBAAgB,EAChB,YAAY,GACb,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,YAAY,EACV,sBAAsB,EACtB,qBAAqB,EACrB,iBAAiB,EACjB,wBAAwB,GACzB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,cAAc,EACd,KAAK,oBAAoB,GAC1B,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAC5E,YAAY,EACV,uBAAuB,EACvB,gBAAgB,EAChB,2BAA2B,GAC5B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ export { TwitchClient } from './twitch/twitch-client.js';
|
|
|
2
2
|
export { TwitchApiClient } from './api/twitch-api-client.js';
|
|
3
3
|
export { TokenManager } from './auth/token-manager.js';
|
|
4
4
|
export { TwitchChatClient } from './chat/twitch-chat-client.js';
|
|
5
|
-
export { EventSubClient } from './eventsub/eventsub-client.js';
|
|
5
|
+
export { EventSubClient, } from './eventsub/eventsub-client.js';
|
|
6
6
|
export { EventSubSubscriptions } from './eventsub/eventsub-subscription.js';
|
|
7
7
|
export { TwitchError } from './errors/twitch-error.js';
|
|
8
8
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzD,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAM7D,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzD,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAM7D,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAWvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAQhE,OAAO,EACL,cAAc,GAEf,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAO5E,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC"}
|
|
@@ -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;
|
|
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;AAG9D,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;IAuCnC,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAKhC,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;CAG1C"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { TwitchApiClient } from '../api/twitch-api-client.js';
|
|
2
|
+
import { AuthorizationCodeFlow } from '../auth/authorization-code-flow.js';
|
|
2
3
|
import { DeviceCodeFlow } from '../auth/device-code-flow.js';
|
|
3
4
|
import { TokenManager } from '../auth/token-manager.js';
|
|
4
5
|
import { TwitchChatClient } from '../chat/twitch-chat-client.js';
|
|
@@ -17,7 +18,13 @@ export class TwitchClient {
|
|
|
17
18
|
scopes: config.scopes,
|
|
18
19
|
fetch: config.fetch,
|
|
19
20
|
});
|
|
20
|
-
|
|
21
|
+
const authorizationCodeFlow = new AuthorizationCodeFlow({
|
|
22
|
+
clientId: config.clientId,
|
|
23
|
+
clientSecret: config.clientSecret,
|
|
24
|
+
scopes: config.scopes,
|
|
25
|
+
fetch: config.fetch,
|
|
26
|
+
});
|
|
27
|
+
this.tokens = new TokenManager(config, tokenStore, deviceCodeFlow, authorizationCodeFlow);
|
|
21
28
|
this.api = new TwitchApiClient(config, this.tokens);
|
|
22
29
|
this.chat = new TwitchChatClient(this.api);
|
|
23
30
|
this.eventSub = new EventSubClient();
|
|
@@ -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,
|
|
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,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAC3E,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,MAAM,qBAAqB,GAAG,IAAI,qBAAqB,CAAC;YACtD,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,YAAY,EAAE,MAAM,CAAC,YAAY;YACjC,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,EACd,qBAAqB,CACtB,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"}
|