@my-life-buddies/cli 0.22.0 → 0.23.0-dev.2
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 +98 -66
- package/dist/bin/buddy.js +89 -70
- package/dist/bin/buddy.js.map +4 -4
- package/dist/bin/core.js +631 -477
- package/dist/bin/core.js.map +4 -4
- package/dist/bin/preview.js +290 -55
- package/dist/bin/preview.js.map +4 -4
- package/dist/web/app.js +2 -2
- package/dist/web/widget-document.js +41 -0
- package/dist/web/widgets.js +88 -54
- package/package.json +8 -7
package/dist/bin/preview.js
CHANGED
|
@@ -269,7 +269,10 @@ var PreviewController = class {
|
|
|
269
269
|
signal: controller.signal,
|
|
270
270
|
onLifecycle: ({ phase, error }) => {
|
|
271
271
|
if (controller.signal.aborted || !this.#handle || this.#state.phase === "stopping") return;
|
|
272
|
-
if (phase === "reloading")
|
|
272
|
+
if (phase === "reloading") {
|
|
273
|
+
this.#stopOutputWatchers();
|
|
274
|
+
void this.#simulator?.resetWidgets?.();
|
|
275
|
+
}
|
|
273
276
|
this.#record({ channel: "dev", type: `dev.${phase}`, summary: phase === "reloading" ? "\u672C\u5730\u6587\u4EF6\u5DF2\u4FEE\u6539\uFF0C\u6B63\u5728\u91CD\u8F7D\uFF1B\u5F53\u524D\u56DE\u590D\u53EF\u80FD\u4E2D\u65AD" : phase === "reload_failed" ? error?.message ?? "\u91CD\u8F7D\u5931\u8D25\uFF0C\u4FEE\u6B63\u6587\u4EF6\u540E\u81EA\u52A8\u91CD\u8BD5" : "\u91CD\u8F7D\u5B8C\u6210\uFF0CDEV \u5DF2\u5C31\u7EEA" });
|
|
274
277
|
this.#setState({ phase, ready: this.#state.ready, ...error ? { error } : {} });
|
|
275
278
|
},
|
|
@@ -294,6 +297,7 @@ var PreviewController = class {
|
|
|
294
297
|
() => {
|
|
295
298
|
if (this.#handle === handle) {
|
|
296
299
|
this.#handle = void 0;
|
|
300
|
+
void this.#simulator?.disposeWidgets?.();
|
|
297
301
|
this.#simulator = void 0;
|
|
298
302
|
this.#stopOutputWatchers();
|
|
299
303
|
this.#record({ channel: "dev", type: "dev.closed", summary: "DEV \u4F1A\u8BDD\u5DF2\u7ED3\u675F" });
|
|
@@ -303,6 +307,7 @@ var PreviewController = class {
|
|
|
303
307
|
(cause) => {
|
|
304
308
|
if (this.#handle === handle) {
|
|
305
309
|
this.#handle = void 0;
|
|
310
|
+
void this.#simulator?.disposeWidgets?.();
|
|
306
311
|
this.#simulator = void 0;
|
|
307
312
|
this.#stopOutputWatchers();
|
|
308
313
|
this.#record({ channel: "dev", type: "dev.failed", summary: "DEV \u4F1A\u8BDD\u5F02\u5E38\u7ED3\u675F" });
|
|
@@ -340,10 +345,12 @@ var PreviewController = class {
|
|
|
340
345
|
this.#startController?.abort(new Error("Preview requested DEV stop"));
|
|
341
346
|
const handle = this.#handle;
|
|
342
347
|
this.#handle = void 0;
|
|
348
|
+
const widgetCleanup = this.#simulator?.disposeWidgets?.();
|
|
343
349
|
this.#simulator = void 0;
|
|
344
350
|
this.#stopOutputWatchers();
|
|
345
351
|
try {
|
|
346
352
|
if (handle) await handle.close();
|
|
353
|
+
await widgetCleanup;
|
|
347
354
|
if (this.#startPromise) await this.#startPromise.catch(() => void 0);
|
|
348
355
|
this.#record({ channel: "dev", type: "dev.stopped", summary: "DEV \u4F1A\u8BDD\u5DF2\u505C\u6B62" });
|
|
349
356
|
return this.#setState({ phase: "idle" });
|
|
@@ -374,7 +381,7 @@ var PreviewController = class {
|
|
|
374
381
|
const client = this.#requiredSimulator();
|
|
375
382
|
const revision = this.#conversationRevision;
|
|
376
383
|
let result;
|
|
377
|
-
if (action === "list" && client.widgets) result = { widgets: await client.widgets(), conversationRevision: revision, capabilities: { documentLifecycle: true,
|
|
384
|
+
if (action === "list" && client.widgets) result = { widgets: await client.widgets(), conversationRevision: revision, capabilities: { documentLifecycle: true, widgetEditing: Boolean(client.widgetCommand) } };
|
|
378
385
|
else if (action === "html" && client.widgetHtml) {
|
|
379
386
|
let target;
|
|
380
387
|
if (messageId !== void 0) {
|
|
@@ -385,9 +392,6 @@ var PreviewController = class {
|
|
|
385
392
|
}
|
|
386
393
|
if (this.#resetting || revision !== this.#conversationRevision || client !== this.#simulator) throw Object.assign(new Error("\u4F1A\u8BDD\u5DF2\u5207\u6362\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00\u5C0F\u6302\u4EF6"), { status: 409 });
|
|
387
394
|
result = await client.widgetHtml(id, target);
|
|
388
|
-
} else if (action === "delete" && client.deleteWidget) {
|
|
389
|
-
await client.deleteWidget(id);
|
|
390
|
-
result = { ok: true };
|
|
391
395
|
} else if (action === "image" && client.widgetImage) result = await client.widgetImage(id, mediaId);
|
|
392
396
|
else if ((action === "pin" || action === "unpin") && client.pinWidget) {
|
|
393
397
|
await client.pinWidget(id, action === "pin");
|
|
@@ -396,6 +400,17 @@ var PreviewController = class {
|
|
|
396
400
|
if (this.#resetting || revision !== this.#conversationRevision || client !== this.#simulator) throw Object.assign(new Error("\u4F1A\u8BDD\u5DF2\u5207\u6362\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00\u5C0F\u6302\u4EF6"), { status: 409 });
|
|
397
401
|
return result;
|
|
398
402
|
}
|
|
403
|
+
async simulatorWidgetCommand(id, body) {
|
|
404
|
+
if (this.#resetting) throw Object.assign(new Error("\u4F1A\u8BDD\u6B63\u5728\u91CD\u7F6E"), { status: 409 });
|
|
405
|
+
const client = this.#requiredSimulator(), revision = this.#conversationRevision;
|
|
406
|
+
if (!body || typeof body !== "object" || body.conversationRevision !== revision)
|
|
407
|
+
throw Object.assign(new Error("\u4F1A\u8BDD\u5DF2\u5207\u6362\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00\u5C0F\u6302\u4EF6"), { status: 409 });
|
|
408
|
+
if (!client.widgetCommand) throw Object.assign(new Error("\u5F53\u524D\u6A21\u62DF\u5668\u4E0D\u652F\u6301 Widget \u7F16\u8F91"), { status: 501 });
|
|
409
|
+
const result = await client.widgetCommand(id, body);
|
|
410
|
+
if (this.#resetting || client !== this.#simulator || revision !== this.#conversationRevision)
|
|
411
|
+
throw Object.assign(new Error("\u4F1A\u8BDD\u5DF2\u5207\u6362\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00\u5C0F\u6302\u4EF6"), { status: 409 });
|
|
412
|
+
return result;
|
|
413
|
+
}
|
|
399
414
|
async resetSimulator() {
|
|
400
415
|
if (this.#resetting || this.#sendingMessage || this.#simulatorState.activeRun) {
|
|
401
416
|
throw Object.assign(new Error("\u8BF7\u7B49\u5F85\u5F53\u524D\u56DE\u590D\u6216\u64CD\u4F5C\u5B8C\u6210\u540E\u518D\u6E05\u7A7A"), { status: 409, code: "SIMULATOR_BUSY" });
|
|
@@ -915,10 +930,10 @@ function safeEqual(left, right) {
|
|
|
915
930
|
return a.length === b.length && timingSafeEqual(a, b);
|
|
916
931
|
}
|
|
917
932
|
function cookieValue(request, name) {
|
|
918
|
-
for (const
|
|
919
|
-
const separator =
|
|
933
|
+
for (const part2 of request.headers.cookie?.split(";") ?? []) {
|
|
934
|
+
const separator = part2.indexOf("=");
|
|
920
935
|
if (separator < 0) continue;
|
|
921
|
-
if (
|
|
936
|
+
if (part2.slice(0, separator).trim() === name) return part2.slice(separator + 1).trim();
|
|
922
937
|
}
|
|
923
938
|
return void 0;
|
|
924
939
|
}
|
|
@@ -952,9 +967,9 @@ function widgetDocument(response, html, render, state = "ready", status = 200) {
|
|
|
952
967
|
response.setHeader("Content-Security-Policy", "sandbox allow-scripts; default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:; connect-src 'none'; frame-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'self'");
|
|
953
968
|
response.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
954
969
|
if (validWidgetRender(render)) {
|
|
955
|
-
const
|
|
956
|
-
|
|
957
|
-
|
|
970
|
+
const script = state === "ready" ? readFileSync(new URL("../web/widget-document.js", import.meta.url), "utf8").replace("__WIDGET_RENDER__", JSON.stringify(render)) : `window.addEventListener('load',()=>parent.postMessage({channel:'buddy-widget-document',render:${JSON.stringify(render)},state:'error'},'*'),{once:true})`;
|
|
971
|
+
const signal = `<script>${script}</script>`;
|
|
972
|
+
if (/<!doctype[^>]*>/iu.test(html)) html = html.replace(/<!doctype[^>]*>/iu, (match) => match + signal);
|
|
958
973
|
else html = signal + html;
|
|
959
974
|
}
|
|
960
975
|
response.end(html);
|
|
@@ -989,9 +1004,9 @@ async function readJsonBody(request, maximumBytes = 64 * 1024) {
|
|
|
989
1004
|
}
|
|
990
1005
|
function exactObject(value, keys) {
|
|
991
1006
|
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
992
|
-
const
|
|
1007
|
+
const object3 = value;
|
|
993
1008
|
const expected = new Set(keys);
|
|
994
|
-
return Object.keys(
|
|
1009
|
+
return Object.keys(object3).length === keys.length && Object.keys(object3).every((key) => expected.has(key)) ? object3 : null;
|
|
995
1010
|
}
|
|
996
1011
|
function safeIdentifier(value, maximumLength = 200) {
|
|
997
1012
|
return typeof value === "string" && value.length > 0 && value.length <= maximumLength && !/[\u0000-\u001f\u007f]/u.test(value);
|
|
@@ -1105,12 +1120,15 @@ data: ${JSON.stringify(value)}
|
|
|
1105
1120
|
return apiError(response, failure.status, failure.code, failure.message);
|
|
1106
1121
|
}
|
|
1107
1122
|
}
|
|
1108
|
-
const widgetRoute = /^\/api\/simulator\/widgets(?:\/([^/]+)(?:\/(image|pin))?)?$/u.exec(url.pathname);
|
|
1123
|
+
const widgetRoute = /^\/api\/simulator\/widgets(?:\/([^/]+)(?:\/(image|pin|bridge))?)?$/u.exec(url.pathname);
|
|
1109
1124
|
if (widgetRoute) {
|
|
1110
1125
|
try {
|
|
1111
1126
|
const id = widgetRoute[1] ? decodeURIComponent(widgetRoute[1]) : "";
|
|
1112
1127
|
const suffix = widgetRoute[2];
|
|
1113
|
-
if (id &&
|
|
1128
|
+
if (id && suffix === "bridge" && request.method === "POST") {
|
|
1129
|
+
const body = await readJsonBody(request, 7 * 1024 * 1024);
|
|
1130
|
+
return json(response, 200, await options.controller.simulatorWidgetCommand(id, body));
|
|
1131
|
+
}
|
|
1114
1132
|
if (request.method === "GET" && !id) return json(response, 200, await options.controller.simulatorWidgets("list"));
|
|
1115
1133
|
if (id && suffix === "pin" && (request.method === "PUT" || request.method === "DELETE")) return json(response, 200, await options.controller.simulatorWidgets(request.method === "PUT" ? "pin" : "unpin", id));
|
|
1116
1134
|
if (id && request.method === "GET" && suffix === "image") {
|
|
@@ -1269,17 +1287,204 @@ data: ${JSON.stringify({
|
|
|
1269
1287
|
}
|
|
1270
1288
|
|
|
1271
1289
|
// apps/preview/src/mlb-simulator.ts
|
|
1272
|
-
import { createHash } from "node:crypto";
|
|
1290
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
1291
|
+
|
|
1292
|
+
// apps/preview/src/widget-bridge.ts
|
|
1293
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
1294
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
1295
|
+
function widgetBridgeError(status, message2) {
|
|
1296
|
+
return Object.assign(new Error(message2), { status, publicMessage: message2, code: "WIDGET_BRIDGE_FAILED" });
|
|
1297
|
+
}
|
|
1298
|
+
function object(value) {
|
|
1299
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw widgetBridgeError(400, "\u9700\u8981\u6709\u6548\u7684\u64CD\u4F5C\u53C2\u6570");
|
|
1300
|
+
return value;
|
|
1301
|
+
}
|
|
1302
|
+
function part(value) {
|
|
1303
|
+
if (typeof value !== "string" || !value.trim() || value.length > 64 || /[\s/\\\u0000-\u001f\u007f]/u.test(value) || value === "." || value === "..") throw widgetBridgeError(400, "\u6A21\u5757\u6216\u8BB0\u5F55\u6807\u8BC6\u65E0\u6548");
|
|
1304
|
+
return encodeURIComponent(value);
|
|
1305
|
+
}
|
|
1306
|
+
function retryable(error) {
|
|
1307
|
+
const status = error?.status;
|
|
1308
|
+
return status === void 0 || status >= 500 || status === 429;
|
|
1309
|
+
}
|
|
1310
|
+
async function retry(run) {
|
|
1311
|
+
for (let attempt = 0; ; attempt++) {
|
|
1312
|
+
try {
|
|
1313
|
+
return await run();
|
|
1314
|
+
} catch (error) {
|
|
1315
|
+
if (attempt >= 2 || !retryable(error)) throw error;
|
|
1316
|
+
await delay(100 * (attempt + 1));
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
var WidgetBridge = class {
|
|
1321
|
+
#sessions = /* @__PURE__ */ new Map();
|
|
1322
|
+
#transport;
|
|
1323
|
+
#timer;
|
|
1324
|
+
#disposed = false;
|
|
1325
|
+
constructor(transport) {
|
|
1326
|
+
this.#transport = transport;
|
|
1327
|
+
this.#timer = setInterval(() => {
|
|
1328
|
+
for (const [key, session] of this.#sessions) if (Date.now() - session.touched > 9e4) {
|
|
1329
|
+
this.#sessions.delete(key);
|
|
1330
|
+
void this.#close(session, "page_lost").catch(() => {
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
}, 15e3);
|
|
1334
|
+
this.#timer.unref();
|
|
1335
|
+
}
|
|
1336
|
+
async call(widgetId2, conversationId, value) {
|
|
1337
|
+
if (this.#disposed) throw widgetBridgeError(409, "\u8C03\u8BD5\u5DF2\u7ED3\u675F\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00\u5C0F\u6302\u4EF6");
|
|
1338
|
+
const body = object(value), render = body.render, method = body.method, requestId = body.requestId;
|
|
1339
|
+
if (typeof render !== "string" || !/^[a-zA-Z0-9-]{16,64}$/u.test(render) || typeof requestId !== "string" || !/^[a-zA-Z0-9-]{16,64}$/u.test(requestId) || typeof method !== "string") throw widgetBridgeError(400, "\u5C0F\u6302\u4EF6\u4EA4\u4E92\u6807\u8BC6\u65E0\u6548");
|
|
1340
|
+
part(widgetId2);
|
|
1341
|
+
let session = this.#sessions.get(render);
|
|
1342
|
+
if (session && (session.widgetId !== widgetId2 || session.conversationId !== conversationId)) throw widgetBridgeError(409, "\u5C0F\u6302\u4EF6\u4F1A\u8BDD\u5DF2\u5207\u6362\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00");
|
|
1343
|
+
if (method === "open") {
|
|
1344
|
+
if (session?.closed) throw widgetBridgeError(409, "\u5C0F\u6302\u4EF6\u5DF2\u5173\u95ED\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00");
|
|
1345
|
+
if (!session) {
|
|
1346
|
+
if (this.#sessions.size >= 32) throw widgetBridgeError(429, "\u6253\u5F00\u7684\u5C0F\u6302\u4EF6\u8FC7\u591A\uFF0C\u8BF7\u5173\u95ED\u540E\u91CD\u8BD5");
|
|
1347
|
+
session = {
|
|
1348
|
+
widgetId: widgetId2,
|
|
1349
|
+
conversationId,
|
|
1350
|
+
interactionId: `wi_${randomUUID()}`,
|
|
1351
|
+
deviceId: `preview_${randomUUID()}`,
|
|
1352
|
+
sequence: 0,
|
|
1353
|
+
touched: Date.now(),
|
|
1354
|
+
closed: false,
|
|
1355
|
+
pending: 0,
|
|
1356
|
+
tail: Promise.resolve(),
|
|
1357
|
+
calls: /* @__PURE__ */ new Map()
|
|
1358
|
+
};
|
|
1359
|
+
this.#sessions.set(render, session);
|
|
1360
|
+
session.tail = this.#transport.request("GET", `/app/widgets/${encodeURIComponent(widgetId2)}/metadata?conversationId=${encodeURIComponent(conversationId)}`).catch((error) => {
|
|
1361
|
+
session.closed = true;
|
|
1362
|
+
throw error;
|
|
1363
|
+
});
|
|
1364
|
+
void session.tail.catch(() => {
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1367
|
+
await session.tail;
|
|
1368
|
+
if (this.#disposed || session.closed) throw widgetBridgeError(409, "\u8C03\u8BD5\u5DF2\u7ED3\u675F\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00\u5C0F\u6302\u4EF6");
|
|
1369
|
+
return { ready: true };
|
|
1370
|
+
}
|
|
1371
|
+
if (!session) throw widgetBridgeError(409, "\u5C0F\u6302\u4EF6\u8FDE\u63A5\u5DF2\u5931\u6548\uFF0C\u8BF7\u5173\u95ED\u540E\u91CD\u65B0\u6253\u5F00");
|
|
1372
|
+
session.touched = Date.now();
|
|
1373
|
+
const serialized = JSON.stringify([method, body.params ?? {}]);
|
|
1374
|
+
if (method !== "media.upload" && Buffer.byteLength(serialized) > 300 * 1024) throw widgetBridgeError(413, "\u64CD\u4F5C\u53C2\u6570\u8FC7\u5927");
|
|
1375
|
+
const signature = createHash("sha256").update(serialized).digest("hex");
|
|
1376
|
+
const previous = session.calls.get(requestId);
|
|
1377
|
+
if (previous) {
|
|
1378
|
+
if (previous.signature !== signature) throw widgetBridgeError(409, "\u4E0D\u80FD\u4F7F\u7528\u540C\u4E00\u8BF7\u6C42\u6807\u8BC6\u6267\u884C\u4E0D\u540C\u64CD\u4F5C");
|
|
1379
|
+
return previous.result;
|
|
1380
|
+
}
|
|
1381
|
+
if (method === "close") return this.#close(session, "user_closed");
|
|
1382
|
+
if (session.closed) throw widgetBridgeError(409, "\u5C0F\u6302\u4EF6\u5DF2\u5173\u95ED\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00");
|
|
1383
|
+
if (session.calls.size >= 256) throw widgetBridgeError(429, "\u672C\u6B21\u7F16\u8F91\u64CD\u4F5C\u8F83\u591A\uFF0C\u8BF7\u5173\u95ED\u540E\u91CD\u65B0\u6253\u5F00");
|
|
1384
|
+
if (session.pending >= 16) throw widgetBridgeError(429, "\u64CD\u4F5C\u8FC7\u4E8E\u9891\u7E41\uFF0C\u8BF7\u7B49\u5F85\u5F53\u524D\u4FDD\u5B58\u5B8C\u6210");
|
|
1385
|
+
const current = session;
|
|
1386
|
+
current.pending++;
|
|
1387
|
+
const result = current.tail.then(() => {
|
|
1388
|
+
if (current.closed || this.#disposed) throw widgetBridgeError(409, "\u5C0F\u6302\u4EF6\u5DF2\u5173\u95ED\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00");
|
|
1389
|
+
return this.#perform(current, method, object(body.params ?? {}), requestId);
|
|
1390
|
+
});
|
|
1391
|
+
current.tail = result.then(() => {
|
|
1392
|
+
current.pending--;
|
|
1393
|
+
}, () => {
|
|
1394
|
+
current.pending--;
|
|
1395
|
+
});
|
|
1396
|
+
if (["records.create", "records.update", "records.delete", "media.upload"].includes(method)) current.calls.set(requestId, { signature, result });
|
|
1397
|
+
return result;
|
|
1398
|
+
}
|
|
1399
|
+
async #perform(s, method, p, requestId) {
|
|
1400
|
+
const base = `/app/widgets/${encodeURIComponent(s.widgetId)}`;
|
|
1401
|
+
if (method === "heartbeat" || method === "resume") {
|
|
1402
|
+
if (!s.sequence) return { active: true };
|
|
1403
|
+
return this.#transport.command(`widget.interaction.${method}`, { interactionId: s.interactionId, widgetId: s.widgetId, lastAckedSequence: s.sequence });
|
|
1404
|
+
}
|
|
1405
|
+
if (method === "media.url") {
|
|
1406
|
+
if (typeof p.mediaId !== "string" || !/^media_[A-Za-z0-9_-]+$/u.test(p.mediaId)) throw widgetBridgeError(400, "\u56FE\u7247\u6807\u8BC6\u65E0\u6548");
|
|
1407
|
+
const image = await this.#transport.image(s.widgetId, p.mediaId);
|
|
1408
|
+
if (!/^image\/(png|jpeg|webp|gif)(?:;|$)/u.test(image.contentType)) throw widgetBridgeError(415, "\u56FE\u7247\u683C\u5F0F\u4E0D\u652F\u6301");
|
|
1409
|
+
return { url: `data:${image.contentType.split(";")[0]};base64,${Buffer.from(image.body).toString("base64")}` };
|
|
1410
|
+
}
|
|
1411
|
+
if (method === "media.upload") {
|
|
1412
|
+
if (typeof p.mimeType !== "string" || !["image/png", "image/jpeg", "image/webp"].includes(p.mimeType) || typeof p.base64 !== "string" || p.base64.length > 7 * 1024 * 1024 || !/^[A-Za-z0-9+/]+={0,2}$/u.test(p.base64)) throw widgetBridgeError(400, "\u8BF7\u9009\u62E9 JPEG\u3001PNG \u6216 WebP \u56FE\u7247");
|
|
1413
|
+
const bytes = Buffer.from(p.base64, "base64");
|
|
1414
|
+
if (bytes.length > 5 * 1024 * 1024 || !bytes.length) throw widgetBridgeError(413, "\u56FE\u7247\u4E0D\u80FD\u8D85\u8FC7 5 MiB");
|
|
1415
|
+
const result = object(await this.#transport.upload(s.conversationId, p.mimeType, bytes));
|
|
1416
|
+
return { mediaId: result.mediaId, width: result.width, height: result.height, mimeType: result.mimeType, size: result.size };
|
|
1417
|
+
}
|
|
1418
|
+
if (!/^records\.(list|get|create|update|delete)$/u.test(method)) throw widgetBridgeError(400, "\u9875\u9762\u8BF7\u6C42\u4E86\u672A\u5F00\u653E\u7684\u80FD\u529B");
|
|
1419
|
+
const path = `${base}/modules/${part(p.module)}/records`;
|
|
1420
|
+
if (method === "records.list" || method === "records.get") {
|
|
1421
|
+
return this.#transport.request("GET", `${path}${method === "records.get" ? `/${part(p.recordId)}` : ""}?conversationId=${encodeURIComponent(s.conversationId)}`);
|
|
1422
|
+
}
|
|
1423
|
+
if (s.uncertain) throw widgetBridgeError(409, "\u4E0A\u6B21\u4FDD\u5B58\u7ED3\u679C\u5C1A\u672A\u786E\u8BA4\uFF0C\u8BF7\u5173\u95ED\u540E\u91CD\u65B0\u6253\u5F00\u5E76\u6838\u5BF9\u6570\u636E");
|
|
1424
|
+
const action = method.slice(8);
|
|
1425
|
+
const mutation = { conversationId: s.conversationId };
|
|
1426
|
+
if (action !== "delete") mutation.data = object(p.data);
|
|
1427
|
+
if (p.expectedRevision !== void 0) {
|
|
1428
|
+
if (!Number.isSafeInteger(p.expectedRevision) || p.expectedRevision < 0) throw widgetBridgeError(400, "\u8BB0\u5F55\u7248\u672C\u53F7\u65E0\u6548");
|
|
1429
|
+
mutation.expectedRevision = p.expectedRevision;
|
|
1430
|
+
}
|
|
1431
|
+
if (Buffer.byteLength(JSON.stringify(mutation)) > 290 * 1024) throw widgetBridgeError(413, "\u8BB0\u5F55\u5185\u5BB9\u8FC7\u5927");
|
|
1432
|
+
const target = action === "create" ? path : `${path}/${part(p.recordId)}`;
|
|
1433
|
+
const sequence = s.sequence + 1;
|
|
1434
|
+
const headers = {
|
|
1435
|
+
"X-Widget-Interaction-Id": s.interactionId,
|
|
1436
|
+
"X-Widget-Operation-Id": `op_${requestId}`,
|
|
1437
|
+
"X-Widget-Sequence": String(sequence),
|
|
1438
|
+
"X-Widget-Entry-Point": "conversation_detail",
|
|
1439
|
+
"X-Device-Id": s.deviceId
|
|
1440
|
+
};
|
|
1441
|
+
try {
|
|
1442
|
+
const result = await retry(() => this.#transport.request(action === "create" ? "POST" : action === "update" ? "PUT" : "DELETE", target, mutation, headers));
|
|
1443
|
+
s.sequence = sequence;
|
|
1444
|
+
return result;
|
|
1445
|
+
} catch (error) {
|
|
1446
|
+
if (retryable(error)) s.uncertain = requestId;
|
|
1447
|
+
throw error;
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
async #close(s, reason) {
|
|
1451
|
+
if (s.closed) return s.tail;
|
|
1452
|
+
s.closed = true;
|
|
1453
|
+
const pending = s.tail;
|
|
1454
|
+
const closing = pending.catch(() => {
|
|
1455
|
+
}).then(() => retry(() => this.#transport.request(
|
|
1456
|
+
"POST",
|
|
1457
|
+
`/app/widgets/${encodeURIComponent(s.widgetId)}/interactions/${s.interactionId}/close`,
|
|
1458
|
+
{ conversationId: s.conversationId, lastIssuedSequence: s.sequence + (s.uncertain ? 1 : 0), closeReason: reason }
|
|
1459
|
+
)));
|
|
1460
|
+
s.tail = closing;
|
|
1461
|
+
void closing.catch(() => {
|
|
1462
|
+
});
|
|
1463
|
+
return closing;
|
|
1464
|
+
}
|
|
1465
|
+
async closeAll() {
|
|
1466
|
+
const sessions = [...this.#sessions.values()];
|
|
1467
|
+
this.#sessions.clear();
|
|
1468
|
+
await Promise.allSettled(sessions.map((s) => this.#close(s, "preview_stopped")));
|
|
1469
|
+
}
|
|
1470
|
+
async dispose() {
|
|
1471
|
+
this.#disposed = true;
|
|
1472
|
+
clearInterval(this.#timer);
|
|
1473
|
+
await this.closeAll();
|
|
1474
|
+
}
|
|
1475
|
+
};
|
|
1476
|
+
|
|
1477
|
+
// apps/preview/src/mlb-simulator.ts
|
|
1273
1478
|
import { normalizePlatformUrl } from "./core.js";
|
|
1274
1479
|
|
|
1275
1480
|
// apps/preview/src/mlb-events.ts
|
|
1276
|
-
import { randomUUID } from "node:crypto";
|
|
1481
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
1277
1482
|
import { WebSocket } from "ws";
|
|
1278
1483
|
var MlbEventStreamError = class extends Error {
|
|
1279
|
-
constructor(message2, status = 502,
|
|
1484
|
+
constructor(message2, status = 502, retryable2 = true) {
|
|
1280
1485
|
super(message2);
|
|
1281
1486
|
this.status = status;
|
|
1282
|
-
this.retryable =
|
|
1487
|
+
this.retryable = retryable2;
|
|
1283
1488
|
this.publicMessage = message2;
|
|
1284
1489
|
}
|
|
1285
1490
|
status;
|
|
@@ -1308,7 +1513,7 @@ function appSocket(url, token) {
|
|
|
1308
1513
|
}
|
|
1309
1514
|
async function mlbAppCommand(url, token, method, params, options = {}) {
|
|
1310
1515
|
if (options.signal?.aborted) throw options.signal.reason;
|
|
1311
|
-
const requestId =
|
|
1516
|
+
const requestId = randomUUID2();
|
|
1312
1517
|
const socket = appSocket(url, token);
|
|
1313
1518
|
return await new Promise((resolve, reject) => {
|
|
1314
1519
|
let settled = false;
|
|
@@ -1515,11 +1720,11 @@ function safeText(value, name, maximum = 4096) {
|
|
|
1515
1720
|
function endpoint(base, path) {
|
|
1516
1721
|
return new URL(path.replace(/^\//u, ""), base);
|
|
1517
1722
|
}
|
|
1518
|
-
function
|
|
1723
|
+
function object2(value) {
|
|
1519
1724
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
1520
1725
|
}
|
|
1521
1726
|
function httpError(status, value) {
|
|
1522
|
-
const serverMessage =
|
|
1727
|
+
const serverMessage = object2(value)?.error;
|
|
1523
1728
|
const message2 = typeof serverMessage === "string" && serverMessage.trim() ? serverMessage.slice(0, 512) : status >= 500 ? "MLB Server \u6682\u65F6\u4E0D\u53EF\u7528" : "\u6A21\u62DF\u5668\u8BF7\u6C42\u672A\u80FD\u5B8C\u6210";
|
|
1524
1729
|
return Object.assign(new Error(message2), {
|
|
1525
1730
|
code: "MLB_REQUEST_FAILED",
|
|
@@ -1528,7 +1733,7 @@ function httpError(status, value) {
|
|
|
1528
1733
|
});
|
|
1529
1734
|
}
|
|
1530
1735
|
function message(value, index) {
|
|
1531
|
-
const input =
|
|
1736
|
+
const input = object2(value);
|
|
1532
1737
|
if (!input || typeof input.author !== "string") return null;
|
|
1533
1738
|
const role = input.author === "buddy" ? "assistant" : input.author === "user" || input.author === "owner" ? "user" : void 0;
|
|
1534
1739
|
if (!role) return null;
|
|
@@ -1542,7 +1747,7 @@ function message(value, index) {
|
|
|
1542
1747
|
throw new Error(`messages[${index}].timestamp is invalid`);
|
|
1543
1748
|
}
|
|
1544
1749
|
if (role === "assistant" && input.type === "question") {
|
|
1545
|
-
const options =
|
|
1750
|
+
const options = object2(input.question)?.options;
|
|
1546
1751
|
if (!Array.isArray(options) || options.length < 2 || options.length > 4 || options.some((option) => typeof option !== "string" || !option.trim() || option.length > 30)) {
|
|
1547
1752
|
throw new Error(`messages[${index}].question is invalid`);
|
|
1548
1753
|
}
|
|
@@ -1553,7 +1758,7 @@ ${options.map((option, i) => `${i + 1}. ${option}`).join("\n")}
|
|
|
1553
1758
|
\u8BF7\u76F4\u63A5\u56DE\u590D\u4F60\u7684\u9009\u62E9\uFF0C\u4E5F\u53EF\u4EE5\u8865\u5145\u8BF4\u660E\u3002`;
|
|
1554
1759
|
}
|
|
1555
1760
|
if (role === "assistant" && input.type === "permissionRequest") {
|
|
1556
|
-
const request =
|
|
1761
|
+
const request = object2(input.permissionRequest);
|
|
1557
1762
|
text2 += request?.status === "pending" ? "\n\n\u8BF7\u5728\u642D\u642D App \u4E2D\u5904\u7406\u8FD9\u5F20\u6388\u6743\u5361\uFF1B\u6D4F\u89C8\u5668\u9884\u89C8\u4E0D\u4EE3\u66FF\u7CFB\u7EDF\u6388\u6743\u3002" : "\n\n\u6388\u6743\u5361\u7684\u5904\u7406\u7ED3\u679C\u8BF7\u4EE5\u642D\u642D App \u4E2D\u7684\u72B6\u6001\u4E3A\u51C6\u3002";
|
|
1558
1763
|
}
|
|
1559
1764
|
return Object.freeze({
|
|
@@ -1561,7 +1766,7 @@ ${options.map((option, i) => `${i + 1}. ${option}`).join("\n")}
|
|
|
1561
1766
|
role,
|
|
1562
1767
|
text: text2,
|
|
1563
1768
|
createdAt: new Date(input.timestamp).toISOString(),
|
|
1564
|
-
...input.type === "widget" &&
|
|
1769
|
+
...input.type === "widget" && object2(input.widget) ? { widget: parseWidget(input.widget) } : {}
|
|
1565
1770
|
});
|
|
1566
1771
|
}
|
|
1567
1772
|
function widgetId(value) {
|
|
@@ -1570,7 +1775,7 @@ function widgetId(value) {
|
|
|
1570
1775
|
return id;
|
|
1571
1776
|
}
|
|
1572
1777
|
function parseWidget(value) {
|
|
1573
|
-
const item =
|
|
1778
|
+
const item = object2(value);
|
|
1574
1779
|
const openTarget = parseOpenTarget(item?.openTarget);
|
|
1575
1780
|
return Object.freeze({
|
|
1576
1781
|
id: widgetId(item?.id),
|
|
@@ -1586,20 +1791,20 @@ function parseWidget(value) {
|
|
|
1586
1791
|
}
|
|
1587
1792
|
function parseOpenTarget(value) {
|
|
1588
1793
|
if (value === void 0) return void 0;
|
|
1589
|
-
const target =
|
|
1794
|
+
const target = object2(value), resource = object2(target?.resource);
|
|
1590
1795
|
const name = (input) => typeof input === "string" && /^[^\s/\u0000-\u001f\u007f]{1,64}$/u.test(input);
|
|
1591
|
-
if (!target || target.version !== 1 || !name(target.entry) || target.resource !== void 0 && (!resource || !name(resource.module) || !name(resource.recordId)) || target.state !== void 0 && !
|
|
1796
|
+
if (!target || target.version !== 1 || !name(target.entry) || target.resource !== void 0 && (!resource || !name(resource.module) || !name(resource.recordId)) || target.state !== void 0 && !object2(target.state)) throw new TypeError("Widget \u6253\u5F00\u76EE\u6807\u683C\u5F0F\u65E0\u6548");
|
|
1592
1797
|
const normalized = {
|
|
1593
1798
|
version: 1,
|
|
1594
1799
|
entry: target.entry,
|
|
1595
1800
|
...resource ? { resource: { module: resource.module, recordId: resource.recordId } } : {},
|
|
1596
|
-
...target.state !== void 0 ? { state:
|
|
1801
|
+
...target.state !== void 0 ? { state: object2(target.state) } : {}
|
|
1597
1802
|
};
|
|
1598
1803
|
if (Buffer.byteLength(JSON.stringify(normalized), "utf8") > 8 * 1024) throw new TypeError("Widget \u6253\u5F00\u76EE\u6807\u8D85\u8FC7 8 KiB");
|
|
1599
1804
|
return Object.freeze(normalized);
|
|
1600
1805
|
}
|
|
1601
1806
|
function localReceipt(prefix, value) {
|
|
1602
|
-
return `${prefix}-${
|
|
1807
|
+
return `${prefix}-${createHash2("sha256").update(value).digest("hex").slice(0, 20)}`;
|
|
1603
1808
|
}
|
|
1604
1809
|
function waitForRetry(milliseconds, signal) {
|
|
1605
1810
|
if (signal?.aborted) return Promise.reject(signal.reason);
|
|
@@ -1620,6 +1825,7 @@ var MlbSimulatorClient = class {
|
|
|
1620
1825
|
#buddyId;
|
|
1621
1826
|
#accessToken;
|
|
1622
1827
|
#fetch;
|
|
1828
|
+
#widgetBridge;
|
|
1623
1829
|
#sending = false;
|
|
1624
1830
|
#resetTarget;
|
|
1625
1831
|
#resetArchived = false;
|
|
@@ -1639,6 +1845,28 @@ var MlbSimulatorClient = class {
|
|
|
1639
1845
|
this.#accessToken = safeText(options.auth.developerAccessToken, "developerAccessToken", 16384);
|
|
1640
1846
|
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
1641
1847
|
if (typeof this.#fetch !== "function") throw new TypeError("A Fetch API implementation is required");
|
|
1848
|
+
this.#widgetBridge = new WidgetBridge({
|
|
1849
|
+
request: (method, path, body, headers) => this.#request(method, path, { body, headers }),
|
|
1850
|
+
command: (method, params) => {
|
|
1851
|
+
const url = endpoint(this.#baseUrl, "/app/ws");
|
|
1852
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
1853
|
+
return mlbAppCommand(url, this.#accessToken, method, params);
|
|
1854
|
+
},
|
|
1855
|
+
image: (id, mediaId) => this.widgetImage(id, mediaId),
|
|
1856
|
+
upload: async (conversationId, mime, bytes) => {
|
|
1857
|
+
const response = await this.#fetch(endpoint(this.#baseUrl, `/app/media/uploads?conversationId=${encodeURIComponent(conversationId)}`), {
|
|
1858
|
+
method: "POST",
|
|
1859
|
+
headers: { Authorization: `Bearer ${this.#accessToken}`, "Content-Type": mime },
|
|
1860
|
+
body: new Uint8Array(bytes),
|
|
1861
|
+
redirect: "error",
|
|
1862
|
+
credentials: "omit",
|
|
1863
|
+
signal: AbortSignal.timeout(25e3)
|
|
1864
|
+
});
|
|
1865
|
+
const value = await response.json().catch(() => void 0);
|
|
1866
|
+
if (!response.ok) throw httpError(response.status, value);
|
|
1867
|
+
return value;
|
|
1868
|
+
}
|
|
1869
|
+
});
|
|
1642
1870
|
}
|
|
1643
1871
|
/** Reading a profile must not add the Buddy or trigger a welcome message. */
|
|
1644
1872
|
async details(options = {}) {
|
|
@@ -1661,7 +1889,7 @@ var MlbSimulatorClient = class {
|
|
|
1661
1889
|
async widgets() {
|
|
1662
1890
|
this.#assertResetComplete();
|
|
1663
1891
|
const id = await this.#ensurePrivate();
|
|
1664
|
-
const body =
|
|
1892
|
+
const body = object2(await this.#request("GET", `/app/privates/${encodeURIComponent(id)}/widgets`, {}));
|
|
1665
1893
|
if (!Array.isArray(body?.widgets)) throw httpError(502, { error: "\u65E0\u6CD5\u8BFB\u53D6\u4F1A\u8BDD\u5C0F\u6302\u4EF6" });
|
|
1666
1894
|
return body.widgets.map(parseWidget).filter((item) => item.type !== void 0);
|
|
1667
1895
|
}
|
|
@@ -1680,15 +1908,20 @@ var MlbSimulatorClient = class {
|
|
|
1680
1908
|
const privateId = await this.#ensurePrivate();
|
|
1681
1909
|
await this.#request(pinned ? "PUT" : "DELETE", `/app/widgets/${encodeURIComponent(widgetId(id))}/pin`, { ...pinned ? { body: { sourceConversationId: privateId } } : {} });
|
|
1682
1910
|
}
|
|
1683
|
-
async
|
|
1911
|
+
async widgetCommand(id, body) {
|
|
1684
1912
|
this.#assertResetComplete();
|
|
1685
|
-
|
|
1686
|
-
|
|
1913
|
+
return this.#widgetBridge.call(widgetId(id), await this.#ensurePrivate(), body);
|
|
1914
|
+
}
|
|
1915
|
+
async disposeWidgets() {
|
|
1916
|
+
await this.#widgetBridge.dispose();
|
|
1917
|
+
}
|
|
1918
|
+
async resetWidgets() {
|
|
1919
|
+
await this.#widgetBridge.closeAll();
|
|
1687
1920
|
}
|
|
1688
1921
|
async widgetImage(id, mediaId) {
|
|
1689
1922
|
this.#assertResetComplete();
|
|
1690
1923
|
if (!/^media_[a-zA-Z0-9_-]+$/u.test(mediaId)) throw new TypeError("Invalid media ID");
|
|
1691
|
-
const access =
|
|
1924
|
+
const access = object2(await this.#request("GET", `/app/media/${encodeURIComponent(mediaId)}?widgetId=${encodeURIComponent(widgetId(id))}`, {}));
|
|
1692
1925
|
const url = new URL(String(access?.url));
|
|
1693
1926
|
if (url.protocol !== "https:" || url.username || url.password || access?.mediaId !== mediaId || typeof access.expiresAt !== "number" || access.expiresAt <= Date.now()) throw new Error("\u56FE\u7247\u94FE\u63A5\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u8BD5");
|
|
1694
1927
|
return this.#widgetResource(url.href, false);
|
|
@@ -1705,11 +1938,11 @@ var MlbSimulatorClient = class {
|
|
|
1705
1938
|
let size = 0;
|
|
1706
1939
|
try {
|
|
1707
1940
|
while (true) {
|
|
1708
|
-
const
|
|
1709
|
-
if (
|
|
1710
|
-
size +=
|
|
1941
|
+
const part2 = await reader.read();
|
|
1942
|
+
if (part2.done) break;
|
|
1943
|
+
size += part2.value.byteLength;
|
|
1711
1944
|
if (size > 4 * 1024 * 1024) throw new Error("\u5C0F\u6302\u4EF6\u5185\u5BB9\u8FC7\u5927\uFF0C\u6682\u65F6\u65E0\u6CD5\u6253\u5F00");
|
|
1712
|
-
chunks.push(
|
|
1945
|
+
chunks.push(part2.value);
|
|
1713
1946
|
}
|
|
1714
1947
|
} finally {
|
|
1715
1948
|
await reader.cancel();
|
|
@@ -1722,8 +1955,8 @@ var MlbSimulatorClient = class {
|
|
|
1722
1955
|
const value = await this.#request("GET", `/app/privates/${encodeURIComponent(privateId)}`, {
|
|
1723
1956
|
signal: options.signal
|
|
1724
1957
|
});
|
|
1725
|
-
const body =
|
|
1726
|
-
const conversation =
|
|
1958
|
+
const body = object2(value);
|
|
1959
|
+
const conversation = object2(body?.private);
|
|
1727
1960
|
if (!body || conversation?.id !== privateId || conversation?.buddyId !== this.#buddyId || !Array.isArray(body.messages) || typeof body.replying !== "boolean") {
|
|
1728
1961
|
throw httpError(502, { error: "MLB Server \u8FD4\u56DE\u4E86\u65E0\u6CD5\u8BC6\u522B\u7684\u79C1\u804A\u72B6\u6001" });
|
|
1729
1962
|
}
|
|
@@ -1786,9 +2019,9 @@ var MlbSimulatorClient = class {
|
|
|
1786
2019
|
messages: [{ type: "text", text: input.text }],
|
|
1787
2020
|
clientMessageId
|
|
1788
2021
|
}, { signal: options.signal });
|
|
1789
|
-
const receipt2 =
|
|
2022
|
+
const receipt2 = object2(value);
|
|
1790
2023
|
const items = Array.isArray(receipt2?.items) ? receipt2.items : [];
|
|
1791
|
-
const item2 =
|
|
2024
|
+
const item2 = object2(items[0]);
|
|
1792
2025
|
if (receipt2?.ok !== true || receipt2.clientMessageId !== clientMessageId || items.length !== 1 || item2?.index !== 0 || typeof item2.messageId !== "string" || !item2.messageId.trim() || !Number.isSafeInteger(item2.timestamp) || item2.timestamp < 0 || typeof receipt2.shouldReply !== "boolean") {
|
|
1793
2026
|
throw httpError(502, { error: "MLB \u6D88\u606F\u56DE\u6267\u65E0\u6548" });
|
|
1794
2027
|
}
|
|
@@ -1801,8 +2034,8 @@ var MlbSimulatorClient = class {
|
|
|
1801
2034
|
publicMessage: "\u53D1\u9001\u7ED3\u679C\u5C1A\u672A\u786E\u8BA4\uFF0C\u8BF7\u5237\u65B0\u804A\u5929\u8BB0\u5F55\u540E\u518D\u91CD\u8BD5"
|
|
1802
2035
|
});
|
|
1803
2036
|
}
|
|
1804
|
-
const receipt =
|
|
1805
|
-
const item =
|
|
2037
|
+
const receipt = object2(value);
|
|
2038
|
+
const item = object2(receipt.items[0]);
|
|
1806
2039
|
const runId = this.#active?.runId ?? localReceipt("turn", idempotencyKey);
|
|
1807
2040
|
const messageId = safeText(item.messageId, "receipt.items[0].messageId", 256);
|
|
1808
2041
|
if (!this.#active) {
|
|
@@ -1832,17 +2065,18 @@ var MlbSimulatorClient = class {
|
|
|
1832
2065
|
if (this.#sending) throw httpError(409, { error: "\u6D88\u606F\u6B63\u5728\u53D1\u9001\uFF0C\u8BF7\u7A0D\u540E\u6E05\u7A7A" });
|
|
1833
2066
|
if (!this.#resetTarget) {
|
|
1834
2067
|
const privateId = await this.#ensurePrivate(options.signal);
|
|
1835
|
-
const current =
|
|
1836
|
-
const conversation =
|
|
2068
|
+
const current = object2(await this.#request("GET", `/app/privates/${encodeURIComponent(privateId)}`, options));
|
|
2069
|
+
const conversation = object2(current?.private);
|
|
1837
2070
|
if (conversation?.id !== privateId || conversation?.buddyId !== this.#buddyId || typeof current?.replying !== "boolean") {
|
|
1838
2071
|
throw httpError(502, { error: "\u65E0\u6CD5\u786E\u8BA4\u5F53\u524D\u79C1\u804A\u72B6\u6001\uFF0C\u672A\u6E05\u7A7A\u5BF9\u8BDD" });
|
|
1839
2072
|
}
|
|
1840
2073
|
if (current.replying) throw httpError(409, { error: "\u8BF7\u7B49\u5F85\u5F53\u524D\u56DE\u590D\u5B8C\u6210\u540E\u518D\u6E05\u7A7A" });
|
|
1841
2074
|
this.#resetTarget = privateId;
|
|
2075
|
+
await this.#widgetBridge.closeAll();
|
|
1842
2076
|
}
|
|
1843
2077
|
try {
|
|
1844
2078
|
if (!this.#resetArchived) {
|
|
1845
|
-
const receipt =
|
|
2079
|
+
const receipt = object2(await this.#request("DELETE", `/app/privates/${encodeURIComponent(this.#resetTarget)}`, options));
|
|
1846
2080
|
if (receipt?.ok !== true) throw httpError(502, { error: "\u65E0\u6CD5\u786E\u8BA4\u65E7\u4F1A\u8BDD\u5DF2\u7ED3\u675F" });
|
|
1847
2081
|
this.#resetArchived = true;
|
|
1848
2082
|
this.#privateId = void 0;
|
|
@@ -1853,7 +2087,7 @@ var MlbSimulatorClient = class {
|
|
|
1853
2087
|
throw httpError(502, { error: "MLB \u672A\u8FD4\u56DE\u65B0\u7684\u4F1A\u8BDD\u8EAB\u4EFD" });
|
|
1854
2088
|
}
|
|
1855
2089
|
} catch (cause) {
|
|
1856
|
-
const detail =
|
|
2090
|
+
const detail = object2(cause)?.publicMessage;
|
|
1857
2091
|
throw Object.assign(new Error("\u4F1A\u8BDD\u91CD\u7F6E\u5C1A\u672A\u5B8C\u6210", { cause }), {
|
|
1858
2092
|
code: "RESET_INCOMPLETE",
|
|
1859
2093
|
status: 502,
|
|
@@ -1927,7 +2161,7 @@ var MlbSimulatorClient = class {
|
|
|
1927
2161
|
} catch (cause) {
|
|
1928
2162
|
if (options.signal?.aborted) return;
|
|
1929
2163
|
await reset();
|
|
1930
|
-
const status =
|
|
2164
|
+
const status = object2(cause)?.status;
|
|
1931
2165
|
if (cause instanceof MlbEventStreamError && !cause.retryable || typeof status === "number" && status >= 400 && status < 500 && status !== 429 || reconnects >= 4) throw cause;
|
|
1932
2166
|
}
|
|
1933
2167
|
await waitForRetry(Math.min(1e3, (reconnects + 1) * 150), options.signal);
|
|
@@ -1941,7 +2175,7 @@ var MlbSimulatorClient = class {
|
|
|
1941
2175
|
"/app/privates",
|
|
1942
2176
|
{ signal, body: { buddyId: this.#buddyId } }
|
|
1943
2177
|
).then((value) => {
|
|
1944
|
-
const body =
|
|
2178
|
+
const body = object2(value);
|
|
1945
2179
|
if (body?.ok !== true || typeof body.id !== "string" || !/^p_[a-zA-Z0-9_-]{1,253}$/u.test(body.id)) {
|
|
1946
2180
|
throw httpError(502, { error: "MLB \u521B\u5EFA\u79C1\u804A\u54CD\u5E94\u7F3A\u5C11\u6709\u6548\u7684 privateId" });
|
|
1947
2181
|
}
|
|
@@ -1957,7 +2191,8 @@ var MlbSimulatorClient = class {
|
|
|
1957
2191
|
async #request(method, path, options) {
|
|
1958
2192
|
const headers = new Headers({
|
|
1959
2193
|
Accept: "application/json",
|
|
1960
|
-
Authorization: `Bearer ${this.#accessToken}
|
|
2194
|
+
Authorization: `Bearer ${this.#accessToken}`,
|
|
2195
|
+
...options.headers
|
|
1961
2196
|
});
|
|
1962
2197
|
if (options.body !== void 0) headers.set("Content-Type", "application/json");
|
|
1963
2198
|
const response = await this.#fetch(endpoint(this.#baseUrl, path), {
|
|
@@ -1976,7 +2211,7 @@ var MlbSimulatorClient = class {
|
|
|
1976
2211
|
// apps/preview/src/bridge.ts
|
|
1977
2212
|
import { hostname } from "node:os";
|
|
1978
2213
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
1979
|
-
import { setTimeout as
|
|
2214
|
+
import { setTimeout as delay2 } from "node:timers/promises";
|
|
1980
2215
|
async function connectPreviewBridge(options) {
|
|
1981
2216
|
const connectionId = randomBytes2(32).toString("base64url");
|
|
1982
2217
|
const shutdown = new AbortController();
|
|
@@ -2139,7 +2374,7 @@ async function connectPreviewBridge(options) {
|
|
|
2139
2374
|
break;
|
|
2140
2375
|
} else if (status && status >= 400 && status < 500 && status !== 429) throw cause;
|
|
2141
2376
|
}
|
|
2142
|
-
await
|
|
2377
|
+
await delay2(replies.size ? 20 : 500, void 0, { signal }).catch(() => void 0);
|
|
2143
2378
|
}
|
|
2144
2379
|
} finally {
|
|
2145
2380
|
clearInterval(watchdog);
|