@fraqjs/fraq 0.12.1 → 0.13.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/index.d.mts +50 -41
- package/dist/index.mjs +575 -379
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -5674,18 +5674,42 @@ declare function createContextRouteActivationResolver(
|
|
|
5674
5674
|
): RouteActivationResolver;
|
|
5675
5675
|
//#endregion
|
|
5676
5676
|
//#region src/protocol/endpoint.d.ts
|
|
5677
|
+
type ApiEndpointName = keyof ApiEndpoints$1;
|
|
5677
5678
|
type RequiredKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? never : K }[keyof T];
|
|
5678
5679
|
type AllOptional<T> = RequiredKeys<T> extends never ? true : false;
|
|
5679
|
-
type RawApiEndpoint<E extends
|
|
5680
|
+
type RawApiEndpoint<E extends ApiEndpointName> = {
|
|
5680
5681
|
request: ApiEndpoints$1[E]['request_ZodInput'] extends null ? null : ApiEndpoints$1[E]['request_ZodInput'];
|
|
5681
5682
|
response: ApiEndpoints$1[E]['response'] extends null ? null : ApiEndpoints$1[E]['response'];
|
|
5682
5683
|
};
|
|
5683
|
-
type
|
|
5684
|
-
|
|
5684
|
+
type ApiRequest<E extends ApiEndpointName> = RawApiEndpoint<E>['request'];
|
|
5685
|
+
type ApiParams<E extends ApiEndpointName> = RawApiEndpoint<E>['request'] extends null
|
|
5686
|
+
? undefined
|
|
5685
5687
|
: AllOptional<RawApiEndpoint<E>['request']> extends true
|
|
5686
|
-
?
|
|
5687
|
-
:
|
|
5688
|
-
type
|
|
5688
|
+
? RawApiEndpoint<E>['request'] | undefined
|
|
5689
|
+
: RawApiEndpoint<E>['request'];
|
|
5690
|
+
type ApiResponse<E extends ApiEndpointName> = RawApiEndpoint<E>['response'];
|
|
5691
|
+
interface ApiCall<E extends ApiEndpointName = ApiEndpointName> {
|
|
5692
|
+
endpoint: E;
|
|
5693
|
+
params: ApiParams<E>;
|
|
5694
|
+
}
|
|
5695
|
+
type ApiNext<E extends ApiEndpointName> =
|
|
5696
|
+
undefined extends ApiParams<E>
|
|
5697
|
+
? (params?: ApiParams<E>) => Promise<ApiResponse<E>>
|
|
5698
|
+
: (params: ApiParams<E>) => Promise<ApiResponse<E>>;
|
|
5699
|
+
type ApiHook<E extends ApiEndpointName> = (
|
|
5700
|
+
params: ApiParams<E>,
|
|
5701
|
+
next: ApiNext<E>,
|
|
5702
|
+
call: ApiCall<E>,
|
|
5703
|
+
) => ApiResponse<E> | Promise<ApiResponse<E>>;
|
|
5704
|
+
type AnyApiCall = { [E in ApiEndpointName]: ApiCall<E> }[ApiEndpointName];
|
|
5705
|
+
type AnyApiNext = (params?: unknown) => Promise<unknown>;
|
|
5706
|
+
type AnyApiHook = (call: AnyApiCall, next: AnyApiNext) => unknown | Promise<unknown>;
|
|
5707
|
+
type ApiEndpointFunction<E extends ApiEndpointName> = RawApiEndpoint<E>['request'] extends null
|
|
5708
|
+
? () => Promise<ApiResponse<E>>
|
|
5709
|
+
: AllOptional<RawApiEndpoint<E>['request']> extends true
|
|
5710
|
+
? (params?: RawApiEndpoint<E>['request']) => Promise<ApiResponse<E>>
|
|
5711
|
+
: (params: RawApiEndpoint<E>['request']) => Promise<ApiResponse<E>>;
|
|
5712
|
+
type ApiEndpoints = { [E in ApiEndpointName]: ApiEndpointFunction<E> };
|
|
5689
5713
|
type EventMap = {
|
|
5690
5714
|
[K in Event['event_type']]: Extract<
|
|
5691
5715
|
Event,
|
|
@@ -5804,7 +5828,7 @@ declare function definePlugin<
|
|
|
5804
5828
|
OI extends Injection | undefined,
|
|
5805
5829
|
>(plugin: Plugin<T, I, OI>): Plugin<T, I, OI>;
|
|
5806
5830
|
//#endregion
|
|
5807
|
-
//#region src/core/context.d.ts
|
|
5831
|
+
//#region src/core/context/index.d.ts
|
|
5808
5832
|
interface ContextOptions {
|
|
5809
5833
|
reconnect?: {
|
|
5810
5834
|
initialDelayMs?: number;
|
|
@@ -5818,26 +5842,23 @@ interface ContextUrlOptions {
|
|
|
5818
5842
|
installEventSource?: boolean;
|
|
5819
5843
|
}
|
|
5820
5844
|
declare class Context {
|
|
5821
|
-
readonly client: MilkyClient;
|
|
5822
5845
|
readonly router: Router;
|
|
5823
5846
|
readonly logger: Logger;
|
|
5824
5847
|
readonly name: string;
|
|
5825
5848
|
readonly routeActivationResolver: RouteActivationResolver;
|
|
5849
|
+
readonly client: MilkyClient;
|
|
5850
|
+
private readonly baseClient;
|
|
5826
5851
|
private readonly parent?;
|
|
5827
5852
|
private readonly filter?;
|
|
5828
5853
|
private readonly eventBus;
|
|
5829
|
-
private readonly plugins;
|
|
5830
|
-
private readonly services;
|
|
5831
5854
|
private readonly subContexts;
|
|
5832
|
-
private readonly eventSourceRuntimes;
|
|
5833
5855
|
private readonly parentEventForwarder?;
|
|
5834
|
-
private readonly timers;
|
|
5835
|
-
private readonly initialReconnectDelayMs;
|
|
5836
|
-
private readonly maxReconnectDelayMs;
|
|
5837
5856
|
private readonly logHandler?;
|
|
5838
|
-
private
|
|
5839
|
-
private
|
|
5840
|
-
private
|
|
5857
|
+
private readonly plugins;
|
|
5858
|
+
private readonly apiHooks;
|
|
5859
|
+
private readonly eventSources;
|
|
5860
|
+
private readonly timers;
|
|
5861
|
+
private readonly lifecycle;
|
|
5841
5862
|
private constructor();
|
|
5842
5863
|
on<K extends keyof EventMap>(type: K, handler: (event: EventMap[K]) => void | Promise<void>): () => void;
|
|
5843
5864
|
install<T extends ParameterList, I extends Injection | undefined, OI extends Injection | undefined>(
|
|
@@ -5845,6 +5866,8 @@ declare class Context {
|
|
|
5845
5866
|
...args: T
|
|
5846
5867
|
): void;
|
|
5847
5868
|
installEventSource(eventSource: MilkyEventSource): void;
|
|
5869
|
+
hookApi<E extends ApiEndpointName>(endpoint: E, hook: ApiHook<E>): () => void;
|
|
5870
|
+
hookApi(hook: AnyApiHook): () => void;
|
|
5848
5871
|
provide<T extends object>(service: ServiceClass<T>, instance: T): void;
|
|
5849
5872
|
resolve<T extends object>(service: ServiceClass<T>): T;
|
|
5850
5873
|
tryResolve<T extends object>(service: ServiceClass<T>): T | undefined;
|
|
@@ -5855,30 +5878,6 @@ declare class Context {
|
|
|
5855
5878
|
createSession(selfId: number, message: IncomingMessage): Session;
|
|
5856
5879
|
start(): Promise<void>;
|
|
5857
5880
|
stop(): Promise<void>;
|
|
5858
|
-
private startInternal;
|
|
5859
|
-
private getState;
|
|
5860
|
-
private stopInternal;
|
|
5861
|
-
private assertCanScheduleTimer;
|
|
5862
|
-
private runTimerCallback;
|
|
5863
|
-
private clearTimers;
|
|
5864
|
-
private stopEventSources;
|
|
5865
|
-
private acceptsParentEvent;
|
|
5866
|
-
private applyPlugins;
|
|
5867
|
-
private recursiveApplyPlugins;
|
|
5868
|
-
private startPlugins;
|
|
5869
|
-
private recursiveStartPlugins;
|
|
5870
|
-
private sortPlugins;
|
|
5871
|
-
private collectPendingProvidedServices;
|
|
5872
|
-
private areRequiredServicesAvailable;
|
|
5873
|
-
private areOptionalServicesReady;
|
|
5874
|
-
private collectAvailableServices;
|
|
5875
|
-
private createUnresolvablePluginError;
|
|
5876
|
-
private getPluginContext;
|
|
5877
|
-
private createProxyContextForPlugin;
|
|
5878
|
-
private startEventSource;
|
|
5879
|
-
private runEventSource;
|
|
5880
|
-
private waitForReconnectDelay;
|
|
5881
|
-
private resolveReconnectDelay;
|
|
5882
5881
|
static fromUrl(baseUrl: string | URL, options?: ContextOptions & ContextUrlOptions): Context;
|
|
5883
5882
|
static fromClient(client: MilkyClient, options?: ContextOptions): Context;
|
|
5884
5883
|
}
|
|
@@ -5924,7 +5923,17 @@ declare namespace seg {
|
|
|
5924
5923
|
}
|
|
5925
5924
|
//#endregion
|
|
5926
5925
|
export {
|
|
5926
|
+
AnyApiCall,
|
|
5927
|
+
AnyApiHook,
|
|
5928
|
+
AnyApiNext,
|
|
5929
|
+
ApiCall,
|
|
5930
|
+
ApiEndpointName,
|
|
5927
5931
|
ApiEndpoints,
|
|
5932
|
+
ApiHook,
|
|
5933
|
+
ApiNext,
|
|
5934
|
+
ApiParams,
|
|
5935
|
+
ApiRequest,
|
|
5936
|
+
ApiResponse,
|
|
5928
5937
|
Capturer,
|
|
5929
5938
|
Command,
|
|
5930
5939
|
CommandBuilder,
|
package/dist/index.mjs
CHANGED
|
@@ -75,7 +75,7 @@ function uniqueTags(...tagLists) {
|
|
|
75
75
|
}
|
|
76
76
|
//#endregion
|
|
77
77
|
//#region src/routing/tokenizer.ts
|
|
78
|
-
const WHITESPACE_CHARS = new Set([
|
|
78
|
+
const WHITESPACE_CHARS = /* @__PURE__ */ new Set([
|
|
79
79
|
" ",
|
|
80
80
|
" ",
|
|
81
81
|
"\n",
|
|
@@ -314,12 +314,16 @@ var Router = class Router {
|
|
|
314
314
|
}
|
|
315
315
|
match(session, message) {
|
|
316
316
|
for (const branch of this.branchesFrom(session, [], { includeHidden: true })) {
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
317
|
+
const literalActivationIndex = branch.type === "rawPattern" ? Object.values(branch.rawPattern.pattern).findIndex((parameter) => parameter.capturer.typeInstruction.type === "literal") : -1;
|
|
318
|
+
let activationInputs = DEFAULT_ACTIVATIONS;
|
|
319
|
+
if (branch.type === "command" || literalActivationIndex !== -1) {
|
|
320
|
+
const descriptor = this.describeBranch(branch);
|
|
321
|
+
const activations = this.activationResolver(descriptor, session) ?? DEFAULT_ACTIVATIONS;
|
|
322
|
+
activationInputs = Array.isArray(activations) ? activations : [activations];
|
|
323
|
+
}
|
|
324
|
+
for (const activation of activationInputs) {
|
|
320
325
|
const tokenizer = new Tokenizer(message.segments);
|
|
321
|
-
|
|
322
|
-
const match = this.matchBranch(branch, tokenizer, activation);
|
|
326
|
+
const match = this.matchBranch(branch, tokenizer, activation, session, literalActivationIndex);
|
|
323
327
|
if (match !== void 0) return match;
|
|
324
328
|
}
|
|
325
329
|
}
|
|
@@ -366,11 +370,12 @@ var Router = class Router {
|
|
|
366
370
|
case "prefix": return tokenizer.consumeTextPrefix(activation.prefix);
|
|
367
371
|
}
|
|
368
372
|
}
|
|
369
|
-
matchBranch(branch, tokenizer, activation) {
|
|
373
|
+
matchBranch(branch, tokenizer, activation, session, literalActivationIndex) {
|
|
374
|
+
if (branch.type === "command" && !this.consumeActivation(tokenizer, activation, session)) return;
|
|
370
375
|
if (!this.matchPath(branch.path, tokenizer)) return;
|
|
371
376
|
switch (branch.type) {
|
|
372
377
|
case "command": return this.matchCommand(branch.command, tokenizer, branch.path, activation);
|
|
373
|
-
case "rawPattern": return this.matchRawPattern(branch.rawPattern, tokenizer, branch.path, activation);
|
|
378
|
+
case "rawPattern": return this.matchRawPattern(branch.rawPattern, tokenizer, branch.path, activation, session, literalActivationIndex);
|
|
374
379
|
}
|
|
375
380
|
}
|
|
376
381
|
matchPath(path, tokenizer) {
|
|
@@ -395,9 +400,15 @@ var Router = class Router {
|
|
|
395
400
|
activation
|
|
396
401
|
};
|
|
397
402
|
}
|
|
398
|
-
matchRawPattern(rawPattern, tokenizer, path, activation) {
|
|
399
|
-
const params =
|
|
400
|
-
|
|
403
|
+
matchRawPattern(rawPattern, tokenizer, path, activation, session, literalActivationIndex) {
|
|
404
|
+
const params = {};
|
|
405
|
+
for (const [index, [name, parameter]] of Object.entries(rawPattern.pattern).entries()) {
|
|
406
|
+
if (index === literalActivationIndex && !this.consumeActivation(tokenizer, activation, session)) return;
|
|
407
|
+
const value = parameter.capturer.capture(tokenizer);
|
|
408
|
+
if (value === void 0) return;
|
|
409
|
+
params[name] = value;
|
|
410
|
+
}
|
|
411
|
+
if (tokenizer.hasNext()) return;
|
|
401
412
|
return {
|
|
402
413
|
type: "rawPattern",
|
|
403
414
|
path: [...path],
|
|
@@ -465,7 +476,6 @@ var Router = class Router {
|
|
|
465
476
|
validatePattern(pattern, options) {
|
|
466
477
|
const entries = Object.entries(pattern);
|
|
467
478
|
if (options?.rawPattern && entries.length === 0) throw new Error("Raw pattern must have at least one parameter.");
|
|
468
|
-
if (options?.rawPattern && entries[0]?.[1].capturer.typeInstruction.type === "literal") throw new Error("The first parameter of a raw pattern cannot be a literal, as it would conflict with command patterns.");
|
|
469
479
|
const catchAllEntryIndex = entries.findIndex(([, parameter]) => {
|
|
470
480
|
return parameter.capturer.typeInstruction.type === "catchAll";
|
|
471
481
|
});
|
|
@@ -485,12 +495,6 @@ var Router = class Router {
|
|
|
485
495
|
return params;
|
|
486
496
|
}
|
|
487
497
|
};
|
|
488
|
-
function routeActivationInputs(activations) {
|
|
489
|
-
return isRouteActivationArray(activations) ? activations : [activations];
|
|
490
|
-
}
|
|
491
|
-
function isRouteActivationArray(activations) {
|
|
492
|
-
return Array.isArray(activations);
|
|
493
|
-
}
|
|
494
498
|
//#endregion
|
|
495
499
|
//#region src/core/activation.ts
|
|
496
500
|
function createContextRouteActivationResolver(routing, fallback = defaultRouteActivationResolver) {
|
|
@@ -502,7 +506,8 @@ function createContextRouteActivationResolver(routing, fallback = defaultRouteAc
|
|
|
502
506
|
}
|
|
503
507
|
function compileContextRouteActivationConfig(config, fallback) {
|
|
504
508
|
return (route, session) => {
|
|
505
|
-
|
|
509
|
+
const rules = config.rules === void 0 ? [] : Array.isArray(config.rules) ? config.rules : [config.rules];
|
|
510
|
+
for (const rule of rules) {
|
|
506
511
|
if (!matchesContextRouteActivationRule(route, session, rule)) continue;
|
|
507
512
|
const activation = resolveContextRouteActivation(rule.activation, session.raw.message_scene);
|
|
508
513
|
if (activation !== void 0) return activation;
|
|
@@ -514,22 +519,10 @@ function compileContextRouteActivationConfig(config, fallback) {
|
|
|
514
519
|
return fallback(route, session);
|
|
515
520
|
};
|
|
516
521
|
}
|
|
517
|
-
function contextRouteActivationRules(rules) {
|
|
518
|
-
if (rules === void 0) return [];
|
|
519
|
-
return isContextRouteActivationRuleArray(rules) ? rules : [rules];
|
|
520
|
-
}
|
|
521
|
-
function isContextRouteActivationRuleArray(rules) {
|
|
522
|
-
return Array.isArray(rules);
|
|
523
|
-
}
|
|
524
522
|
function resolveContextRouteActivation(activation, scene) {
|
|
525
|
-
if (
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
function isRouteActivationInput(activation) {
|
|
529
|
-
return Array.isArray(activation) || isRouteActivation(activation);
|
|
530
|
-
}
|
|
531
|
-
function isRouteActivation(activation) {
|
|
532
|
-
return "type" in activation;
|
|
523
|
+
if (Array.isArray(activation) || "type" in activation) return activation;
|
|
524
|
+
const activationByScene = activation;
|
|
525
|
+
return activationByScene[scene] ?? activationByScene.default;
|
|
533
526
|
}
|
|
534
527
|
function matchesContextRouteActivationRule(route, session, rule) {
|
|
535
528
|
const match = rule.match;
|
|
@@ -808,190 +801,235 @@ function combineLogHandlers(...handlers) {
|
|
|
808
801
|
};
|
|
809
802
|
}
|
|
810
803
|
//#endregion
|
|
811
|
-
//#region src/core/
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
804
|
+
//#region src/core/context/api-hooks.ts
|
|
805
|
+
var ApiHookRegistry = class {
|
|
806
|
+
baseClient;
|
|
807
|
+
parent;
|
|
808
|
+
contextName;
|
|
809
|
+
getState;
|
|
810
|
+
client;
|
|
811
|
+
entries = [];
|
|
812
|
+
constructor(baseClient, parent, contextName, getState) {
|
|
813
|
+
this.baseClient = baseClient;
|
|
814
|
+
this.parent = parent;
|
|
815
|
+
this.contextName = contextName;
|
|
816
|
+
this.getState = getState;
|
|
817
|
+
this.client = this.createHookClient();
|
|
818
|
+
}
|
|
819
|
+
register(endpointOrHook, hook) {
|
|
820
|
+
const state = this.getState();
|
|
821
|
+
if (state === "stopping") throw new Error(`Context "${this.contextName}" cannot register API hooks while it is stopping.`);
|
|
822
|
+
if (state === "stopped") throw new Error(`Context "${this.contextName}" cannot register API hooks after it has stopped.`);
|
|
823
|
+
let entry;
|
|
824
|
+
if (typeof endpointOrHook === "function") entry = { hook: (params, next, call) => {
|
|
825
|
+
return endpointOrHook(call, (nextParams = params) => next(nextParams));
|
|
826
|
+
} };
|
|
827
|
+
else {
|
|
828
|
+
if (!hook) throw new Error(`API hook for endpoint ${endpointOrHook} is missing a handler.`);
|
|
829
|
+
entry = {
|
|
830
|
+
endpoint: endpointOrHook,
|
|
831
|
+
hook
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
this.entries.push(entry);
|
|
835
|
+
let disposed = false;
|
|
836
|
+
return () => {
|
|
837
|
+
if (disposed) return;
|
|
838
|
+
disposed = true;
|
|
839
|
+
const index = this.entries.indexOf(entry);
|
|
840
|
+
if (index !== -1) this.entries.splice(index, 1);
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
clear() {
|
|
844
|
+
this.entries.length = 0;
|
|
845
|
+
}
|
|
846
|
+
createHookClient() {
|
|
847
|
+
return new Proxy(this.baseClient, { get: (target, prop, receiver) => {
|
|
848
|
+
if (typeof prop === "string" && prop.includes("_")) return (params) => this.callHookedApi(prop, params);
|
|
849
|
+
return Reflect.get(target, prop, receiver);
|
|
850
|
+
} });
|
|
851
|
+
}
|
|
852
|
+
async callHookedApi(endpoint, params) {
|
|
853
|
+
const hooks = this.collectApiHooks(endpoint);
|
|
854
|
+
const dispatch = async (index, currentParams) => {
|
|
855
|
+
const hook = hooks[index];
|
|
856
|
+
if (!hook) return await this.callBaseApi(endpoint, currentParams);
|
|
857
|
+
let nextCalled = false;
|
|
858
|
+
return await hook(currentParams, async (nextParams = currentParams) => {
|
|
859
|
+
if (nextCalled) throw new Error(`API hook for endpoint ${endpoint} called next() multiple times.`);
|
|
860
|
+
nextCalled = true;
|
|
861
|
+
return await dispatch(index + 1, nextParams);
|
|
862
|
+
}, {
|
|
863
|
+
endpoint,
|
|
864
|
+
params: currentParams
|
|
865
|
+
});
|
|
866
|
+
};
|
|
867
|
+
return await dispatch(0, params);
|
|
868
|
+
}
|
|
869
|
+
collectApiHooks(endpoint) {
|
|
870
|
+
const hooks = [];
|
|
871
|
+
let registry = this;
|
|
872
|
+
while (registry) {
|
|
873
|
+
for (const entry of registry.entries.toReversed()) if (entry.endpoint === void 0 || entry.endpoint === endpoint) hooks.push(entry.hook);
|
|
874
|
+
registry = registry.parent;
|
|
875
|
+
}
|
|
876
|
+
return hooks;
|
|
877
|
+
}
|
|
878
|
+
async callBaseApi(endpoint, params) {
|
|
879
|
+
const callApi = this.baseClient.callApi;
|
|
880
|
+
if (typeof callApi === "function") return await callApi.call(this.baseClient, endpoint, params);
|
|
881
|
+
const method = this.baseClient[endpoint];
|
|
882
|
+
if (typeof method !== "function") throw new Error(`Milky client does not implement API endpoint ${endpoint}.`);
|
|
883
|
+
return await method.call(this.baseClient, params);
|
|
884
|
+
}
|
|
885
|
+
};
|
|
818
886
|
//#endregion
|
|
819
|
-
//#region src/core/context.ts
|
|
887
|
+
//#region src/core/context/event-sources.ts
|
|
820
888
|
const DEFAULT_INITIAL_RECONNECT_DELAY_MS = 1e3;
|
|
821
889
|
const DEFAULT_MAX_RECONNECT_DELAY_MS = 3e4;
|
|
822
|
-
var
|
|
823
|
-
client;
|
|
824
|
-
router = new Router();
|
|
890
|
+
var EventSourceRegistry = class {
|
|
825
891
|
logger;
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
filter;
|
|
830
|
-
eventBus = mitt();
|
|
831
|
-
plugins = [];
|
|
832
|
-
services = /* @__PURE__ */ new Map();
|
|
833
|
-
subContexts = /* @__PURE__ */ new Map();
|
|
834
|
-
eventSourceRuntimes = /* @__PURE__ */ new Map();
|
|
835
|
-
parentEventForwarder;
|
|
836
|
-
timers = /* @__PURE__ */ new Set();
|
|
892
|
+
getState;
|
|
893
|
+
emit;
|
|
894
|
+
runtimes = /* @__PURE__ */ new Map();
|
|
837
895
|
initialReconnectDelayMs;
|
|
838
896
|
maxReconnectDelayMs;
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
this.
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
this.
|
|
848
|
-
this.
|
|
849
|
-
this.
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
this.
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
897
|
+
constructor(options, logger, getState, emit) {
|
|
898
|
+
this.logger = logger;
|
|
899
|
+
this.getState = getState;
|
|
900
|
+
this.emit = emit;
|
|
901
|
+
this.initialReconnectDelayMs = options?.initialDelayMs ?? DEFAULT_INITIAL_RECONNECT_DELAY_MS;
|
|
902
|
+
this.maxReconnectDelayMs = options?.maxDelayMs ?? DEFAULT_MAX_RECONNECT_DELAY_MS;
|
|
903
|
+
}
|
|
904
|
+
install(eventSource) {
|
|
905
|
+
if (this.runtimes.has(eventSource)) return;
|
|
906
|
+
this.runtimes.set(eventSource, {});
|
|
907
|
+
if (this.getState() === "started") this.startEventSource(eventSource);
|
|
908
|
+
}
|
|
909
|
+
startAll() {
|
|
910
|
+
for (const eventSource of this.runtimes.keys()) this.startEventSource(eventSource);
|
|
911
|
+
}
|
|
912
|
+
async stop() {
|
|
913
|
+
const errors = [];
|
|
914
|
+
for (const runtime of this.runtimes.values()) {
|
|
915
|
+
this.resolveReconnectDelay(runtime);
|
|
916
|
+
const subscription = runtime.subscription;
|
|
917
|
+
if (!subscription) continue;
|
|
918
|
+
try {
|
|
919
|
+
await subscription.stop();
|
|
920
|
+
} catch (error) {
|
|
921
|
+
errors.push(error);
|
|
922
|
+
}
|
|
923
|
+
runtime.subscription = void 0;
|
|
860
924
|
}
|
|
861
|
-
|
|
925
|
+
for (const runtime of this.runtimes.values()) {
|
|
926
|
+
if (!runtime.task) continue;
|
|
862
927
|
try {
|
|
863
|
-
|
|
864
|
-
await this.router.dispatch(this.createSession(self_id, message), message);
|
|
928
|
+
await runtime.task;
|
|
865
929
|
} catch (error) {
|
|
866
|
-
|
|
930
|
+
errors.push(error);
|
|
931
|
+
} finally {
|
|
932
|
+
runtime.task = void 0;
|
|
867
933
|
}
|
|
868
|
-
}
|
|
934
|
+
}
|
|
935
|
+
return errors;
|
|
869
936
|
}
|
|
870
|
-
|
|
871
|
-
const
|
|
937
|
+
startEventSource(eventSource) {
|
|
938
|
+
const runtime = this.runtimes.get(eventSource);
|
|
939
|
+
if (!runtime) return;
|
|
940
|
+
runtime.task = this.runEventSource(eventSource, runtime);
|
|
941
|
+
}
|
|
942
|
+
async runEventSource(eventSource, runtime) {
|
|
943
|
+
let reconnectDelay = this.initialReconnectDelayMs;
|
|
944
|
+
let reconnectAttempt = 1;
|
|
945
|
+
while (this.getState() === "started") {
|
|
872
946
|
try {
|
|
873
|
-
|
|
874
|
-
await
|
|
947
|
+
this.logger.debug(`Connecting ${eventSource.name ?? "event source"} (attempt=${reconnectAttempt})`);
|
|
948
|
+
const subscription = await eventSource.start((event) => {
|
|
949
|
+
try {
|
|
950
|
+
if (this.getState() !== "started") return;
|
|
951
|
+
this.emit(event);
|
|
952
|
+
} catch (error) {
|
|
953
|
+
this.logger.error("Error handling event stream event", error);
|
|
954
|
+
}
|
|
955
|
+
});
|
|
956
|
+
if (this.getState() !== "started") {
|
|
957
|
+
await subscription.stop();
|
|
958
|
+
break;
|
|
959
|
+
}
|
|
960
|
+
runtime.subscription = subscription;
|
|
961
|
+
this.logger.info(`${eventSource.name ?? "Event source"} connected`);
|
|
962
|
+
reconnectDelay = this.initialReconnectDelayMs;
|
|
963
|
+
reconnectAttempt = 1;
|
|
964
|
+
await subscription.closed;
|
|
965
|
+
if (runtime.subscription === subscription) runtime.subscription = void 0;
|
|
966
|
+
if (this.getState() !== "started") break;
|
|
967
|
+
this.logger.warn(`${eventSource.name ?? "Event source"} disconnected; reconnecting in ${reconnectDelay}ms`);
|
|
875
968
|
} catch (error) {
|
|
876
|
-
this.
|
|
969
|
+
if (this.getState() !== "started") break;
|
|
970
|
+
this.logger.error(`Error connecting ${eventSource.name ?? "event source"}; reconnecting in ${reconnectDelay}ms`, error);
|
|
877
971
|
}
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
};
|
|
972
|
+
await this.waitForReconnectDelay(runtime, reconnectDelay);
|
|
973
|
+
reconnectDelay = Math.min(reconnectDelay * 2, this.maxReconnectDelayMs);
|
|
974
|
+
reconnectAttempt += 1;
|
|
975
|
+
}
|
|
883
976
|
}
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
977
|
+
waitForReconnectDelay(runtime, delay) {
|
|
978
|
+
return new Promise((resolve) => {
|
|
979
|
+
runtime.resolveReconnectTimer = resolve;
|
|
980
|
+
runtime.reconnectTimer = setTimeout(() => {
|
|
981
|
+
runtime.reconnectTimer = void 0;
|
|
982
|
+
runtime.resolveReconnectTimer = void 0;
|
|
983
|
+
resolve();
|
|
984
|
+
}, delay);
|
|
888
985
|
});
|
|
889
986
|
}
|
|
890
|
-
|
|
891
|
-
if (
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
}
|
|
895
|
-
provide(service, instance) {
|
|
896
|
-
if (this.services.has(service)) throw new Error(`Service ${service.name} has already been provided in this context.`);
|
|
897
|
-
if (implementsESNextDisposable(instance) && !isDisposable(instance)) throw new Error(`
|
|
898
|
-
Service ${service.name} implements ESNext Disposable but not Fraq Disposable.
|
|
899
|
-
Please explicitly import the interface like this:
|
|
900
|
-
|
|
901
|
-
import type { Disposable } from '@fraqjs/fraq';
|
|
902
|
-
|
|
903
|
-
and implement the dispose method to clean up resources when the context stops.
|
|
904
|
-
`.trim());
|
|
905
|
-
this.services.set(service, instance);
|
|
906
|
-
}
|
|
907
|
-
resolve(service) {
|
|
908
|
-
const instance = this.tryResolve(service);
|
|
909
|
-
if (instance === void 0) throw new Error(`Service ${service.name} has not been provided.`);
|
|
910
|
-
return instance;
|
|
911
|
-
}
|
|
912
|
-
tryResolve(service) {
|
|
913
|
-
if (this.services.has(service)) return this.services.get(service);
|
|
914
|
-
return this.parent?.tryResolve(service);
|
|
915
|
-
}
|
|
916
|
-
isProvided(service) {
|
|
917
|
-
return this.tryResolve(service) !== void 0;
|
|
918
|
-
}
|
|
919
|
-
fork(name, filter) {
|
|
920
|
-
if (this.subContexts.has(name)) {
|
|
921
|
-
if (filter) throw new Error(`Sub context "${name}" already exists, so the provided filter cannot be applied. Please use fork('${name}') without a filter to get the existing subcontext.`);
|
|
922
|
-
return this.subContexts.get(name);
|
|
987
|
+
resolveReconnectDelay(runtime) {
|
|
988
|
+
if (runtime.reconnectTimer) {
|
|
989
|
+
clearTimeout(runtime.reconnectTimer);
|
|
990
|
+
runtime.reconnectTimer = void 0;
|
|
923
991
|
}
|
|
924
|
-
const
|
|
925
|
-
|
|
926
|
-
|
|
992
|
+
const resolve = runtime.resolveReconnectTimer;
|
|
993
|
+
runtime.resolveReconnectTimer = void 0;
|
|
994
|
+
resolve?.();
|
|
927
995
|
}
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
996
|
+
};
|
|
997
|
+
//#endregion
|
|
998
|
+
//#region src/core/context/lifecycle.ts
|
|
999
|
+
var LifecycleManager = class {
|
|
1000
|
+
contextName;
|
|
1001
|
+
plugins;
|
|
1002
|
+
timers;
|
|
1003
|
+
eventSources;
|
|
1004
|
+
apiHooks;
|
|
1005
|
+
detachParentEvents;
|
|
1006
|
+
children = /* @__PURE__ */ new Set();
|
|
1007
|
+
currentState = "idle";
|
|
1008
|
+
startPromise;
|
|
1009
|
+
stopPromise;
|
|
1010
|
+
constructor(contextName, plugins, timers, eventSources, apiHooks, detachParentEvents) {
|
|
1011
|
+
this.contextName = contextName;
|
|
1012
|
+
this.plugins = plugins;
|
|
1013
|
+
this.timers = timers;
|
|
1014
|
+
this.eventSources = eventSources;
|
|
1015
|
+
this.apiHooks = apiHooks;
|
|
1016
|
+
this.detachParentEvents = detachParentEvents;
|
|
936
1017
|
}
|
|
937
|
-
|
|
938
|
-
this.
|
|
939
|
-
const interval = setInterval(() => {
|
|
940
|
-
this.runTimerCallback(callback);
|
|
941
|
-
}, intervalMs);
|
|
942
|
-
this.timers.add(interval);
|
|
943
|
-
return interval;
|
|
1018
|
+
get state() {
|
|
1019
|
+
return this.currentState;
|
|
944
1020
|
}
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
selfId,
|
|
948
|
-
raw: message,
|
|
949
|
-
reply: async (textOrSegments, options) => {
|
|
950
|
-
const actualSegments = [];
|
|
951
|
-
if (typeof textOrSegments === "string") actualSegments.push({
|
|
952
|
-
type: "text",
|
|
953
|
-
data: { text: textOrSegments }
|
|
954
|
-
});
|
|
955
|
-
else actualSegments.push(...textOrSegments);
|
|
956
|
-
if (options?.withMention && message.message_scene === "group") actualSegments.unshift(seg.mention(message.sender_id));
|
|
957
|
-
if (options?.withQuote) actualSegments.unshift(seg.reply(message.message_seq));
|
|
958
|
-
switch (message.message_scene) {
|
|
959
|
-
case "friend": {
|
|
960
|
-
const { message_seq } = await this.client.send_private_message({
|
|
961
|
-
user_id: message.peer_id,
|
|
962
|
-
message: actualSegments
|
|
963
|
-
});
|
|
964
|
-
return { messageSeq: message_seq };
|
|
965
|
-
}
|
|
966
|
-
case "group": {
|
|
967
|
-
const { message_seq } = await this.client.send_group_message({
|
|
968
|
-
group_id: message.peer_id,
|
|
969
|
-
message: actualSegments
|
|
970
|
-
});
|
|
971
|
-
return { messageSeq: message_seq };
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
return { messageSeq: 0 };
|
|
975
|
-
},
|
|
976
|
-
reaction: async (type, reactionId) => {
|
|
977
|
-
if (message.message_scene === "group") await this.client.send_group_message_reaction({
|
|
978
|
-
group_id: message.peer_id,
|
|
979
|
-
message_seq: message.message_seq,
|
|
980
|
-
reaction_type: type,
|
|
981
|
-
reaction: reactionId
|
|
982
|
-
});
|
|
983
|
-
}
|
|
984
|
-
};
|
|
1021
|
+
addChild(child) {
|
|
1022
|
+
this.children.add(child);
|
|
985
1023
|
}
|
|
986
1024
|
async start() {
|
|
987
|
-
if (this.
|
|
988
|
-
if (this.
|
|
1025
|
+
if (this.currentState === "started") return;
|
|
1026
|
+
if (this.currentState === "starting") {
|
|
989
1027
|
await this.startPromise;
|
|
990
1028
|
return;
|
|
991
1029
|
}
|
|
992
|
-
if (this.
|
|
993
|
-
if (this.
|
|
994
|
-
this.
|
|
1030
|
+
if (this.currentState === "stopping") throw new Error(`Context "${this.contextName}" cannot be started while it is stopping.`);
|
|
1031
|
+
if (this.currentState === "stopped") throw new Error(`Context "${this.contextName}" cannot be restarted after it has been stopped.`);
|
|
1032
|
+
this.currentState = "starting";
|
|
995
1033
|
this.startPromise = this.startInternal();
|
|
996
1034
|
try {
|
|
997
1035
|
await this.startPromise;
|
|
@@ -1000,20 +1038,23 @@ and implement the dispose method to clean up resources when the context stops.
|
|
|
1000
1038
|
}
|
|
1001
1039
|
}
|
|
1002
1040
|
async stop() {
|
|
1003
|
-
if (this.
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1041
|
+
if (this.currentState === "idle" && !this.timers.hasTimers || this.currentState === "stopped") return;
|
|
1042
|
+
let stateAfterStart = this.currentState;
|
|
1043
|
+
if (stateAfterStart === "starting") {
|
|
1044
|
+
await this.startPromise;
|
|
1045
|
+
stateAfterStart = this.currentState;
|
|
1046
|
+
}
|
|
1047
|
+
if (stateAfterStart === "idle" && !this.timers.hasTimers || stateAfterStart === "stopped") return;
|
|
1007
1048
|
if (stateAfterStart === "stopping") {
|
|
1008
1049
|
await this.stopPromise;
|
|
1009
1050
|
return;
|
|
1010
1051
|
}
|
|
1011
|
-
this.
|
|
1052
|
+
this.currentState = "stopping";
|
|
1012
1053
|
this.stopPromise = this.stopInternal();
|
|
1013
1054
|
try {
|
|
1014
1055
|
await this.stopPromise;
|
|
1015
1056
|
} finally {
|
|
1016
|
-
this.
|
|
1057
|
+
this.currentState = "stopped";
|
|
1017
1058
|
this.stopPromise = void 0;
|
|
1018
1059
|
}
|
|
1019
1060
|
}
|
|
@@ -1022,85 +1063,128 @@ and implement the dispose method to clean up resources when the context stops.
|
|
|
1022
1063
|
const startingContexts = [];
|
|
1023
1064
|
try {
|
|
1024
1065
|
await this.recursiveApplyPlugins(appliedContextPlugins, startingContexts);
|
|
1025
|
-
await
|
|
1066
|
+
for (const { lifecycle, sortedPlugins } of appliedContextPlugins) await lifecycle.plugins.start(sortedPlugins);
|
|
1026
1067
|
} catch (error) {
|
|
1027
|
-
for (const
|
|
1068
|
+
for (const lifecycle of startingContexts) if (lifecycle.currentState === "starting") lifecycle.currentState = "idle";
|
|
1028
1069
|
throw error;
|
|
1029
1070
|
}
|
|
1030
|
-
for (const {
|
|
1031
|
-
|
|
1032
|
-
|
|
1071
|
+
for (const { lifecycle } of appliedContextPlugins) {
|
|
1072
|
+
lifecycle.currentState = "started";
|
|
1073
|
+
lifecycle.eventSources.startAll();
|
|
1033
1074
|
}
|
|
1034
1075
|
}
|
|
1035
|
-
getState() {
|
|
1036
|
-
return this.state;
|
|
1037
|
-
}
|
|
1038
1076
|
async stopInternal() {
|
|
1039
1077
|
const errors = [];
|
|
1040
|
-
this.
|
|
1041
|
-
for (const
|
|
1042
|
-
await
|
|
1078
|
+
this.timers.clear();
|
|
1079
|
+
for (const child of [...this.children].reverse()) try {
|
|
1080
|
+
await child.stop();
|
|
1043
1081
|
} catch (error) {
|
|
1044
1082
|
errors.push(error);
|
|
1045
1083
|
}
|
|
1046
|
-
await this.
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
try {
|
|
1051
|
-
await service.dispose();
|
|
1052
|
-
} catch (error) {
|
|
1053
|
-
errors.push(error);
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1084
|
+
errors.push(...await this.eventSources.stop());
|
|
1085
|
+
this.detachParentEvents();
|
|
1086
|
+
errors.push(...await this.plugins.disposeServices());
|
|
1087
|
+
this.apiHooks.clear();
|
|
1056
1088
|
if (errors.length === 1) throw errors[0];
|
|
1057
|
-
if (errors.length > 1) throw new AggregateError(errors, `Context "${this.
|
|
1058
|
-
}
|
|
1059
|
-
assertCanScheduleTimer() {
|
|
1060
|
-
if (this.state === "stopping") throw new Error(`Context "${this.name}" cannot schedule timers while it is stopping.`);
|
|
1061
|
-
if (this.state === "stopped") throw new Error(`Context "${this.name}" cannot schedule timers after it has stopped.`);
|
|
1089
|
+
if (errors.length > 1) throw new AggregateError(errors, `Context "${this.contextName}" failed to stop cleanly.`);
|
|
1062
1090
|
}
|
|
1063
|
-
async
|
|
1064
|
-
if (this.
|
|
1065
|
-
|
|
1066
|
-
await
|
|
1067
|
-
|
|
1068
|
-
this.logger.error("Error handling timer callback", error);
|
|
1091
|
+
async recursiveApplyPlugins(appliedContextPlugins, startingContexts) {
|
|
1092
|
+
if (this.currentState === "started") return;
|
|
1093
|
+
if (this.currentState === "starting" && this.startPromise) {
|
|
1094
|
+
await this.startPromise;
|
|
1095
|
+
return;
|
|
1069
1096
|
}
|
|
1097
|
+
if (this.currentState === "stopping") throw new Error(`Context "${this.contextName}" cannot be started while it is stopping.`);
|
|
1098
|
+
if (this.currentState === "stopped") throw new Error(`Context "${this.contextName}" cannot be restarted after it has been stopped.`);
|
|
1099
|
+
if (this.currentState === "idle") this.currentState = "starting";
|
|
1100
|
+
startingContexts.push(this);
|
|
1101
|
+
const sortedPlugins = await this.plugins.apply();
|
|
1102
|
+
appliedContextPlugins.push({
|
|
1103
|
+
lifecycle: this,
|
|
1104
|
+
sortedPlugins
|
|
1105
|
+
});
|
|
1106
|
+
for (const child of this.children) await child.recursiveApplyPlugins(appliedContextPlugins, startingContexts);
|
|
1070
1107
|
}
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1108
|
+
};
|
|
1109
|
+
//#endregion
|
|
1110
|
+
//#region src/core/service.ts
|
|
1111
|
+
function isDisposable(service) {
|
|
1112
|
+
return "dispose" in service && typeof service.dispose === "function";
|
|
1113
|
+
}
|
|
1114
|
+
function implementsESNextDisposable(service) {
|
|
1115
|
+
return Symbol.dispose in service && typeof service[Symbol.dispose] === "function";
|
|
1116
|
+
}
|
|
1117
|
+
//#endregion
|
|
1118
|
+
//#region src/core/context/plugins.ts
|
|
1119
|
+
function areRequiredServicesAvailable(plugin, available) {
|
|
1120
|
+
return (plugin.requires ?? []).every((service) => available.has(service));
|
|
1121
|
+
}
|
|
1122
|
+
function areOptionalServicesReady(plugin, available, pendingProviders) {
|
|
1123
|
+
return (plugin.optionalRequires ?? []).every((service) => {
|
|
1124
|
+
if (available.has(service)) return true;
|
|
1125
|
+
const pendingProvider = pendingProviders.get(service);
|
|
1126
|
+
return pendingProvider === void 0 || pendingProvider.plugin === plugin;
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
function createUnresolvablePluginError(pending, available) {
|
|
1130
|
+
const missingRequirements = /* @__PURE__ */ new Map();
|
|
1131
|
+
const pendingProviders = /* @__PURE__ */ new Set();
|
|
1132
|
+
for (const { plugin } of pending) for (const service of plugin.provides ?? []) pendingProviders.add(service);
|
|
1133
|
+
for (const { plugin } of pending) for (const service of plugin.requires ?? []) if (!available.has(service)) {
|
|
1134
|
+
const plugins = missingRequirements.get(service) ?? [];
|
|
1135
|
+
plugins.push(plugin);
|
|
1136
|
+
missingRequirements.set(service, plugins);
|
|
1137
|
+
}
|
|
1138
|
+
const lines = [...missingRequirements].map(([service, plugins]) => {
|
|
1139
|
+
const dependents = plugins.map((plugin) => plugin.name).join(", ");
|
|
1140
|
+
const reason = pendingProviders.has(service) ? "blocked by a dependency cycle" : "no installed plugin provides it";
|
|
1141
|
+
return `${service.name} required by ${dependents} (${reason})`;
|
|
1142
|
+
});
|
|
1143
|
+
return /* @__PURE__ */ new Error(`Unable to resolve plugin service dependencies: ${lines.join("; ")}.`);
|
|
1144
|
+
}
|
|
1145
|
+
var PluginRegistry = class {
|
|
1146
|
+
context;
|
|
1147
|
+
parent;
|
|
1148
|
+
logHandler;
|
|
1149
|
+
plugins = [];
|
|
1150
|
+
services = /* @__PURE__ */ new Map();
|
|
1151
|
+
constructor(context, parent, logHandler) {
|
|
1152
|
+
this.context = context;
|
|
1153
|
+
this.parent = parent;
|
|
1154
|
+
this.logHandler = logHandler;
|
|
1074
1155
|
}
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
try {
|
|
1081
|
-
await subscription.stop();
|
|
1082
|
-
} catch (error) {
|
|
1083
|
-
errors.push(error);
|
|
1084
|
-
}
|
|
1085
|
-
runtime.subscription = void 0;
|
|
1086
|
-
}
|
|
1087
|
-
for (const runtime of this.eventSourceRuntimes.values()) {
|
|
1088
|
-
if (!runtime.task) continue;
|
|
1089
|
-
try {
|
|
1090
|
-
await runtime.task;
|
|
1091
|
-
} catch (error) {
|
|
1092
|
-
errors.push(error);
|
|
1093
|
-
} finally {
|
|
1094
|
-
runtime.task = void 0;
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1156
|
+
install(plugin, ...args) {
|
|
1157
|
+
this.plugins.push({
|
|
1158
|
+
plugin,
|
|
1159
|
+
args
|
|
1160
|
+
});
|
|
1097
1161
|
}
|
|
1098
|
-
|
|
1099
|
-
if (
|
|
1100
|
-
|
|
1101
|
-
|
|
1162
|
+
provide(service, instance) {
|
|
1163
|
+
if (this.services.has(service)) throw new Error(`Service ${service.name} has already been provided in this context.`);
|
|
1164
|
+
if (implementsESNextDisposable(instance) && !isDisposable(instance)) throw new Error(`
|
|
1165
|
+
Service ${service.name} implements ESNext Disposable but not Fraq Disposable.
|
|
1166
|
+
Please explicitly import the interface like this:
|
|
1167
|
+
|
|
1168
|
+
import type { Disposable } from '@fraqjs/fraq';
|
|
1169
|
+
|
|
1170
|
+
and implement the dispose method to clean up resources when the context stops.
|
|
1171
|
+
`.trim());
|
|
1172
|
+
this.services.set(service, instance);
|
|
1173
|
+
}
|
|
1174
|
+
resolve(service) {
|
|
1175
|
+
const instance = this.tryResolve(service);
|
|
1176
|
+
if (instance === void 0) throw new Error(`Service ${service.name} has not been provided.`);
|
|
1177
|
+
return instance;
|
|
1178
|
+
}
|
|
1179
|
+
tryResolve(service) {
|
|
1180
|
+
if (this.services.has(service)) return this.services.get(service);
|
|
1181
|
+
return this.parent?.tryResolve(service);
|
|
1102
1182
|
}
|
|
1103
|
-
|
|
1183
|
+
isProvided(service) {
|
|
1184
|
+
return this.tryResolve(service) !== void 0;
|
|
1185
|
+
}
|
|
1186
|
+
async apply() {
|
|
1187
|
+
const sortedPlugins = this.sortPlugins();
|
|
1104
1188
|
for (const installedPlugin of sortedPlugins) {
|
|
1105
1189
|
const { plugin, args } = installedPlugin;
|
|
1106
1190
|
const providedBeforeApply = new Set(this.services.keys());
|
|
@@ -1112,39 +1196,31 @@ and implement the dispose method to clean up resources when the context stops.
|
|
|
1112
1196
|
if (plugin.provides) for (const service of plugin.provides) providedServices.push(service.name);
|
|
1113
1197
|
if (requiredServices.length > 0) applyingMessage += `, requires: [${requiredServices.join(", ")}]`;
|
|
1114
1198
|
if (providedServices.length > 0) applyingMessage += `, provides: [${providedServices.join(", ")}]`;
|
|
1115
|
-
this.logger.info(applyingMessage);
|
|
1199
|
+
this.context.logger.info(applyingMessage);
|
|
1116
1200
|
await plugin.apply(this.getPluginContext(installedPlugin), ...args);
|
|
1117
1201
|
for (const service of plugin.provides ?? []) if (!this.services.has(service) || providedBeforeApply.has(service)) throw new Error(`${plugin.name} declares service ${service.name} but did not provide it.`);
|
|
1118
1202
|
}
|
|
1203
|
+
return sortedPlugins;
|
|
1119
1204
|
}
|
|
1120
|
-
async
|
|
1121
|
-
if (this.state === "started") return;
|
|
1122
|
-
if (this.state === "starting" && this.startPromise) {
|
|
1123
|
-
await this.startPromise;
|
|
1124
|
-
return;
|
|
1125
|
-
}
|
|
1126
|
-
if (this.state === "stopping") throw new Error(`Context "${this.name}" cannot be started while it is stopping.`);
|
|
1127
|
-
if (this.state === "stopped") throw new Error(`Context "${this.name}" cannot be restarted after it has been stopped.`);
|
|
1128
|
-
if (this.state === "idle") this.state = "starting";
|
|
1129
|
-
startingContexts.push(this);
|
|
1130
|
-
const sortedPlugins = this.sortPlugins();
|
|
1131
|
-
await this.applyPlugins(sortedPlugins);
|
|
1132
|
-
appliedContextPlugins.push({
|
|
1133
|
-
context: this,
|
|
1134
|
-
sortedPlugins
|
|
1135
|
-
});
|
|
1136
|
-
for (const subContext of this.subContexts.values()) await subContext.recursiveApplyPlugins(appliedContextPlugins, startingContexts);
|
|
1137
|
-
}
|
|
1138
|
-
async startPlugins(sortedPlugins) {
|
|
1205
|
+
async start(sortedPlugins) {
|
|
1139
1206
|
for (const installedPlugin of sortedPlugins) {
|
|
1140
1207
|
const { plugin } = installedPlugin;
|
|
1141
1208
|
if (!plugin.start) continue;
|
|
1142
|
-
this.logger.debug(`Plugin ${plugin.name} is starting...`);
|
|
1209
|
+
this.context.logger.debug(`Plugin ${plugin.name} is starting...`);
|
|
1143
1210
|
await plugin.start(this.getPluginContext(installedPlugin));
|
|
1144
1211
|
}
|
|
1145
1212
|
}
|
|
1146
|
-
async
|
|
1147
|
-
|
|
1213
|
+
async disposeServices() {
|
|
1214
|
+
const errors = [];
|
|
1215
|
+
for (const service of [...this.services.values()].reverse()) {
|
|
1216
|
+
if (!isDisposable(service)) continue;
|
|
1217
|
+
try {
|
|
1218
|
+
await service.dispose();
|
|
1219
|
+
} catch (error) {
|
|
1220
|
+
errors.push(error);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
return errors;
|
|
1148
1224
|
}
|
|
1149
1225
|
sortPlugins() {
|
|
1150
1226
|
const providers = /* @__PURE__ */ new Map();
|
|
@@ -1155,64 +1231,33 @@ and implement the dispose method to clean up resources when the context stops.
|
|
|
1155
1231
|
}
|
|
1156
1232
|
const pending = [...this.plugins];
|
|
1157
1233
|
const sorted = [];
|
|
1158
|
-
const available =
|
|
1159
|
-
for (const service of this.collectAvailableServices()) available.add(service);
|
|
1234
|
+
const available = new Set(this.collectAvailableServices());
|
|
1160
1235
|
while (pending.length > 0) {
|
|
1161
|
-
const availableByPendingPlugins =
|
|
1162
|
-
const
|
|
1163
|
-
const
|
|
1236
|
+
const availableByPendingPlugins = /* @__PURE__ */ new Map();
|
|
1237
|
+
for (const installedPlugin of pending) for (const service of installedPlugin.plugin.provides ?? []) availableByPendingPlugins.set(service, installedPlugin);
|
|
1238
|
+
const requiredReadyIndex = pending.findIndex(({ plugin }) => areRequiredServicesAvailable(plugin, available));
|
|
1239
|
+
const optionalReadyIndex = pending.findIndex(({ plugin }) => areRequiredServicesAvailable(plugin, available) && areOptionalServicesReady(plugin, available, availableByPendingPlugins));
|
|
1164
1240
|
const nextIndex = optionalReadyIndex === -1 ? requiredReadyIndex : optionalReadyIndex;
|
|
1165
|
-
if (nextIndex === -1) throw
|
|
1241
|
+
if (nextIndex === -1) throw createUnresolvablePluginError(pending, available);
|
|
1166
1242
|
const [next] = pending.splice(nextIndex, 1);
|
|
1167
1243
|
sorted.push(next);
|
|
1168
1244
|
for (const service of next.plugin.provides ?? []) available.add(service);
|
|
1169
1245
|
}
|
|
1170
1246
|
return sorted;
|
|
1171
1247
|
}
|
|
1172
|
-
collectPendingProvidedServices(pending) {
|
|
1173
|
-
const providedServices = /* @__PURE__ */ new Map();
|
|
1174
|
-
for (const installedPlugin of pending) for (const service of installedPlugin.plugin.provides ?? []) providedServices.set(service, installedPlugin);
|
|
1175
|
-
return providedServices;
|
|
1176
|
-
}
|
|
1177
|
-
areRequiredServicesAvailable(plugin, available) {
|
|
1178
|
-
return (plugin.requires ?? []).every((service) => available.has(service));
|
|
1179
|
-
}
|
|
1180
|
-
areOptionalServicesReady(plugin, available, pendingProviders) {
|
|
1181
|
-
return (plugin.optionalRequires ?? []).every((service) => {
|
|
1182
|
-
if (available.has(service)) return true;
|
|
1183
|
-
const pendingProvider = pendingProviders.get(service);
|
|
1184
|
-
return pendingProvider === void 0 || pendingProvider.plugin === plugin;
|
|
1185
|
-
});
|
|
1186
|
-
}
|
|
1187
1248
|
collectAvailableServices() {
|
|
1188
1249
|
const services = [...this.services.keys()];
|
|
1189
1250
|
if (this.parent) services.push(...this.parent.collectAvailableServices());
|
|
1190
1251
|
return services;
|
|
1191
1252
|
}
|
|
1192
|
-
createUnresolvablePluginError(pending, available) {
|
|
1193
|
-
const missingRequirements = /* @__PURE__ */ new Map();
|
|
1194
|
-
const pendingProviders = /* @__PURE__ */ new Set();
|
|
1195
|
-
for (const { plugin } of pending) for (const service of plugin.provides ?? []) pendingProviders.add(service);
|
|
1196
|
-
for (const { plugin } of pending) for (const service of plugin.requires ?? []) if (!available.has(service)) {
|
|
1197
|
-
const plugins = missingRequirements.get(service) ?? [];
|
|
1198
|
-
plugins.push(plugin);
|
|
1199
|
-
missingRequirements.set(service, plugins);
|
|
1200
|
-
}
|
|
1201
|
-
const lines = [...missingRequirements].map(([service, plugins]) => {
|
|
1202
|
-
const dependents = plugins.map((plugin) => plugin.name).join(", ");
|
|
1203
|
-
const reason = pendingProviders.has(service) ? "blocked by a dependency cycle" : "no installed plugin provides it";
|
|
1204
|
-
return `${service.name} required by ${dependents} (${reason})`;
|
|
1205
|
-
});
|
|
1206
|
-
return /* @__PURE__ */ new Error(`Unable to resolve plugin service dependencies: ${lines.join("; ")}.`);
|
|
1207
|
-
}
|
|
1208
1253
|
getPluginContext(installedPlugin) {
|
|
1209
1254
|
installedPlugin.proxy ??= this.createProxyContextForPlugin(installedPlugin.plugin);
|
|
1210
1255
|
return installedPlugin.proxy;
|
|
1211
1256
|
}
|
|
1212
1257
|
createProxyContextForPlugin(plugin) {
|
|
1213
|
-
const proxyLogger = new Logger((message) => this.logHandler?.(message), `plugin:${this.name ? `${this.name}/` : ""}${plugin.name}`);
|
|
1214
|
-
const proxyRouter = this.router.withMeta({
|
|
1215
|
-
context: this.name,
|
|
1258
|
+
const proxyLogger = new Logger((message) => this.logHandler?.(message), `plugin:${this.context.name ? `${this.context.name}/` : ""}${plugin.name}`);
|
|
1259
|
+
const proxyRouter = this.context.router.withMeta({
|
|
1260
|
+
context: this.context.name,
|
|
1216
1261
|
plugin: plugin.name
|
|
1217
1262
|
});
|
|
1218
1263
|
let proxyInjections;
|
|
@@ -1224,74 +1269,225 @@ and implement the dispose method to clean up resources when the context stops.
|
|
|
1224
1269
|
proxyInjections ??= {};
|
|
1225
1270
|
for (const [key, service] of Object.entries(plugin.optionalInject)) proxyInjections[key] = this.tryResolve(service);
|
|
1226
1271
|
}
|
|
1227
|
-
return new Proxy(this, { get(target, prop, receiver) {
|
|
1272
|
+
return new Proxy(this.context, { get(target, prop, receiver) {
|
|
1228
1273
|
if (prop === "logger") return proxyLogger;
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1274
|
+
if (prop === "router") return proxyRouter;
|
|
1275
|
+
if (proxyInjections && prop in proxyInjections) return proxyInjections[prop];
|
|
1276
|
+
return Reflect.get(target, prop, receiver);
|
|
1232
1277
|
} });
|
|
1233
1278
|
}
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1279
|
+
};
|
|
1280
|
+
//#endregion
|
|
1281
|
+
//#region src/core/context/timers.ts
|
|
1282
|
+
var TimerRegistry = class {
|
|
1283
|
+
contextName;
|
|
1284
|
+
logger;
|
|
1285
|
+
getState;
|
|
1286
|
+
timers = /* @__PURE__ */ new Set();
|
|
1287
|
+
constructor(contextName, logger, getState) {
|
|
1288
|
+
this.contextName = contextName;
|
|
1289
|
+
this.logger = logger;
|
|
1290
|
+
this.getState = getState;
|
|
1238
1291
|
}
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1292
|
+
get hasTimers() {
|
|
1293
|
+
return this.timers.size > 0;
|
|
1294
|
+
}
|
|
1295
|
+
timeout(delayMs, callback) {
|
|
1296
|
+
this.assertCanScheduleTimer();
|
|
1297
|
+
const timeout = setTimeout(() => {
|
|
1298
|
+
this.timers.delete(timeout);
|
|
1299
|
+
this.runTimerCallback(callback);
|
|
1300
|
+
}, delayMs);
|
|
1301
|
+
this.timers.add(timeout);
|
|
1302
|
+
return timeout;
|
|
1303
|
+
}
|
|
1304
|
+
interval(intervalMs, callback) {
|
|
1305
|
+
this.assertCanScheduleTimer();
|
|
1306
|
+
const interval = setInterval(() => {
|
|
1307
|
+
this.runTimerCallback(callback);
|
|
1308
|
+
}, intervalMs);
|
|
1309
|
+
this.timers.add(interval);
|
|
1310
|
+
return interval;
|
|
1311
|
+
}
|
|
1312
|
+
clear() {
|
|
1313
|
+
for (const timer of this.timers) clearTimeout(timer);
|
|
1314
|
+
this.timers.clear();
|
|
1315
|
+
}
|
|
1316
|
+
assertCanScheduleTimer() {
|
|
1317
|
+
const state = this.getState();
|
|
1318
|
+
if (state === "stopping") throw new Error(`Context "${this.contextName}" cannot schedule timers while it is stopping.`);
|
|
1319
|
+
if (state === "stopped") throw new Error(`Context "${this.contextName}" cannot schedule timers after it has stopped.`);
|
|
1320
|
+
}
|
|
1321
|
+
async runTimerCallback(callback) {
|
|
1322
|
+
const state = this.getState();
|
|
1323
|
+
if (state === "stopping" || state === "stopped") return;
|
|
1324
|
+
try {
|
|
1325
|
+
await callback();
|
|
1326
|
+
} catch (error) {
|
|
1327
|
+
this.logger.error("Error handling timer callback", error);
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
};
|
|
1331
|
+
//#endregion
|
|
1332
|
+
//#region src/core/context/index.ts
|
|
1333
|
+
var Context = class Context {
|
|
1334
|
+
router = new Router();
|
|
1335
|
+
logger;
|
|
1336
|
+
name;
|
|
1337
|
+
routeActivationResolver;
|
|
1338
|
+
client;
|
|
1339
|
+
baseClient;
|
|
1340
|
+
parent;
|
|
1341
|
+
filter;
|
|
1342
|
+
eventBus = mitt();
|
|
1343
|
+
subContexts = /* @__PURE__ */ new Map();
|
|
1344
|
+
parentEventForwarder;
|
|
1345
|
+
logHandler;
|
|
1346
|
+
plugins;
|
|
1347
|
+
apiHooks;
|
|
1348
|
+
eventSources;
|
|
1349
|
+
timers;
|
|
1350
|
+
lifecycle;
|
|
1351
|
+
constructor(baseClient, options, name, parent, filter) {
|
|
1352
|
+
this.baseClient = baseClient;
|
|
1353
|
+
this.logHandler = options?.logHandler ?? parent?.logHandler;
|
|
1354
|
+
this.name = name ?? "root";
|
|
1355
|
+
this.logger = new Logger((message) => this.logHandler?.(message), `context:${this.name}`);
|
|
1356
|
+
this.routeActivationResolver = createContextRouteActivationResolver(options?.routing, parent?.routeActivationResolver);
|
|
1357
|
+
this.router.setActivationResolver(this.routeActivationResolver);
|
|
1358
|
+
this.parent = parent;
|
|
1359
|
+
this.filter = filter;
|
|
1360
|
+
if (parent) {
|
|
1361
|
+
this.parentEventForwarder = (type, event) => {
|
|
1362
|
+
if (this.filter) {
|
|
1363
|
+
const predicate = this.filter[type];
|
|
1364
|
+
if (predicate?.(event) !== true) return;
|
|
1256
1365
|
}
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1366
|
+
this.eventBus.emit(type, event);
|
|
1367
|
+
};
|
|
1368
|
+
parent.eventBus.on("*", this.parentEventForwarder);
|
|
1369
|
+
}
|
|
1370
|
+
const getState = () => this.lifecycle.state;
|
|
1371
|
+
this.plugins = new PluginRegistry(this, parent?.plugins, this.logHandler);
|
|
1372
|
+
this.apiHooks = new ApiHookRegistry(baseClient, parent?.apiHooks, this.name, getState);
|
|
1373
|
+
this.client = this.apiHooks.client;
|
|
1374
|
+
this.timers = new TimerRegistry(this.name, this.logger, getState);
|
|
1375
|
+
this.eventSources = new EventSourceRegistry(options?.reconnect, this.logger, getState, (event) => {
|
|
1376
|
+
this.eventBus.emit(event.event_type, event);
|
|
1377
|
+
});
|
|
1378
|
+
this.lifecycle = new LifecycleManager(this.name, this.plugins, this.timers, this.eventSources, this.apiHooks, () => {
|
|
1379
|
+
if (this.parent && this.parentEventForwarder) this.parent.eventBus.off("*", this.parentEventForwarder);
|
|
1380
|
+
});
|
|
1381
|
+
parent?.lifecycle.addChild(this.lifecycle);
|
|
1382
|
+
this.eventBus.on("message_receive", async ({ self_id, data: message }) => {
|
|
1383
|
+
try {
|
|
1384
|
+
if (this.lifecycle.state === "stopping" || this.lifecycle.state === "stopped") return;
|
|
1385
|
+
await this.router.dispatch(this.createSession(self_id, message), message);
|
|
1265
1386
|
} catch (error) {
|
|
1266
|
-
|
|
1267
|
-
this.logger.error(`Error connecting ${eventSource.name ?? "event source"}; reconnecting in ${reconnectDelay}ms`, error);
|
|
1387
|
+
this.logger.error(`Error routing command (scene=${message.message_scene} peer=${message.peer_id} sender=${message.sender_id} seq=${message.message_seq})`, error);
|
|
1268
1388
|
}
|
|
1269
|
-
await this.waitForReconnectDelay(runtime, reconnectDelay);
|
|
1270
|
-
reconnectDelay = Math.min(reconnectDelay * 2, this.maxReconnectDelayMs);
|
|
1271
|
-
reconnectAttempt += 1;
|
|
1272
|
-
}
|
|
1273
|
-
}
|
|
1274
|
-
waitForReconnectDelay(runtime, delay) {
|
|
1275
|
-
return new Promise((resolve) => {
|
|
1276
|
-
runtime.resolveReconnectTimer = resolve;
|
|
1277
|
-
runtime.reconnectTimer = setTimeout(() => {
|
|
1278
|
-
runtime.reconnectTimer = void 0;
|
|
1279
|
-
runtime.resolveReconnectTimer = void 0;
|
|
1280
|
-
resolve();
|
|
1281
|
-
}, delay);
|
|
1282
1389
|
});
|
|
1283
1390
|
}
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1391
|
+
on(type, handler) {
|
|
1392
|
+
const wrappedHandler = async (event) => {
|
|
1393
|
+
try {
|
|
1394
|
+
if (this.lifecycle.state === "stopping" || this.lifecycle.state === "stopped") return;
|
|
1395
|
+
await handler(event);
|
|
1396
|
+
} catch (error) {
|
|
1397
|
+
this.logger.error(`Error handling event ${type}`, error);
|
|
1398
|
+
}
|
|
1399
|
+
};
|
|
1400
|
+
this.eventBus.on(type, wrappedHandler);
|
|
1401
|
+
return () => {
|
|
1402
|
+
this.eventBus.off(type, wrappedHandler);
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
install(plugin, ...args) {
|
|
1406
|
+
this.plugins.install(plugin, ...args);
|
|
1407
|
+
}
|
|
1408
|
+
installEventSource(eventSource) {
|
|
1409
|
+
this.eventSources.install(eventSource);
|
|
1410
|
+
}
|
|
1411
|
+
hookApi(endpointOrHook, hook) {
|
|
1412
|
+
return this.apiHooks.register(endpointOrHook, hook);
|
|
1413
|
+
}
|
|
1414
|
+
provide(service, instance) {
|
|
1415
|
+
this.plugins.provide(service, instance);
|
|
1416
|
+
}
|
|
1417
|
+
resolve(service) {
|
|
1418
|
+
return this.plugins.resolve(service);
|
|
1419
|
+
}
|
|
1420
|
+
tryResolve(service) {
|
|
1421
|
+
return this.plugins.tryResolve(service);
|
|
1422
|
+
}
|
|
1423
|
+
isProvided(service) {
|
|
1424
|
+
return this.plugins.isProvided(service);
|
|
1425
|
+
}
|
|
1426
|
+
fork(name, filter) {
|
|
1427
|
+
if (this.subContexts.has(name)) {
|
|
1428
|
+
if (filter) throw new Error(`Sub context "${name}" already exists, so the provided filter cannot be applied. Please use fork('${name}') without a filter to get the existing subcontext.`);
|
|
1429
|
+
return this.subContexts.get(name);
|
|
1288
1430
|
}
|
|
1289
|
-
const
|
|
1290
|
-
|
|
1291
|
-
|
|
1431
|
+
const subContext = new Context(this.baseClient, void 0, name, this, filter);
|
|
1432
|
+
this.subContexts.set(name, subContext);
|
|
1433
|
+
return subContext;
|
|
1434
|
+
}
|
|
1435
|
+
timeout(delayMs, callback) {
|
|
1436
|
+
return this.timers.timeout(delayMs, callback);
|
|
1437
|
+
}
|
|
1438
|
+
interval(intervalMs, callback) {
|
|
1439
|
+
return this.timers.interval(intervalMs, callback);
|
|
1440
|
+
}
|
|
1441
|
+
createSession(selfId, message) {
|
|
1442
|
+
return {
|
|
1443
|
+
selfId,
|
|
1444
|
+
raw: message,
|
|
1445
|
+
reply: async (textOrSegments, options) => {
|
|
1446
|
+
const actualSegments = [];
|
|
1447
|
+
if (typeof textOrSegments === "string") actualSegments.push({
|
|
1448
|
+
type: "text",
|
|
1449
|
+
data: { text: textOrSegments }
|
|
1450
|
+
});
|
|
1451
|
+
else actualSegments.push(...textOrSegments);
|
|
1452
|
+
if (options?.withMention && message.message_scene === "group") actualSegments.unshift(seg.mention(message.sender_id));
|
|
1453
|
+
if (options?.withQuote) actualSegments.unshift(seg.reply(message.message_seq));
|
|
1454
|
+
switch (message.message_scene) {
|
|
1455
|
+
case "friend": {
|
|
1456
|
+
const { message_seq } = await this.client.send_private_message({
|
|
1457
|
+
user_id: message.peer_id,
|
|
1458
|
+
message: actualSegments
|
|
1459
|
+
});
|
|
1460
|
+
return { messageSeq: message_seq };
|
|
1461
|
+
}
|
|
1462
|
+
case "group": {
|
|
1463
|
+
const { message_seq } = await this.client.send_group_message({
|
|
1464
|
+
group_id: message.peer_id,
|
|
1465
|
+
message: actualSegments
|
|
1466
|
+
});
|
|
1467
|
+
return { messageSeq: message_seq };
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
return { messageSeq: 0 };
|
|
1471
|
+
},
|
|
1472
|
+
reaction: async (type, reactionId) => {
|
|
1473
|
+
if (message.message_scene === "group") await this.client.send_group_message_reaction({
|
|
1474
|
+
group_id: message.peer_id,
|
|
1475
|
+
message_seq: message.message_seq,
|
|
1476
|
+
reaction_type: type,
|
|
1477
|
+
reaction: reactionId
|
|
1478
|
+
});
|
|
1479
|
+
}
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
async start() {
|
|
1483
|
+
await this.lifecycle.start();
|
|
1484
|
+
}
|
|
1485
|
+
async stop() {
|
|
1486
|
+
await this.lifecycle.stop();
|
|
1292
1487
|
}
|
|
1293
1488
|
static fromUrl(baseUrl, options) {
|
|
1294
|
-
const
|
|
1489
|
+
const client = createMilkyClient(baseUrl, { accessToken: options?.accessToken });
|
|
1490
|
+
const context = new Context(client, options);
|
|
1295
1491
|
if (options?.installEventSource ?? true) context.installEventSource(createMilkyWebSocketEventSource(baseUrl, { accessToken: options?.accessToken }));
|
|
1296
1492
|
return context;
|
|
1297
1493
|
}
|