@fraqjs/fraq 0.10.3 → 0.12.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 +1 -1
- package/dist/index.d.mts +173 -61
- package/dist/index.mjs +513 -291
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1,201 +1,4 @@
|
|
|
1
1
|
import mitt from "mitt";
|
|
2
|
-
//#region src/protocol/client.ts
|
|
3
|
-
var MilkyClientBase = class {
|
|
4
|
-
baseUrl;
|
|
5
|
-
wsBaseUrl;
|
|
6
|
-
accessToken;
|
|
7
|
-
baseHeaders;
|
|
8
|
-
constructor(baseUrl, options) {
|
|
9
|
-
const normalizedBaseUrl = baseUrl.toString();
|
|
10
|
-
this.baseUrl = normalizedBaseUrl.endsWith("/") ? normalizedBaseUrl.slice(0, -1) : normalizedBaseUrl;
|
|
11
|
-
this.wsBaseUrl = this.baseUrl.replace(/^http/, "ws");
|
|
12
|
-
this.accessToken = options?.accessToken;
|
|
13
|
-
this.baseHeaders = { "Content-Type": "application/json" };
|
|
14
|
-
if (options?.accessToken) this.baseHeaders.Authorization = `Bearer ${options.accessToken}`;
|
|
15
|
-
}
|
|
16
|
-
async callApi(endpoint, params) {
|
|
17
|
-
const response = await fetch(`${this.baseUrl}/api/${endpoint}`, {
|
|
18
|
-
method: "POST",
|
|
19
|
-
body: JSON.stringify(params ?? {}),
|
|
20
|
-
headers: this.baseHeaders
|
|
21
|
-
});
|
|
22
|
-
if (!response.ok) throw new Error(`API call ${endpoint} failed with HTTP status ${response.status}`);
|
|
23
|
-
const json = await response.json();
|
|
24
|
-
if (json.status === "failed") throw new Error(`API call ${endpoint} failed with retcode ${json.retcode}: ${json.message}`);
|
|
25
|
-
return json.data;
|
|
26
|
-
}
|
|
27
|
-
async startEvents(onEvent) {
|
|
28
|
-
const ws = new WebSocket(`${this.wsBaseUrl}/event${this.accessToken ? `?access_token=${this.accessToken}` : ""}`);
|
|
29
|
-
let closeSubscription = () => {};
|
|
30
|
-
const closed = new Promise((resolve, reject) => {
|
|
31
|
-
closeSubscription = (error) => {
|
|
32
|
-
if (error) reject(error);
|
|
33
|
-
else resolve();
|
|
34
|
-
};
|
|
35
|
-
});
|
|
36
|
-
ws.addEventListener("message", async (event) => {
|
|
37
|
-
try {
|
|
38
|
-
if (typeof event.data !== "string") throw new Error(`Expected text frame, got ${typeof event.data}`);
|
|
39
|
-
await onEvent(JSON.parse(event.data));
|
|
40
|
-
} catch (error) {
|
|
41
|
-
closeSubscription(error);
|
|
42
|
-
ws.close();
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
await new Promise((resolve, reject) => {
|
|
46
|
-
ws.addEventListener("open", () => resolve(), { once: true });
|
|
47
|
-
ws.addEventListener("error", (event) => reject(/* @__PURE__ */ new Error(`WebSocket error: ${event}`)));
|
|
48
|
-
});
|
|
49
|
-
ws.addEventListener("error", (event) => closeSubscription(/* @__PURE__ */ new Error(`WebSocket error: ${event}`)));
|
|
50
|
-
ws.addEventListener("close", () => closeSubscription(), { once: true });
|
|
51
|
-
return {
|
|
52
|
-
closed,
|
|
53
|
-
stop() {
|
|
54
|
-
ws.close();
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
function createMilkyClient(...params) {
|
|
60
|
-
const base = new MilkyClientBase(...params);
|
|
61
|
-
return new Proxy(base, { get(target, endpoint) {
|
|
62
|
-
if (typeof endpoint === "string" && endpoint.includes("_")) return (params) => target.callApi(endpoint, params);
|
|
63
|
-
else return target[endpoint];
|
|
64
|
-
} });
|
|
65
|
-
}
|
|
66
|
-
//#endregion
|
|
67
|
-
//#region src/protocol/segment.ts
|
|
68
|
-
function msg(strings, ...values) {
|
|
69
|
-
let buffer = "";
|
|
70
|
-
const segments = [];
|
|
71
|
-
for (let i = 0; i < strings.length; i++) {
|
|
72
|
-
buffer += strings[i];
|
|
73
|
-
if (i < values.length) {
|
|
74
|
-
const value = values[i];
|
|
75
|
-
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") buffer += value.toString();
|
|
76
|
-
else {
|
|
77
|
-
if (buffer) {
|
|
78
|
-
segments.push({
|
|
79
|
-
type: "text",
|
|
80
|
-
data: { text: buffer }
|
|
81
|
-
});
|
|
82
|
-
buffer = "";
|
|
83
|
-
}
|
|
84
|
-
segments.push(value);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
if (buffer) segments.push({
|
|
89
|
-
type: "text",
|
|
90
|
-
data: { text: buffer }
|
|
91
|
-
});
|
|
92
|
-
return trimBoundaryTextSegments(segments);
|
|
93
|
-
}
|
|
94
|
-
function isTextSegment(segment) {
|
|
95
|
-
return segment.type === "text";
|
|
96
|
-
}
|
|
97
|
-
function withText(segment, text) {
|
|
98
|
-
return {
|
|
99
|
-
...segment,
|
|
100
|
-
data: {
|
|
101
|
-
...segment.data,
|
|
102
|
-
text
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
function trimBoundaryTextSegments(segments) {
|
|
107
|
-
const result = [...segments];
|
|
108
|
-
const first = result[0];
|
|
109
|
-
if (first && isTextSegment(first)) {
|
|
110
|
-
const text = first.data.text.trimStart();
|
|
111
|
-
if (text) result[0] = withText(first, text);
|
|
112
|
-
else result.shift();
|
|
113
|
-
}
|
|
114
|
-
const lastIndex = result.length - 1;
|
|
115
|
-
const last = result[lastIndex];
|
|
116
|
-
if (last && isTextSegment(last)) {
|
|
117
|
-
const text = last.data.text.trimEnd();
|
|
118
|
-
if (text) result[lastIndex] = withText(last, text);
|
|
119
|
-
else result.pop();
|
|
120
|
-
}
|
|
121
|
-
return result;
|
|
122
|
-
}
|
|
123
|
-
let seg;
|
|
124
|
-
(function(_seg) {
|
|
125
|
-
function mention(userId) {
|
|
126
|
-
return {
|
|
127
|
-
type: "mention",
|
|
128
|
-
data: { user_id: userId }
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
_seg.mention = mention;
|
|
132
|
-
function mentionAll() {
|
|
133
|
-
return {
|
|
134
|
-
type: "mention_all",
|
|
135
|
-
data: {}
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
_seg.mentionAll = mentionAll;
|
|
139
|
-
function face(faceId, options) {
|
|
140
|
-
return {
|
|
141
|
-
type: "face",
|
|
142
|
-
data: {
|
|
143
|
-
face_id: faceId.toString(),
|
|
144
|
-
is_large: options?.isLarge ?? false
|
|
145
|
-
}
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
_seg.face = face;
|
|
149
|
-
function reply(messageSeq) {
|
|
150
|
-
return {
|
|
151
|
-
type: "reply",
|
|
152
|
-
data: { message_seq: messageSeq }
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
_seg.reply = reply;
|
|
156
|
-
function image(uri, options) {
|
|
157
|
-
return {
|
|
158
|
-
type: "image",
|
|
159
|
-
data: {
|
|
160
|
-
uri,
|
|
161
|
-
sub_type: options?.subType,
|
|
162
|
-
summary: options?.summary
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
_seg.image = image;
|
|
167
|
-
function record(uri) {
|
|
168
|
-
return {
|
|
169
|
-
type: "record",
|
|
170
|
-
data: { uri }
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
_seg.record = record;
|
|
174
|
-
function video(uri, options) {
|
|
175
|
-
return {
|
|
176
|
-
type: "video",
|
|
177
|
-
data: {
|
|
178
|
-
uri,
|
|
179
|
-
thumb_uri: options?.thumbUri
|
|
180
|
-
}
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
_seg.video = video;
|
|
184
|
-
function forward(messages, options) {
|
|
185
|
-
return {
|
|
186
|
-
type: "forward",
|
|
187
|
-
data: {
|
|
188
|
-
messages,
|
|
189
|
-
title: options?.title,
|
|
190
|
-
preview: options?.preview,
|
|
191
|
-
summary: options?.summary,
|
|
192
|
-
prompt: options?.prompt
|
|
193
|
-
}
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
_seg.forward = forward;
|
|
197
|
-
})(seg || (seg = {}));
|
|
198
|
-
//#endregion
|
|
199
2
|
//#region src/routing/command.ts
|
|
200
3
|
var CommandBuilder = class {
|
|
201
4
|
name;
|
|
@@ -205,6 +8,7 @@ var CommandBuilder = class {
|
|
|
205
8
|
description;
|
|
206
9
|
aliases;
|
|
207
10
|
hidden;
|
|
11
|
+
routeMeta;
|
|
208
12
|
constructor(name, sink = (command) => command) {
|
|
209
13
|
this.name = name;
|
|
210
14
|
this.sink = sink;
|
|
@@ -226,6 +30,14 @@ var CommandBuilder = class {
|
|
|
226
30
|
this.hidden = true;
|
|
227
31
|
return this;
|
|
228
32
|
}
|
|
33
|
+
meta(meta) {
|
|
34
|
+
this.routeMeta = mergeRouteMeta(this.routeMeta, meta);
|
|
35
|
+
return this;
|
|
36
|
+
}
|
|
37
|
+
tag(...tags) {
|
|
38
|
+
this.routeMeta = mergeRouteMeta(this.routeMeta, { tags });
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
229
41
|
execute(executor) {
|
|
230
42
|
this.executor = executor;
|
|
231
43
|
const command = {
|
|
@@ -236,9 +48,31 @@ var CommandBuilder = class {
|
|
|
236
48
|
if (this.description !== void 0) command.description = this.description;
|
|
237
49
|
if (this.aliases !== void 0) command.aliases = this.aliases;
|
|
238
50
|
if (this.hidden !== void 0) command.hidden = this.hidden;
|
|
51
|
+
if (this.routeMeta !== void 0) command.meta = this.routeMeta;
|
|
239
52
|
return this.sink(command);
|
|
240
53
|
}
|
|
241
54
|
};
|
|
55
|
+
function mergeRouteMeta(left, right) {
|
|
56
|
+
if (left === void 0 && right === void 0) return;
|
|
57
|
+
const merged = {
|
|
58
|
+
...left ?? {},
|
|
59
|
+
...right ?? {}
|
|
60
|
+
};
|
|
61
|
+
const tags = uniqueTags(left?.tags, right?.tags);
|
|
62
|
+
if (tags.length > 0) merged.tags = tags;
|
|
63
|
+
else delete merged.tags;
|
|
64
|
+
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
65
|
+
}
|
|
66
|
+
function uniqueTags(...tagLists) {
|
|
67
|
+
const tags = [];
|
|
68
|
+
const seen = /* @__PURE__ */ new Set();
|
|
69
|
+
for (const tagList of tagLists) for (const tag of tagList ?? []) {
|
|
70
|
+
if (seen.has(tag)) continue;
|
|
71
|
+
seen.add(tag);
|
|
72
|
+
tags.push(tag);
|
|
73
|
+
}
|
|
74
|
+
return tags;
|
|
75
|
+
}
|
|
242
76
|
//#endregion
|
|
243
77
|
//#region src/routing/tokenizer.ts
|
|
244
78
|
const WHITESPACE_CHARS = new Set([
|
|
@@ -343,6 +177,33 @@ var Tokenizer = class {
|
|
|
343
177
|
this.subOffset = void 0;
|
|
344
178
|
return segments;
|
|
345
179
|
}
|
|
180
|
+
consumeMention(userId) {
|
|
181
|
+
const token = this.peek();
|
|
182
|
+
if (typeof token === "object" && token !== null && token.type === "mention" && token.data.user_id === userId) {
|
|
183
|
+
this.next();
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
consumeTextPrefix(prefix) {
|
|
189
|
+
if (prefix.length === 0) return false;
|
|
190
|
+
const position = this.findNextPosition();
|
|
191
|
+
if (position === void 0) return false;
|
|
192
|
+
const segment = this.segments[position.offset];
|
|
193
|
+
if (segment.type !== "text") return false;
|
|
194
|
+
const prefixStart = this.findTextTokenStart(segment.data.text, position.subOffset ?? 0);
|
|
195
|
+
if (!segment.data.text.startsWith(prefix, prefixStart)) return false;
|
|
196
|
+
const prefixEnd = prefixStart + prefix.length;
|
|
197
|
+
const nextSubOffset = this.findTextTokenStart(segment.data.text, prefixEnd);
|
|
198
|
+
if (nextSubOffset < segment.data.text.length) {
|
|
199
|
+
this.offset = position.offset;
|
|
200
|
+
this.subOffset = nextSubOffset;
|
|
201
|
+
} else {
|
|
202
|
+
this.offset = position.offset + 1;
|
|
203
|
+
this.subOffset = void 0;
|
|
204
|
+
}
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
346
207
|
findNextPosition() {
|
|
347
208
|
let offset = this.offset;
|
|
348
209
|
let subOffset = this.subOffset;
|
|
@@ -374,19 +235,35 @@ var Tokenizer = class {
|
|
|
374
235
|
};
|
|
375
236
|
//#endregion
|
|
376
237
|
//#region src/routing/router.ts
|
|
238
|
+
const DEFAULT_ACTIVATIONS = [{ type: "direct" }];
|
|
239
|
+
const defaultRouteActivationResolver = () => DEFAULT_ACTIVATIONS;
|
|
377
240
|
var Router = class Router {
|
|
378
241
|
entries = [];
|
|
379
242
|
groups = /* @__PURE__ */ new Map();
|
|
243
|
+
activationResolver = defaultRouteActivationResolver;
|
|
244
|
+
scopeMeta;
|
|
245
|
+
setActivationResolver(resolver) {
|
|
246
|
+
this.activationResolver = resolver;
|
|
247
|
+
return this;
|
|
248
|
+
}
|
|
249
|
+
withMeta(meta) {
|
|
250
|
+
const router = new Router();
|
|
251
|
+
router.entries = this.entries;
|
|
252
|
+
router.groups = this.groups;
|
|
253
|
+
router.scopeMeta = mergeRouteMeta(this.scopeMeta, meta);
|
|
254
|
+
return router;
|
|
255
|
+
}
|
|
380
256
|
command(command) {
|
|
381
257
|
if (typeof command === "string") return new CommandBuilder(command, (builtCommand) => {
|
|
382
258
|
this.command(builtCommand);
|
|
383
259
|
return builtCommand;
|
|
384
260
|
});
|
|
385
|
-
this.
|
|
386
|
-
this.
|
|
261
|
+
const scopedCommand = this.applyScopeMeta(command);
|
|
262
|
+
this.validatePattern(scopedCommand.pattern);
|
|
263
|
+
this.resolveAliasConflicts(scopedCommand);
|
|
387
264
|
this.entries.push({
|
|
388
265
|
type: "command",
|
|
389
|
-
command
|
|
266
|
+
command: scopedCommand
|
|
390
267
|
});
|
|
391
268
|
return this;
|
|
392
269
|
}
|
|
@@ -395,10 +272,11 @@ var Router = class Router {
|
|
|
395
272
|
this.rawPattern(builtCommand);
|
|
396
273
|
return builtCommand;
|
|
397
274
|
});
|
|
398
|
-
this.
|
|
275
|
+
const scopedRawPattern = this.applyScopeMeta(rawPattern);
|
|
276
|
+
this.validatePattern(scopedRawPattern.pattern, { rawPattern: true });
|
|
399
277
|
this.entries.push({
|
|
400
278
|
type: "rawPattern",
|
|
401
|
-
rawPattern
|
|
279
|
+
rawPattern: scopedRawPattern
|
|
402
280
|
});
|
|
403
281
|
return this;
|
|
404
282
|
}
|
|
@@ -413,7 +291,7 @@ var Router = class Router {
|
|
|
413
291
|
router
|
|
414
292
|
});
|
|
415
293
|
}
|
|
416
|
-
return router;
|
|
294
|
+
return this.scopeMeta ? router.withMeta(this.scopeMeta) : router;
|
|
417
295
|
}
|
|
418
296
|
filter(predicate) {
|
|
419
297
|
const router = new Router();
|
|
@@ -422,7 +300,7 @@ var Router = class Router {
|
|
|
422
300
|
predicate,
|
|
423
301
|
router
|
|
424
302
|
});
|
|
425
|
-
return router;
|
|
303
|
+
return this.scopeMeta ? router.withMeta(this.scopeMeta) : router;
|
|
426
304
|
}
|
|
427
305
|
routes() {
|
|
428
306
|
return this.entries;
|
|
@@ -432,11 +310,19 @@ var Router = class Router {
|
|
|
432
310
|
return [];
|
|
433
311
|
}
|
|
434
312
|
branches(session) {
|
|
435
|
-
return this.branchesFrom(session, []);
|
|
313
|
+
return [...this.branchesFrom(session, [], { includeHidden: false })];
|
|
436
314
|
}
|
|
437
315
|
match(session, message) {
|
|
438
|
-
const
|
|
439
|
-
|
|
316
|
+
for (const branch of this.branchesFrom(session, [], { includeHidden: true })) {
|
|
317
|
+
const descriptor = this.describeBranch(branch);
|
|
318
|
+
const activations = this.activationResolver(descriptor, session) ?? DEFAULT_ACTIVATIONS;
|
|
319
|
+
for (const activation of activations) {
|
|
320
|
+
const tokenizer = new Tokenizer(message.segments);
|
|
321
|
+
if (!this.consumeActivation(tokenizer, activation, session)) continue;
|
|
322
|
+
const match = this.matchBranch(branch, tokenizer, activation);
|
|
323
|
+
if (match !== void 0) return match;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
440
326
|
}
|
|
441
327
|
async dispatch(session, message) {
|
|
442
328
|
const match = this.match(session, message);
|
|
@@ -451,26 +337,51 @@ var Router = class Router {
|
|
|
451
337
|
}
|
|
452
338
|
return true;
|
|
453
339
|
}
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
340
|
+
describeBranch(branch) {
|
|
341
|
+
switch (branch.type) {
|
|
342
|
+
case "command": {
|
|
343
|
+
const descriptor = {
|
|
344
|
+
type: "command",
|
|
345
|
+
path: [...branch.path],
|
|
346
|
+
name: branch.command.name
|
|
347
|
+
};
|
|
348
|
+
if (branch.command.aliases !== void 0) descriptor.aliases = [...branch.command.aliases];
|
|
349
|
+
if (branch.command.meta !== void 0) descriptor.meta = branch.command.meta;
|
|
350
|
+
return descriptor;
|
|
351
|
+
}
|
|
352
|
+
case "rawPattern": {
|
|
353
|
+
const descriptor = {
|
|
354
|
+
type: "rawPattern",
|
|
355
|
+
path: [...branch.path]
|
|
356
|
+
};
|
|
357
|
+
if (branch.rawPattern.meta !== void 0) descriptor.meta = branch.rawPattern.meta;
|
|
358
|
+
return descriptor;
|
|
359
|
+
}
|
|
460
360
|
}
|
|
461
|
-
tokenizer.setState(initialState);
|
|
462
361
|
}
|
|
463
|
-
|
|
464
|
-
switch (
|
|
465
|
-
case "
|
|
466
|
-
case "
|
|
467
|
-
case "
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
362
|
+
consumeActivation(tokenizer, activation, session) {
|
|
363
|
+
switch (activation.type) {
|
|
364
|
+
case "direct": return true;
|
|
365
|
+
case "mention": return tokenizer.consumeMention(session.selfId);
|
|
366
|
+
case "prefix": return tokenizer.consumeTextPrefix(activation.prefix);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
matchBranch(branch, tokenizer, activation) {
|
|
370
|
+
if (!this.matchPath(branch.path, tokenizer)) return;
|
|
371
|
+
switch (branch.type) {
|
|
372
|
+
case "command": return this.matchCommand(branch.command, tokenizer, branch.path, activation);
|
|
373
|
+
case "rawPattern": return this.matchRawPattern(branch.rawPattern, tokenizer, branch.path, activation);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
matchPath(path, tokenizer) {
|
|
377
|
+
for (const name of path) {
|
|
378
|
+
const token = tokenizer.peek();
|
|
379
|
+
if (typeof token !== "string" || token !== name) return false;
|
|
380
|
+
tokenizer.next();
|
|
471
381
|
}
|
|
382
|
+
return true;
|
|
472
383
|
}
|
|
473
|
-
matchCommand(command, tokenizer, path) {
|
|
384
|
+
matchCommand(command, tokenizer, path, activation) {
|
|
474
385
|
const token = tokenizer.peek();
|
|
475
386
|
if (typeof token !== "string" || token !== command.name && !command.aliases?.includes(token)) return;
|
|
476
387
|
tokenizer.next();
|
|
@@ -480,50 +391,55 @@ var Router = class Router {
|
|
|
480
391
|
type: "command",
|
|
481
392
|
path: [...path],
|
|
482
393
|
command,
|
|
483
|
-
params
|
|
394
|
+
params,
|
|
395
|
+
activation
|
|
484
396
|
};
|
|
485
397
|
}
|
|
486
|
-
|
|
487
|
-
const token = tokenizer.peek();
|
|
488
|
-
if (typeof token !== "string" || token !== name) return;
|
|
489
|
-
tokenizer.next();
|
|
490
|
-
return router.matchFrom(session, tokenizer, [...path, name]);
|
|
491
|
-
}
|
|
492
|
-
matchRawPattern(rawPattern, tokenizer, path) {
|
|
398
|
+
matchRawPattern(rawPattern, tokenizer, path, activation) {
|
|
493
399
|
const params = this.capturePattern(rawPattern.pattern, tokenizer);
|
|
494
400
|
if (params === void 0 || tokenizer.hasNext()) return;
|
|
495
401
|
return {
|
|
496
402
|
type: "rawPattern",
|
|
497
403
|
path: [...path],
|
|
498
404
|
rawPattern,
|
|
499
|
-
params
|
|
405
|
+
params,
|
|
406
|
+
activation
|
|
500
407
|
};
|
|
501
408
|
}
|
|
502
|
-
branchesFrom(session, path) {
|
|
503
|
-
const branches = [];
|
|
409
|
+
*branchesFrom(session, path, options) {
|
|
504
410
|
for (const entry of this.entries) switch (entry.type) {
|
|
505
411
|
case "command":
|
|
506
|
-
if (!entry.command.hidden)
|
|
412
|
+
if (options.includeHidden || !entry.command.hidden) yield {
|
|
507
413
|
type: "command",
|
|
508
414
|
path: [...path],
|
|
509
|
-
command: entry.command
|
|
510
|
-
|
|
415
|
+
command: entry.command,
|
|
416
|
+
meta: entry.command.meta
|
|
417
|
+
};
|
|
511
418
|
break;
|
|
512
419
|
case "group":
|
|
513
|
-
|
|
420
|
+
yield* entry.router.branchesFrom(session, [...path, entry.name], options);
|
|
514
421
|
break;
|
|
515
422
|
case "filter":
|
|
516
|
-
if (entry.predicate(session) === true)
|
|
423
|
+
if (entry.predicate(session) === true) yield* entry.router.branchesFrom(session, path, options);
|
|
517
424
|
break;
|
|
518
425
|
case "rawPattern":
|
|
519
|
-
|
|
426
|
+
yield {
|
|
520
427
|
type: "rawPattern",
|
|
521
428
|
path: [...path],
|
|
522
|
-
rawPattern: entry.rawPattern
|
|
523
|
-
|
|
429
|
+
rawPattern: entry.rawPattern,
|
|
430
|
+
meta: entry.rawPattern.meta
|
|
431
|
+
};
|
|
524
432
|
break;
|
|
525
433
|
}
|
|
526
|
-
|
|
434
|
+
}
|
|
435
|
+
applyScopeMeta(route) {
|
|
436
|
+
if (this.scopeMeta === void 0) return route;
|
|
437
|
+
const meta = mergeRouteMeta(route.meta, this.scopeMeta);
|
|
438
|
+
if (meta === void 0) return route;
|
|
439
|
+
return {
|
|
440
|
+
...route,
|
|
441
|
+
meta
|
|
442
|
+
};
|
|
527
443
|
}
|
|
528
444
|
resolveAliasConflicts(command) {
|
|
529
445
|
for (const entry of this.entries) {
|
|
@@ -570,6 +486,283 @@ var Router = class Router {
|
|
|
570
486
|
}
|
|
571
487
|
};
|
|
572
488
|
//#endregion
|
|
489
|
+
//#region src/core/activation.ts
|
|
490
|
+
function createContextRouteActivationResolver(routing, fallback = defaultRouteActivationResolver) {
|
|
491
|
+
if (!routing) return fallback;
|
|
492
|
+
if (routing.activation && routing.activationResolver) throw new Error("Context routing cannot specify both activation and activationResolver.");
|
|
493
|
+
if (routing.activationResolver) return routing.activationResolver;
|
|
494
|
+
if (routing.activation) return compileContextRouteActivationConfig(routing.activation, fallback);
|
|
495
|
+
return fallback;
|
|
496
|
+
}
|
|
497
|
+
function compileContextRouteActivationConfig(config, fallback) {
|
|
498
|
+
return (route, session) => {
|
|
499
|
+
for (const rule of contextRouteActivationRules(config.rules)) {
|
|
500
|
+
if (!matchesContextRouteActivationRule(route, session, rule)) continue;
|
|
501
|
+
const activation = resolveContextRouteActivation(rule.activation, session.raw.message_scene);
|
|
502
|
+
if (activation !== void 0) return activation;
|
|
503
|
+
}
|
|
504
|
+
if (config.default !== void 0) {
|
|
505
|
+
const activation = resolveContextRouteActivation(config.default, session.raw.message_scene);
|
|
506
|
+
if (activation !== void 0) return activation;
|
|
507
|
+
}
|
|
508
|
+
return fallback(route, session);
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
function contextRouteActivationRules(rules) {
|
|
512
|
+
if (rules === void 0) return [];
|
|
513
|
+
return isContextRouteActivationRuleArray(rules) ? rules : [rules];
|
|
514
|
+
}
|
|
515
|
+
function isContextRouteActivationRuleArray(rules) {
|
|
516
|
+
return Array.isArray(rules);
|
|
517
|
+
}
|
|
518
|
+
function resolveContextRouteActivation(activation, scene) {
|
|
519
|
+
if (isRouteActivationArray(activation)) return activation;
|
|
520
|
+
return activation[scene] ?? activation.default;
|
|
521
|
+
}
|
|
522
|
+
function isRouteActivationArray(activation) {
|
|
523
|
+
return Array.isArray(activation);
|
|
524
|
+
}
|
|
525
|
+
function matchesContextRouteActivationRule(route, session, rule) {
|
|
526
|
+
const match = rule.match;
|
|
527
|
+
if (!match) return true;
|
|
528
|
+
if (!matchesStringSelector(match.type, route.type)) return false;
|
|
529
|
+
if (!matchesStringSelector(match.plugin, routeMetaString(route, "plugin"))) return false;
|
|
530
|
+
if (!matchesStringSelector(match.context, routeMetaString(route, "context"))) return false;
|
|
531
|
+
if (!matchesTagSelector(match.tag, route.meta?.tags)) return false;
|
|
532
|
+
if (match.command !== void 0 && (route.type !== "command" || !matchesStringSelector(match.command, route.name))) return false;
|
|
533
|
+
if (match.path !== void 0 && !sameStringArray(match.path, route.path)) return false;
|
|
534
|
+
if (match.route !== void 0 && !sameStringArray(match.route, routeSegments(route))) return false;
|
|
535
|
+
if (match.predicate && match.predicate(route, session) !== true) return false;
|
|
536
|
+
return true;
|
|
537
|
+
}
|
|
538
|
+
function routeMetaString(route, key) {
|
|
539
|
+
const value = route.meta?.[key];
|
|
540
|
+
return typeof value === "string" ? value : void 0;
|
|
541
|
+
}
|
|
542
|
+
function matchesStringSelector(selector, value) {
|
|
543
|
+
if (selector === void 0) return true;
|
|
544
|
+
if (value === void 0) return false;
|
|
545
|
+
return typeof selector === "string" ? selector === value : selector.includes(value);
|
|
546
|
+
}
|
|
547
|
+
function matchesTagSelector(selector, tags) {
|
|
548
|
+
if (selector === void 0) return true;
|
|
549
|
+
if (!tags) return false;
|
|
550
|
+
return typeof selector === "string" ? tags.includes(selector) : selector.some((tag) => tags.includes(tag));
|
|
551
|
+
}
|
|
552
|
+
function routeSegments(route) {
|
|
553
|
+
if (route.type === "command") return [...route.path, route.name];
|
|
554
|
+
return route.path;
|
|
555
|
+
}
|
|
556
|
+
function sameStringArray(left, right) {
|
|
557
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
558
|
+
}
|
|
559
|
+
//#endregion
|
|
560
|
+
//#region src/protocol/client.ts
|
|
561
|
+
var MilkyClientBase = class {
|
|
562
|
+
baseUrl;
|
|
563
|
+
wsBaseUrl;
|
|
564
|
+
accessToken;
|
|
565
|
+
baseHeaders;
|
|
566
|
+
constructor(baseUrl, options) {
|
|
567
|
+
const normalizedBaseUrl = baseUrl.toString();
|
|
568
|
+
this.baseUrl = normalizedBaseUrl.endsWith("/") ? normalizedBaseUrl.slice(0, -1) : normalizedBaseUrl;
|
|
569
|
+
this.wsBaseUrl = this.baseUrl.replace(/^http/, "ws");
|
|
570
|
+
this.accessToken = options?.accessToken;
|
|
571
|
+
this.baseHeaders = { "Content-Type": "application/json" };
|
|
572
|
+
if (options?.accessToken) this.baseHeaders.Authorization = `Bearer ${options.accessToken}`;
|
|
573
|
+
}
|
|
574
|
+
async callApi(endpoint, params) {
|
|
575
|
+
const response = await fetch(`${this.baseUrl}/api/${endpoint}`, {
|
|
576
|
+
method: "POST",
|
|
577
|
+
body: JSON.stringify(params ?? {}),
|
|
578
|
+
headers: this.baseHeaders
|
|
579
|
+
});
|
|
580
|
+
if (!response.ok) throw new Error(`API call ${endpoint} failed with HTTP status ${response.status}`);
|
|
581
|
+
const json = await response.json();
|
|
582
|
+
if (json.status === "failed") throw new Error(`API call ${endpoint} failed with retcode ${json.retcode}: ${json.message}`);
|
|
583
|
+
return json.data;
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
function createMilkyClient(...params) {
|
|
587
|
+
const base = new MilkyClientBase(...params);
|
|
588
|
+
return new Proxy(base, { get(target, endpoint) {
|
|
589
|
+
if (typeof endpoint === "string" && endpoint.includes("_")) return (params) => target.callApi(endpoint, params);
|
|
590
|
+
else return target[endpoint];
|
|
591
|
+
} });
|
|
592
|
+
}
|
|
593
|
+
//#endregion
|
|
594
|
+
//#region src/protocol/event.ts
|
|
595
|
+
function createMilkyWebSocketEventSource(baseUrl, options) {
|
|
596
|
+
const normalizedBaseUrl = baseUrl.toString();
|
|
597
|
+
const wsBaseUrl = normalizedBaseUrl.endsWith("/") ? normalizedBaseUrl.slice(0, -1).replace(/^http/, "ws") : normalizedBaseUrl.replace(/^http/, "ws");
|
|
598
|
+
return {
|
|
599
|
+
name: "websocket",
|
|
600
|
+
async start(onEvent) {
|
|
601
|
+
const ws = new WebSocket(`${wsBaseUrl}/event${options?.accessToken ? `?access_token=${options.accessToken}` : ""}`);
|
|
602
|
+
let closeSubscription = () => {};
|
|
603
|
+
const closed = new Promise((resolve, reject) => {
|
|
604
|
+
closeSubscription = (error) => {
|
|
605
|
+
if (error) reject(error);
|
|
606
|
+
else resolve();
|
|
607
|
+
};
|
|
608
|
+
});
|
|
609
|
+
ws.addEventListener("message", async (event) => {
|
|
610
|
+
try {
|
|
611
|
+
if (typeof event.data !== "string") throw new Error(`Expected text frame, got ${typeof event.data}`);
|
|
612
|
+
await onEvent(JSON.parse(event.data));
|
|
613
|
+
} catch (error) {
|
|
614
|
+
closeSubscription(error);
|
|
615
|
+
ws.close();
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
await new Promise((resolve, reject) => {
|
|
619
|
+
ws.addEventListener("open", () => resolve(), { once: true });
|
|
620
|
+
ws.addEventListener("error", (event) => reject(/* @__PURE__ */ new Error(`WebSocket error: ${event}`)));
|
|
621
|
+
});
|
|
622
|
+
ws.addEventListener("error", (event) => closeSubscription(/* @__PURE__ */ new Error(`WebSocket error: ${event}`)));
|
|
623
|
+
ws.addEventListener("close", () => closeSubscription(), { once: true });
|
|
624
|
+
return {
|
|
625
|
+
closed,
|
|
626
|
+
stop() {
|
|
627
|
+
ws.close();
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
//#endregion
|
|
634
|
+
//#region src/protocol/segment.ts
|
|
635
|
+
function msg(strings, ...values) {
|
|
636
|
+
let buffer = "";
|
|
637
|
+
const segments = [];
|
|
638
|
+
for (let i = 0; i < strings.length; i++) {
|
|
639
|
+
buffer += strings[i];
|
|
640
|
+
if (i < values.length) {
|
|
641
|
+
const value = values[i];
|
|
642
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") buffer += value.toString();
|
|
643
|
+
else {
|
|
644
|
+
if (buffer) {
|
|
645
|
+
segments.push({
|
|
646
|
+
type: "text",
|
|
647
|
+
data: { text: buffer }
|
|
648
|
+
});
|
|
649
|
+
buffer = "";
|
|
650
|
+
}
|
|
651
|
+
segments.push(value);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
if (buffer) segments.push({
|
|
656
|
+
type: "text",
|
|
657
|
+
data: { text: buffer }
|
|
658
|
+
});
|
|
659
|
+
return trimBoundaryTextSegments(segments);
|
|
660
|
+
}
|
|
661
|
+
function isTextSegment(segment) {
|
|
662
|
+
return segment.type === "text";
|
|
663
|
+
}
|
|
664
|
+
function withText(segment, text) {
|
|
665
|
+
return {
|
|
666
|
+
...segment,
|
|
667
|
+
data: {
|
|
668
|
+
...segment.data,
|
|
669
|
+
text
|
|
670
|
+
}
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
function trimBoundaryTextSegments(segments) {
|
|
674
|
+
const result = [...segments];
|
|
675
|
+
const first = result[0];
|
|
676
|
+
if (first && isTextSegment(first)) {
|
|
677
|
+
const text = first.data.text.trimStart();
|
|
678
|
+
if (text) result[0] = withText(first, text);
|
|
679
|
+
else result.shift();
|
|
680
|
+
}
|
|
681
|
+
const lastIndex = result.length - 1;
|
|
682
|
+
const last = result[lastIndex];
|
|
683
|
+
if (last && isTextSegment(last)) {
|
|
684
|
+
const text = last.data.text.trimEnd();
|
|
685
|
+
if (text) result[lastIndex] = withText(last, text);
|
|
686
|
+
else result.pop();
|
|
687
|
+
}
|
|
688
|
+
return result;
|
|
689
|
+
}
|
|
690
|
+
let seg;
|
|
691
|
+
(function(_seg) {
|
|
692
|
+
function mention(userId) {
|
|
693
|
+
return {
|
|
694
|
+
type: "mention",
|
|
695
|
+
data: { user_id: userId }
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
_seg.mention = mention;
|
|
699
|
+
function mentionAll() {
|
|
700
|
+
return {
|
|
701
|
+
type: "mention_all",
|
|
702
|
+
data: {}
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
_seg.mentionAll = mentionAll;
|
|
706
|
+
function face(faceId, options) {
|
|
707
|
+
return {
|
|
708
|
+
type: "face",
|
|
709
|
+
data: {
|
|
710
|
+
face_id: faceId.toString(),
|
|
711
|
+
is_large: options?.isLarge ?? false
|
|
712
|
+
}
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
_seg.face = face;
|
|
716
|
+
function reply(messageSeq) {
|
|
717
|
+
return {
|
|
718
|
+
type: "reply",
|
|
719
|
+
data: { message_seq: messageSeq }
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
_seg.reply = reply;
|
|
723
|
+
function image(uri, options) {
|
|
724
|
+
return {
|
|
725
|
+
type: "image",
|
|
726
|
+
data: {
|
|
727
|
+
uri,
|
|
728
|
+
sub_type: options?.subType,
|
|
729
|
+
summary: options?.summary
|
|
730
|
+
}
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
_seg.image = image;
|
|
734
|
+
function record(uri) {
|
|
735
|
+
return {
|
|
736
|
+
type: "record",
|
|
737
|
+
data: { uri }
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
_seg.record = record;
|
|
741
|
+
function video(uri, options) {
|
|
742
|
+
return {
|
|
743
|
+
type: "video",
|
|
744
|
+
data: {
|
|
745
|
+
uri,
|
|
746
|
+
thumb_uri: options?.thumbUri
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
_seg.video = video;
|
|
751
|
+
function forward(messages, options) {
|
|
752
|
+
return {
|
|
753
|
+
type: "forward",
|
|
754
|
+
data: {
|
|
755
|
+
messages,
|
|
756
|
+
title: options?.title,
|
|
757
|
+
preview: options?.preview,
|
|
758
|
+
summary: options?.summary,
|
|
759
|
+
prompt: options?.prompt
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
_seg.forward = forward;
|
|
764
|
+
})(seg || (seg = {}));
|
|
765
|
+
//#endregion
|
|
573
766
|
//#region src/core/logging.ts
|
|
574
767
|
var Logger = class {
|
|
575
768
|
logHandler;
|
|
@@ -622,12 +815,14 @@ var Context = class Context {
|
|
|
622
815
|
router = new Router();
|
|
623
816
|
logger;
|
|
624
817
|
name;
|
|
818
|
+
routeActivationResolver;
|
|
625
819
|
parent;
|
|
626
820
|
filter;
|
|
627
821
|
eventBus = mitt();
|
|
628
822
|
plugins = [];
|
|
629
823
|
services = /* @__PURE__ */ new Map();
|
|
630
824
|
subContexts = /* @__PURE__ */ new Map();
|
|
825
|
+
eventSourceRuntimes = /* @__PURE__ */ new Map();
|
|
631
826
|
parentEventForwarder;
|
|
632
827
|
timers = /* @__PURE__ */ new Set();
|
|
633
828
|
initialReconnectDelayMs;
|
|
@@ -636,10 +831,6 @@ var Context = class Context {
|
|
|
636
831
|
state = "idle";
|
|
637
832
|
startPromise;
|
|
638
833
|
stopPromise;
|
|
639
|
-
eventSubscription;
|
|
640
|
-
eventStreamTask;
|
|
641
|
-
reconnectTimer;
|
|
642
|
-
resolveReconnectTimer;
|
|
643
834
|
constructor(client, options, name, parent, filter) {
|
|
644
835
|
this.client = client;
|
|
645
836
|
this.initialReconnectDelayMs = options?.reconnect?.initialDelayMs ?? DEFAULT_INITIAL_RECONNECT_DELAY_MS;
|
|
@@ -647,6 +838,8 @@ var Context = class Context {
|
|
|
647
838
|
this.logHandler = options?.logHandler ?? parent?.logHandler;
|
|
648
839
|
this.name = name ?? "root";
|
|
649
840
|
this.logger = new Logger((message) => this.logHandler?.(message), `context:${this.name}`);
|
|
841
|
+
this.routeActivationResolver = createContextRouteActivationResolver(options?.routing, parent?.routeActivationResolver);
|
|
842
|
+
this.router.setActivationResolver(this.routeActivationResolver);
|
|
650
843
|
this.parent = parent;
|
|
651
844
|
this.filter = filter;
|
|
652
845
|
if (parent) {
|
|
@@ -656,10 +849,10 @@ var Context = class Context {
|
|
|
656
849
|
};
|
|
657
850
|
parent.eventBus.on("*", this.parentEventForwarder);
|
|
658
851
|
}
|
|
659
|
-
this.eventBus.on("message_receive", async ({ data: message }) => {
|
|
852
|
+
this.eventBus.on("message_receive", async ({ self_id, data: message }) => {
|
|
660
853
|
try {
|
|
661
854
|
if (this.state === "stopping" || this.state === "stopped") return;
|
|
662
|
-
await this.router.dispatch(this.createSession(message), message);
|
|
855
|
+
await this.router.dispatch(this.createSession(self_id, message), message);
|
|
663
856
|
} catch (error) {
|
|
664
857
|
this.logger.error(`Error routing command (scene=${message.message_scene} peer=${message.peer_id} sender=${message.sender_id} seq=${message.message_seq})`, error);
|
|
665
858
|
}
|
|
@@ -685,6 +878,11 @@ var Context = class Context {
|
|
|
685
878
|
args
|
|
686
879
|
});
|
|
687
880
|
}
|
|
881
|
+
installEventSource(eventSource) {
|
|
882
|
+
if (this.eventSourceRuntimes.has(eventSource)) return;
|
|
883
|
+
this.eventSourceRuntimes.set(eventSource, {});
|
|
884
|
+
if (this.state === "started") this.startEventSource(eventSource);
|
|
885
|
+
}
|
|
688
886
|
provide(service, instance) {
|
|
689
887
|
if (this.services.has(service)) throw new Error(`Service ${service.name} has already been provided in this context.`);
|
|
690
888
|
if (implementsESNextDisposable(instance) && !isDisposable(instance)) throw new Error(`
|
|
@@ -735,8 +933,9 @@ and implement the dispose method to clean up resources when the context stops.
|
|
|
735
933
|
this.timers.add(interval);
|
|
736
934
|
return interval;
|
|
737
935
|
}
|
|
738
|
-
createSession(message) {
|
|
936
|
+
createSession(selfId, message) {
|
|
739
937
|
return {
|
|
938
|
+
selfId,
|
|
740
939
|
raw: message,
|
|
741
940
|
reply: async (textOrSegments, options) => {
|
|
742
941
|
const actualSegments = [];
|
|
@@ -819,8 +1018,10 @@ and implement the dispose method to clean up resources when the context stops.
|
|
|
819
1018
|
for (const context of startingContexts) if (context.state === "starting") context.state = "idle";
|
|
820
1019
|
throw error;
|
|
821
1020
|
}
|
|
822
|
-
for (const { context } of appliedContextPlugins)
|
|
823
|
-
|
|
1021
|
+
for (const { context } of appliedContextPlugins) {
|
|
1022
|
+
context.state = "started";
|
|
1023
|
+
for (const eventSource of context.eventSourceRuntimes.keys()) context.startEventSource(eventSource);
|
|
1024
|
+
}
|
|
824
1025
|
}
|
|
825
1026
|
getState() {
|
|
826
1027
|
return this.state;
|
|
@@ -833,7 +1034,7 @@ and implement the dispose method to clean up resources when the context stops.
|
|
|
833
1034
|
} catch (error) {
|
|
834
1035
|
errors.push(error);
|
|
835
1036
|
}
|
|
836
|
-
await this.
|
|
1037
|
+
await this.stopEventSources(errors);
|
|
837
1038
|
if (this.parent && this.parentEventForwarder) this.parent.eventBus.off("*", this.parentEventForwarder);
|
|
838
1039
|
for (const service of [...this.services.values()].reverse()) {
|
|
839
1040
|
if (!isDisposable(service)) continue;
|
|
@@ -862,20 +1063,27 @@ and implement the dispose method to clean up resources when the context stops.
|
|
|
862
1063
|
for (const timer of this.timers) clearTimeout(timer);
|
|
863
1064
|
this.timers.clear();
|
|
864
1065
|
}
|
|
865
|
-
async
|
|
866
|
-
this.
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
1066
|
+
async stopEventSources(errors) {
|
|
1067
|
+
for (const [_, runtime] of this.eventSourceRuntimes) {
|
|
1068
|
+
this.resolveReconnectDelay(runtime);
|
|
1069
|
+
const subscription = runtime.subscription;
|
|
1070
|
+
if (!subscription) continue;
|
|
1071
|
+
try {
|
|
1072
|
+
await subscription.stop();
|
|
1073
|
+
} catch (error) {
|
|
1074
|
+
errors.push(error);
|
|
1075
|
+
}
|
|
1076
|
+
runtime.subscription = void 0;
|
|
872
1077
|
}
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
1078
|
+
for (const runtime of this.eventSourceRuntimes.values()) {
|
|
1079
|
+
if (!runtime.task) continue;
|
|
1080
|
+
try {
|
|
1081
|
+
await runtime.task;
|
|
1082
|
+
} catch (error) {
|
|
1083
|
+
errors.push(error);
|
|
1084
|
+
} finally {
|
|
1085
|
+
runtime.task = void 0;
|
|
1086
|
+
}
|
|
879
1087
|
}
|
|
880
1088
|
}
|
|
881
1089
|
acceptsParentEvent(type, event) {
|
|
@@ -994,6 +1202,10 @@ and implement the dispose method to clean up resources when the context stops.
|
|
|
994
1202
|
}
|
|
995
1203
|
createProxyContextForPlugin(plugin) {
|
|
996
1204
|
const proxyLogger = new Logger((message) => this.logHandler?.(message), `plugin:${this.name ? `${this.name}/` : ""}${plugin.name}`);
|
|
1205
|
+
const proxyRouter = this.router.withMeta({
|
|
1206
|
+
context: this.name,
|
|
1207
|
+
plugin: plugin.name
|
|
1208
|
+
});
|
|
997
1209
|
let proxyInjections;
|
|
998
1210
|
if (plugin.inject) {
|
|
999
1211
|
proxyInjections = {};
|
|
@@ -1005,17 +1217,23 @@ and implement the dispose method to clean up resources when the context stops.
|
|
|
1005
1217
|
}
|
|
1006
1218
|
return new Proxy(this, { get(target, prop, receiver) {
|
|
1007
1219
|
if (prop === "logger") return proxyLogger;
|
|
1220
|
+
else if (prop === "router") return proxyRouter;
|
|
1008
1221
|
else if (proxyInjections && prop in proxyInjections) return proxyInjections[prop];
|
|
1009
1222
|
else return Reflect.get(target, prop, receiver);
|
|
1010
1223
|
} });
|
|
1011
1224
|
}
|
|
1012
|
-
|
|
1225
|
+
startEventSource(eventSource) {
|
|
1226
|
+
const runtime = this.eventSourceRuntimes.get(eventSource);
|
|
1227
|
+
if (!runtime) return;
|
|
1228
|
+
runtime.task = this.runEventSource(eventSource, runtime);
|
|
1229
|
+
}
|
|
1230
|
+
async runEventSource(eventSource, runtime) {
|
|
1013
1231
|
let reconnectDelay = this.initialReconnectDelayMs;
|
|
1014
1232
|
let reconnectAttempt = 1;
|
|
1015
1233
|
while (this.state === "started") {
|
|
1016
1234
|
try {
|
|
1017
|
-
this.logger.debug(`Connecting event
|
|
1018
|
-
const subscription = await
|
|
1235
|
+
this.logger.debug(`Connecting ${eventSource.name ?? "event source"} (attempt=${reconnectAttempt})`);
|
|
1236
|
+
const subscription = await eventSource.start((event) => {
|
|
1019
1237
|
try {
|
|
1020
1238
|
if (this.state !== "started") return;
|
|
1021
1239
|
this.eventBus.emit(event.event_type, event);
|
|
@@ -1027,48 +1245,52 @@ and implement the dispose method to clean up resources when the context stops.
|
|
|
1027
1245
|
await subscription.stop();
|
|
1028
1246
|
break;
|
|
1029
1247
|
}
|
|
1030
|
-
|
|
1031
|
-
this.logger.info("Event
|
|
1248
|
+
runtime.subscription = subscription;
|
|
1249
|
+
this.logger.info(`${eventSource.name ?? "Event source"} connected`);
|
|
1032
1250
|
reconnectDelay = this.initialReconnectDelayMs;
|
|
1033
1251
|
reconnectAttempt = 1;
|
|
1034
1252
|
await subscription.closed;
|
|
1035
|
-
|
|
1253
|
+
if (runtime.subscription === subscription) runtime.subscription = void 0;
|
|
1036
1254
|
if (this.state !== "started") break;
|
|
1037
|
-
this.logger.warn(
|
|
1255
|
+
this.logger.warn(`${eventSource.name ?? "Event source"} disconnected; reconnecting in ${reconnectDelay}ms`);
|
|
1038
1256
|
} catch (error) {
|
|
1039
|
-
this.eventSubscription = void 0;
|
|
1040
1257
|
if (this.state !== "started") break;
|
|
1041
|
-
this.logger.error(`Error connecting event
|
|
1258
|
+
this.logger.error(`Error connecting ${eventSource.name ?? "event source"}; reconnecting in ${reconnectDelay}ms`, error);
|
|
1042
1259
|
}
|
|
1043
|
-
await this.waitForReconnectDelay(reconnectDelay);
|
|
1260
|
+
await this.waitForReconnectDelay(runtime, reconnectDelay);
|
|
1044
1261
|
reconnectDelay = Math.min(reconnectDelay * 2, this.maxReconnectDelayMs);
|
|
1045
1262
|
reconnectAttempt += 1;
|
|
1046
1263
|
}
|
|
1047
1264
|
}
|
|
1048
|
-
waitForReconnectDelay(delay) {
|
|
1265
|
+
waitForReconnectDelay(runtime, delay) {
|
|
1049
1266
|
return new Promise((resolve) => {
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1267
|
+
runtime.resolveReconnectTimer = resolve;
|
|
1268
|
+
runtime.reconnectTimer = setTimeout(() => {
|
|
1269
|
+
runtime.reconnectTimer = void 0;
|
|
1270
|
+
runtime.resolveReconnectTimer = void 0;
|
|
1054
1271
|
resolve();
|
|
1055
1272
|
}, delay);
|
|
1056
1273
|
});
|
|
1057
1274
|
}
|
|
1058
|
-
resolveReconnectDelay() {
|
|
1059
|
-
if (
|
|
1060
|
-
clearTimeout(
|
|
1061
|
-
|
|
1275
|
+
resolveReconnectDelay(runtime) {
|
|
1276
|
+
if (runtime.reconnectTimer) {
|
|
1277
|
+
clearTimeout(runtime.reconnectTimer);
|
|
1278
|
+
runtime.reconnectTimer = void 0;
|
|
1062
1279
|
}
|
|
1063
|
-
const resolve =
|
|
1064
|
-
|
|
1280
|
+
const resolve = runtime.resolveReconnectTimer;
|
|
1281
|
+
runtime.resolveReconnectTimer = void 0;
|
|
1065
1282
|
resolve?.();
|
|
1066
1283
|
}
|
|
1067
1284
|
static fromUrl(baseUrl, options) {
|
|
1068
|
-
|
|
1285
|
+
const context = new Context(createMilkyClient(baseUrl, { accessToken: options?.accessToken }), options);
|
|
1286
|
+
if (options?.installEventSource ?? true) context.installEventSource(createMilkyWebSocketEventSource(baseUrl, { accessToken: options?.accessToken }));
|
|
1287
|
+
return context;
|
|
1069
1288
|
}
|
|
1070
1289
|
static fromClient(client, options) {
|
|
1071
|
-
|
|
1290
|
+
const context = new Context(client, options);
|
|
1291
|
+
const eventSourceLike = client;
|
|
1292
|
+
if (typeof eventSourceLike.start === "function") context.installEventSource(eventSourceLike);
|
|
1293
|
+
return context;
|
|
1072
1294
|
}
|
|
1073
1295
|
};
|
|
1074
1296
|
//#endregion
|
|
@@ -1226,7 +1448,7 @@ function definePlugin(plugin) {
|
|
|
1226
1448
|
//#endregion
|
|
1227
1449
|
//#region src/protocol/types.ts
|
|
1228
1450
|
const milkyVersion = "1.3";
|
|
1229
|
-
const milkyPackageVersion = "1.3.0-rc.
|
|
1451
|
+
const milkyPackageVersion = "1.3.0-rc.2";
|
|
1230
1452
|
//#endregion
|
|
1231
1453
|
//#region src/routing/parameter.ts
|
|
1232
1454
|
var Parameter = class Parameter {
|
|
@@ -1352,4 +1574,4 @@ let param;
|
|
|
1352
1574
|
_param.segment = segment;
|
|
1353
1575
|
})(param || (param = {}));
|
|
1354
1576
|
//#endregion
|
|
1355
|
-
export { CommandBuilder, Context, Logger, Parameter, Router, combineLogHandlers, createMilkyClient, definePlugin, filter, implementsESNextDisposable, isDisposable, milkyPackageVersion, milkyVersion, msg, param, seg };
|
|
1577
|
+
export { CommandBuilder, Context, Logger, Parameter, Router, combineLogHandlers, createContextRouteActivationResolver, createMilkyClient, createMilkyWebSocketEventSource, defaultRouteActivationResolver, definePlugin, filter, implementsESNextDisposable, isDisposable, mergeRouteMeta, milkyPackageVersion, milkyVersion, msg, param, seg };
|