@openclaw/twitch 2026.5.22 → 2026.5.24-beta.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/dist/api.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as twitchPlugin } from "./plugin-
|
|
1
|
+
import { t as twitchPlugin } from "./plugin-BWX0hpqA.js";
|
|
2
2
|
export { twitchPlugin };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as stripMarkdownForTwitch, r as getOrCreateClientManager } from "./plugin-
|
|
1
|
+
import { n as stripMarkdownForTwitch, r as getOrCreateClientManager } from "./plugin-BWX0hpqA.js";
|
|
2
2
|
import { t as getTwitchRuntime } from "./runtime-B5If4b0g.js";
|
|
3
3
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
4
4
|
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
@@ -22,7 +22,10 @@ var TwitchClientManager = class {
|
|
|
22
22
|
constructor(logger) {
|
|
23
23
|
this.logger = logger;
|
|
24
24
|
this.clients = /* @__PURE__ */ new Map();
|
|
25
|
+
this.pendingClients = /* @__PURE__ */ new Map();
|
|
26
|
+
this.connectionPromises = /* @__PURE__ */ new Map();
|
|
25
27
|
this.messageHandlers = /* @__PURE__ */ new Map();
|
|
28
|
+
this.messageHandlerTokens = /* @__PURE__ */ new Map();
|
|
26
29
|
}
|
|
27
30
|
/**
|
|
28
31
|
* Create an auth provider for the account.
|
|
@@ -34,16 +37,17 @@ var TwitchClientManager = class {
|
|
|
34
37
|
clientId: account.clientId,
|
|
35
38
|
clientSecret: account.clientSecret
|
|
36
39
|
});
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
try {
|
|
41
|
+
const userId = await authProvider.addUserForToken({
|
|
42
|
+
accessToken: normalizedToken,
|
|
43
|
+
refreshToken: account.refreshToken ?? null,
|
|
44
|
+
expiresIn: account.expiresIn ?? null,
|
|
45
|
+
obtainmentTimestamp: account.obtainmentTimestamp ?? Date.now()
|
|
46
|
+
}, TWITCH_CHAT_AUTH_INTENTS);
|
|
43
47
|
this.logger.info(`Added user ${userId} to RefreshingAuthProvider for ${account.username}`);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
}
|
|
48
|
+
} catch (err) {
|
|
49
|
+
throw new Error(`Failed to add user to RefreshingAuthProvider: ${formatErrorMessage(err)}`, { cause: err });
|
|
50
|
+
}
|
|
47
51
|
authProvider.onRefresh((userId, token) => {
|
|
48
52
|
this.logger.info(`Access token refreshed for user ${userId} (expires in ${token.expiresIn ? `${token.expiresIn}s` : "unknown"})`);
|
|
49
53
|
});
|
|
@@ -64,6 +68,17 @@ var TwitchClientManager = class {
|
|
|
64
68
|
const key = this.getAccountKey(account);
|
|
65
69
|
const existing = this.clients.get(key);
|
|
66
70
|
if (existing) return existing;
|
|
71
|
+
const pending = this.connectionPromises.get(key);
|
|
72
|
+
if (pending) return pending;
|
|
73
|
+
const connection = this.createConnectedClient(key, account, cfg, accountId);
|
|
74
|
+
this.connectionPromises.set(key, connection);
|
|
75
|
+
try {
|
|
76
|
+
return await connection;
|
|
77
|
+
} finally {
|
|
78
|
+
if (this.connectionPromises.get(key) === connection) this.connectionPromises.delete(key);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async createConnectedClient(key, account, cfg, accountId) {
|
|
67
82
|
const tokenResolution = resolveTwitchToken(cfg, { accountId });
|
|
68
83
|
if (!tokenResolution.token) {
|
|
69
84
|
this.logger.error(`Missing Twitch token for account ${account.username} (set channels.twitch.accounts.${account.username}.token or OPENCLAW_TWITCH_ACCESS_TOKEN for default)`);
|
|
@@ -107,11 +122,62 @@ var TwitchClientManager = class {
|
|
|
107
122
|
}
|
|
108
123
|
});
|
|
109
124
|
this.setupClientHandlers(client, account);
|
|
110
|
-
|
|
125
|
+
this.pendingClients.set(key, client);
|
|
126
|
+
try {
|
|
127
|
+
await this.connectClient(client, account);
|
|
128
|
+
if (this.pendingClients.get(key) !== client) {
|
|
129
|
+
client.quit();
|
|
130
|
+
throw new Error(`Twitch connection cancelled for ${account.username}`);
|
|
131
|
+
}
|
|
132
|
+
this.pendingClients.delete(key);
|
|
133
|
+
} catch (error) {
|
|
134
|
+
if (this.pendingClients.get(key) === client) this.pendingClients.delete(key);
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
111
137
|
this.clients.set(key, client);
|
|
112
138
|
this.logger.info(`Connected to Twitch as ${account.username}`);
|
|
113
139
|
return client;
|
|
114
140
|
}
|
|
141
|
+
async connectClient(client, account) {
|
|
142
|
+
const connectTimeoutMs = 15e3;
|
|
143
|
+
await new Promise((resolve, reject) => {
|
|
144
|
+
let settled = false;
|
|
145
|
+
let authRetryPending = false;
|
|
146
|
+
const listeners = [];
|
|
147
|
+
let timeout;
|
|
148
|
+
const finish = (error) => {
|
|
149
|
+
if (settled) return;
|
|
150
|
+
settled = true;
|
|
151
|
+
if (timeout) clearTimeout(timeout);
|
|
152
|
+
for (const listener of listeners) listener.unbind();
|
|
153
|
+
if (error) {
|
|
154
|
+
try {
|
|
155
|
+
client.quit();
|
|
156
|
+
} catch {}
|
|
157
|
+
reject(error);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
resolve();
|
|
161
|
+
};
|
|
162
|
+
listeners.push(client.onAuthenticationSuccess(() => finish()), client.onAuthenticationFailure((text) => {
|
|
163
|
+
authRetryPending = true;
|
|
164
|
+
this.logger.warn(`Twitch authentication failed for ${account.username}; waiting for retry, disconnect, or timeout: ${text}`);
|
|
165
|
+
}), client.onDisconnect((manual, reason) => {
|
|
166
|
+
if (authRetryPending && !manual) {
|
|
167
|
+
this.logger.debug?.(`Twitch disconnected during auth retry for ${account.username}: ${formatErrorMessage(reason)}`);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
finish(reason ?? /* @__PURE__ */ new Error(manual ? `Twitch connection cancelled for ${account.username}` : `Twitch disconnected before ready for ${account.username}`));
|
|
171
|
+
}));
|
|
172
|
+
timeout = setTimeout(() => finish(/* @__PURE__ */ new Error(`Timed out connecting to Twitch as ${account.username}`)), connectTimeoutMs);
|
|
173
|
+
timeout.unref?.();
|
|
174
|
+
try {
|
|
175
|
+
client.connect();
|
|
176
|
+
} catch (error) {
|
|
177
|
+
finish(error instanceof Error ? error : new Error(String(error)));
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
115
181
|
/**
|
|
116
182
|
* Set up message and event handlers for a client
|
|
117
183
|
*/
|
|
@@ -148,21 +214,37 @@ var TwitchClientManager = class {
|
|
|
148
214
|
*/
|
|
149
215
|
onMessage(account, handler) {
|
|
150
216
|
const key = this.getAccountKey(account);
|
|
217
|
+
const token = Symbol(key);
|
|
151
218
|
this.messageHandlers.set(key, handler);
|
|
219
|
+
this.messageHandlerTokens.set(key, token);
|
|
152
220
|
return () => {
|
|
153
|
-
this.
|
|
221
|
+
if (this.messageHandlerTokens.get(key) === token) {
|
|
222
|
+
this.messageHandlers.delete(key);
|
|
223
|
+
this.messageHandlerTokens.delete(key);
|
|
224
|
+
}
|
|
154
225
|
};
|
|
155
226
|
}
|
|
227
|
+
clearMessageHandler(key) {
|
|
228
|
+
this.messageHandlers.delete(key);
|
|
229
|
+
this.messageHandlerTokens.delete(key);
|
|
230
|
+
}
|
|
156
231
|
/**
|
|
157
232
|
* Disconnect a client
|
|
158
233
|
*/
|
|
159
234
|
async disconnect(account) {
|
|
160
235
|
const key = this.getAccountKey(account);
|
|
161
236
|
const client = this.clients.get(key);
|
|
237
|
+
const pendingClient = this.pendingClients.get(key);
|
|
238
|
+
if (pendingClient) {
|
|
239
|
+
pendingClient.quit();
|
|
240
|
+
this.pendingClients.delete(key);
|
|
241
|
+
this.connectionPromises.delete(key);
|
|
242
|
+
this.clearMessageHandler(key);
|
|
243
|
+
}
|
|
162
244
|
if (client) {
|
|
163
245
|
client.quit();
|
|
164
246
|
this.clients.delete(key);
|
|
165
|
-
this.
|
|
247
|
+
this.clearMessageHandler(key);
|
|
166
248
|
this.logger.info(`Disconnected ${key}`);
|
|
167
249
|
}
|
|
168
250
|
}
|
|
@@ -170,9 +252,13 @@ var TwitchClientManager = class {
|
|
|
170
252
|
* Disconnect all clients
|
|
171
253
|
*/
|
|
172
254
|
async disconnectAll() {
|
|
255
|
+
this.pendingClients.forEach((client) => client.quit());
|
|
173
256
|
this.clients.forEach((client) => client.quit());
|
|
257
|
+
this.pendingClients.clear();
|
|
258
|
+
this.connectionPromises.clear();
|
|
174
259
|
this.clients.clear();
|
|
175
260
|
this.messageHandlers.clear();
|
|
261
|
+
this.messageHandlerTokens.clear();
|
|
176
262
|
this.logger.info(" Disconnected all clients");
|
|
177
263
|
}
|
|
178
264
|
/**
|
|
@@ -206,6 +292,8 @@ var TwitchClientManager = class {
|
|
|
206
292
|
*/
|
|
207
293
|
clearForTest() {
|
|
208
294
|
this.clients.clear();
|
|
295
|
+
this.pendingClients.clear();
|
|
296
|
+
this.connectionPromises.clear();
|
|
209
297
|
this.messageHandlers.clear();
|
|
210
298
|
}
|
|
211
299
|
};
|
|
@@ -260,9 +348,12 @@ function getClientManager(accountId) {
|
|
|
260
348
|
async function removeClientManager(accountId) {
|
|
261
349
|
const entry = registry.get(accountId);
|
|
262
350
|
if (!entry) return;
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
351
|
+
try {
|
|
352
|
+
await entry.manager.disconnectAll();
|
|
353
|
+
} finally {
|
|
354
|
+
registry.delete(accountId);
|
|
355
|
+
entry.logger.info(`Unregistered client manager for account: ${accountId}`);
|
|
356
|
+
}
|
|
266
357
|
}
|
|
267
358
|
//#endregion
|
|
268
359
|
//#region extensions/twitch/src/utils/markdown.ts
|
|
@@ -1257,7 +1348,7 @@ const twitchPlugin = createChatChannelPlugin({
|
|
|
1257
1348
|
await runStoppablePassiveMonitor({
|
|
1258
1349
|
abortSignal: ctx.abortSignal,
|
|
1259
1350
|
start: async () => {
|
|
1260
|
-
const { monitorTwitchProvider } = await import("./monitor-
|
|
1351
|
+
const { monitorTwitchProvider } = await import("./monitor-ClcKuSAz.js");
|
|
1261
1352
|
return monitorTwitchProvider({
|
|
1262
1353
|
account,
|
|
1263
1354
|
accountId,
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/twitch",
|
|
3
|
-
"version": "2026.5.
|
|
3
|
+
"version": "2026.5.24-beta.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@openclaw/twitch",
|
|
9
|
-
"version": "2026.5.
|
|
9
|
+
"version": "2026.5.24-beta.1",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@twurple/api": "8.1.4",
|
|
12
12
|
"@twurple/auth": "8.1.4",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/twitch",
|
|
3
|
-
"version": "2026.5.
|
|
3
|
+
"version": "2026.5.24-beta.1",
|
|
4
4
|
"description": "OpenClaw Twitch channel plugin",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -24,10 +24,10 @@
|
|
|
24
24
|
"minHostVersion": ">=2026.4.10"
|
|
25
25
|
},
|
|
26
26
|
"compat": {
|
|
27
|
-
"pluginApi": ">=2026.5.
|
|
27
|
+
"pluginApi": ">=2026.5.24-beta.1"
|
|
28
28
|
},
|
|
29
29
|
"build": {
|
|
30
|
-
"openclawVersion": "2026.5.
|
|
30
|
+
"openclawVersion": "2026.5.24-beta.1"
|
|
31
31
|
},
|
|
32
32
|
"channel": {
|
|
33
33
|
"id": "twitch",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"README.md"
|
|
56
56
|
],
|
|
57
57
|
"peerDependencies": {
|
|
58
|
-
"openclaw": ">=2026.5.
|
|
58
|
+
"openclaw": ">=2026.5.24-beta.1"
|
|
59
59
|
},
|
|
60
60
|
"peerDependenciesMeta": {
|
|
61
61
|
"openclaw": {
|