@tinycloud/sdk-services 3.1.0-beta.10 → 3.1.0-beta.13
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.cjs +495 -160
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +103 -12
- package/dist/index.d.ts +103 -12
- package/dist/index.js +495 -160
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -3996,7 +3996,7 @@ var AsyncQueue = class {
|
|
|
3996
3996
|
}
|
|
3997
3997
|
const waiter = this.waiters.shift();
|
|
3998
3998
|
if (waiter) {
|
|
3999
|
-
waiter({ value, done: false });
|
|
3999
|
+
waiter.resolve({ value, done: false });
|
|
4000
4000
|
return;
|
|
4001
4001
|
}
|
|
4002
4002
|
this.values.push(value);
|
|
@@ -4008,7 +4008,22 @@ var AsyncQueue = class {
|
|
|
4008
4008
|
this.closed = true;
|
|
4009
4009
|
while (this.waiters.length > 0) {
|
|
4010
4010
|
const waiter = this.waiters.shift();
|
|
4011
|
-
waiter?.({ value: void 0, done: true });
|
|
4011
|
+
waiter?.resolve({ value: void 0, done: true });
|
|
4012
|
+
}
|
|
4013
|
+
}
|
|
4014
|
+
/**
|
|
4015
|
+
* Fail the queue: pending and future `next()` calls reject with `error`,
|
|
4016
|
+
* so a `for await` consumer sees the iterator throw.
|
|
4017
|
+
*/
|
|
4018
|
+
fail(error) {
|
|
4019
|
+
if (this.closed) {
|
|
4020
|
+
return;
|
|
4021
|
+
}
|
|
4022
|
+
this.closed = true;
|
|
4023
|
+
this.failure = error;
|
|
4024
|
+
while (this.waiters.length > 0) {
|
|
4025
|
+
const waiter = this.waiters.shift();
|
|
4026
|
+
waiter?.reject(error);
|
|
4012
4027
|
}
|
|
4013
4028
|
}
|
|
4014
4029
|
next() {
|
|
@@ -4016,20 +4031,28 @@ var AsyncQueue = class {
|
|
|
4016
4031
|
const value = this.values.shift();
|
|
4017
4032
|
return Promise.resolve({ value, done: false });
|
|
4018
4033
|
}
|
|
4034
|
+
if (this.failure !== void 0) {
|
|
4035
|
+
return Promise.reject(this.failure);
|
|
4036
|
+
}
|
|
4019
4037
|
if (this.closed) {
|
|
4020
4038
|
return Promise.resolve({ value: void 0, done: true });
|
|
4021
4039
|
}
|
|
4022
|
-
return new Promise((resolve) => {
|
|
4023
|
-
this.waiters.push(resolve);
|
|
4040
|
+
return new Promise((resolve, reject) => {
|
|
4041
|
+
this.waiters.push({ resolve, reject });
|
|
4024
4042
|
});
|
|
4025
4043
|
}
|
|
4026
4044
|
};
|
|
4045
|
+
var HOOK_STREAM_RETRY_BASE_DELAY_MS = 250;
|
|
4046
|
+
var HOOK_STREAM_RETRY_MAX_DELAY_MS = 3e4;
|
|
4047
|
+
var HOOK_STREAM_HEALTHY_MS = 5e3;
|
|
4027
4048
|
var HooksService = class extends BaseService {
|
|
4028
4049
|
constructor(config = {}) {
|
|
4029
4050
|
super();
|
|
4030
4051
|
this._subscribers = /* @__PURE__ */ new Set();
|
|
4031
|
-
|
|
4032
|
-
this.
|
|
4052
|
+
/** True while the one supervisor loop runs. Set only by wake(), cleared only at loop exit. */
|
|
4053
|
+
this._supervising = false;
|
|
4054
|
+
/** Bumped on every session change; part of the plan key. */
|
|
4055
|
+
this._sessionGeneration = 0;
|
|
4033
4056
|
this._config = config;
|
|
4034
4057
|
}
|
|
4035
4058
|
get config() {
|
|
@@ -4045,35 +4068,32 @@ var HooksService = class extends BaseService {
|
|
|
4045
4068
|
if (subscriptions.length === 0) {
|
|
4046
4069
|
throw new Error("At least one hook subscription is required");
|
|
4047
4070
|
}
|
|
4048
|
-
const
|
|
4071
|
+
const requested = subscriptions.map(normalizeSubscription);
|
|
4049
4072
|
const subscriber = {
|
|
4050
|
-
requested
|
|
4073
|
+
requested,
|
|
4074
|
+
signatures: requested.map(subscriptionSignature),
|
|
4051
4075
|
ttlSeconds: options.ttlSeconds,
|
|
4052
4076
|
queue: new AsyncQueue()
|
|
4053
4077
|
};
|
|
4054
|
-
|
|
4055
|
-
const abortHandler = () => {
|
|
4078
|
+
const leave = () => {
|
|
4056
4079
|
this._subscribers.delete(subscriber);
|
|
4057
4080
|
subscriber.queue.close();
|
|
4058
|
-
|
|
4081
|
+
this.wake();
|
|
4059
4082
|
};
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
} else {
|
|
4064
|
-
options.signal.addEventListener("abort", abortHandler, { once: true });
|
|
4065
|
-
}
|
|
4083
|
+
const { signal } = options;
|
|
4084
|
+
if (signal?.aborted) {
|
|
4085
|
+
return;
|
|
4066
4086
|
}
|
|
4067
|
-
|
|
4087
|
+
signal?.addEventListener("abort", leave, { once: true });
|
|
4088
|
+
this._subscribers.add(subscriber);
|
|
4089
|
+
this.wake();
|
|
4068
4090
|
try {
|
|
4069
4091
|
for await (const event of subscriber.queue) {
|
|
4070
4092
|
yield event;
|
|
4071
4093
|
}
|
|
4072
4094
|
} finally {
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
}
|
|
4076
|
-
abortHandler();
|
|
4095
|
+
signal?.removeEventListener("abort", leave);
|
|
4096
|
+
leave();
|
|
4077
4097
|
}
|
|
4078
4098
|
}
|
|
4079
4099
|
async register(webhook) {
|
|
@@ -4214,146 +4234,292 @@ var HooksService = class extends BaseService {
|
|
|
4214
4234
|
return err(wrapError("hooks", error));
|
|
4215
4235
|
}
|
|
4216
4236
|
}
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
if (state.signature !== this._activeSignature) {
|
|
4229
|
-
this._activeSignature = state.signature;
|
|
4230
|
-
this.abortSharedStream();
|
|
4237
|
+
/**
|
|
4238
|
+
* Called after anything that may change what the supervisor should do:
|
|
4239
|
+
* subscribe, unsubscribe, session change, sign-out. Interrupts the running
|
|
4240
|
+
* step if the plan moved away from it, and starts the supervisor if none
|
|
4241
|
+
* runs. Touches only SDK-owned state, so it never throws into callers such
|
|
4242
|
+
* as `ServiceContext.setSession`.
|
|
4243
|
+
*/
|
|
4244
|
+
wake() {
|
|
4245
|
+
const phase = this._phase;
|
|
4246
|
+
if (phase && phase.key !== this.plan()?.key) {
|
|
4247
|
+
phase.interrupt.abort();
|
|
4231
4248
|
}
|
|
4232
|
-
if (
|
|
4233
|
-
|
|
4234
|
-
if (!isAbortError(error)) {
|
|
4235
|
-
throw error;
|
|
4236
|
-
}
|
|
4237
|
-
}).finally(() => {
|
|
4238
|
-
this._sharedStreamTask = void 0;
|
|
4239
|
-
this._sharedStreamAbort = void 0;
|
|
4240
|
-
if (this._subscribers.size > 0) {
|
|
4241
|
-
void this.scheduleSharedStreamRefresh();
|
|
4242
|
-
}
|
|
4243
|
-
});
|
|
4249
|
+
if (this._supervising || this._subscribers.size === 0) {
|
|
4250
|
+
return;
|
|
4244
4251
|
}
|
|
4252
|
+
this._supervising = true;
|
|
4253
|
+
this.supervise().catch(() => this.collapse());
|
|
4245
4254
|
}
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4255
|
+
/**
|
|
4256
|
+
* The only owner of the shared stream. Each iteration is one attempt
|
|
4257
|
+
* (mint → open → drain), then a classification and, unless the attempt was
|
|
4258
|
+
* superseded or released its subscribers, a backoff. Every await is a
|
|
4259
|
+
* `phase` that wake() can interrupt; an interrupted phase yields
|
|
4260
|
+
* `undefined`, so a superseded mint can never act. Outside `attempt` and
|
|
4261
|
+
* `pause`, which each own one catch, the loop touches only SDK state.
|
|
4262
|
+
*/
|
|
4263
|
+
async supervise() {
|
|
4264
|
+
let streak = 0;
|
|
4265
|
+
for (; ; ) {
|
|
4266
|
+
const plan = this.plan();
|
|
4267
|
+
if (!plan) {
|
|
4268
|
+
this._supervising = false;
|
|
4269
|
+
return;
|
|
4270
|
+
}
|
|
4271
|
+
const health = { openedAt: 0, delivered: false };
|
|
4272
|
+
const outcome = await this.phase(
|
|
4273
|
+
plan.key,
|
|
4274
|
+
(signal) => this.attempt(plan, signal, health)
|
|
4275
|
+
);
|
|
4276
|
+
if (health.delivered || health.openedAt > 0 && performance.now() - health.openedAt >= HOOK_STREAM_HEALTHY_MS) {
|
|
4277
|
+
streak = 0;
|
|
4252
4278
|
}
|
|
4253
|
-
|
|
4254
|
-
|
|
4279
|
+
if (!outcome) {
|
|
4280
|
+
continue;
|
|
4281
|
+
}
|
|
4282
|
+
if (this.plan()?.key !== plan.key) {
|
|
4283
|
+
continue;
|
|
4255
4284
|
}
|
|
4285
|
+
if (outcome.kind === "stopped") {
|
|
4286
|
+
this.release();
|
|
4287
|
+
continue;
|
|
4288
|
+
}
|
|
4289
|
+
if (outcome.kind === "refused" && outcome.terminal) {
|
|
4290
|
+
this.release(outcome.error);
|
|
4291
|
+
this.report(outcome.error);
|
|
4292
|
+
continue;
|
|
4293
|
+
}
|
|
4294
|
+
if (outcome.kind !== "ended") {
|
|
4295
|
+
this.report(outcome.error);
|
|
4296
|
+
}
|
|
4297
|
+
streak += 1;
|
|
4298
|
+
await this.phase(
|
|
4299
|
+
plan.key,
|
|
4300
|
+
(signal) => this.pause(streak, signal)
|
|
4301
|
+
);
|
|
4256
4302
|
}
|
|
4257
|
-
const subscriptions = [...merged.values()].sort(
|
|
4258
|
-
(left, right) => subscriptionSignature(left).localeCompare(subscriptionSignature(right))
|
|
4259
|
-
);
|
|
4260
|
-
const ttlSeconds = ttlCandidates.length > 0 ? Math.min(...ttlCandidates) : void 0;
|
|
4261
|
-
const signature = JSON.stringify({
|
|
4262
|
-
subscriptions: subscriptions.map(subscriptionSignature),
|
|
4263
|
-
ttlSeconds
|
|
4264
|
-
});
|
|
4265
|
-
return {
|
|
4266
|
-
subscriptions,
|
|
4267
|
-
ttlSeconds,
|
|
4268
|
-
signature
|
|
4269
|
-
};
|
|
4270
4303
|
}
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4304
|
+
/**
|
|
4305
|
+
* Run one step under an interrupt that wake() fires when the plan key moves
|
|
4306
|
+
* on. An interrupted step's result is discarded. The interrupt is also
|
|
4307
|
+
* fired when the step returns, which releases whatever it left open: the
|
|
4308
|
+
* request signal and its listeners, an unread response body, a timer.
|
|
4309
|
+
*/
|
|
4310
|
+
async phase(key, step) {
|
|
4311
|
+
const interrupt = new AbortController();
|
|
4312
|
+
this._phase = { key, interrupt };
|
|
4313
|
+
try {
|
|
4314
|
+
if (this.plan()?.key !== key) {
|
|
4315
|
+
interrupt.abort();
|
|
4316
|
+
}
|
|
4317
|
+
const result = await step(interrupt.signal);
|
|
4318
|
+
return interrupt.signal.aborted ? void 0 : result;
|
|
4319
|
+
} finally {
|
|
4320
|
+
this._phase = void 0;
|
|
4321
|
+
interrupt.abort();
|
|
4322
|
+
}
|
|
4323
|
+
}
|
|
4324
|
+
/**
|
|
4325
|
+
* One mint → open → drain. The single catch site for everything the
|
|
4326
|
+
* attempt touches that the SDK does not own: context accessors, invoke,
|
|
4327
|
+
* fetch, response bodies, the SSE parser. Never rejects.
|
|
4328
|
+
*/
|
|
4329
|
+
async attempt(plan, signal, health) {
|
|
4330
|
+
let phase = "mint";
|
|
4331
|
+
let request;
|
|
4332
|
+
const owns = () => !signal.aborted && !request?.signal.aborted && this.plan()?.key === plan.key;
|
|
4274
4333
|
try {
|
|
4275
|
-
const
|
|
4276
|
-
const
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4334
|
+
const context = this.context;
|
|
4335
|
+
const contextSignal = context.abortSignal;
|
|
4336
|
+
this._lastContextSignal = contextSignal;
|
|
4337
|
+
if (!context.isAuthenticated || contextSignal?.aborted) {
|
|
4338
|
+
return { kind: "stopped" };
|
|
4339
|
+
}
|
|
4340
|
+
request = this.createRequestSignal(signal, 0);
|
|
4341
|
+
const host = this.host;
|
|
4342
|
+
const minted = await awaitOwned(
|
|
4343
|
+
request.signal,
|
|
4344
|
+
() => context.fetch(`${host}/hooks/tickets`, {
|
|
4345
|
+
method: "POST",
|
|
4346
|
+
headers: {
|
|
4347
|
+
...serviceHeadersToRecord(this.createInvokeHeaders(plan.subscriptions)),
|
|
4348
|
+
"content-type": "application/json"
|
|
4349
|
+
},
|
|
4350
|
+
body: JSON.stringify({
|
|
4351
|
+
subscriptions: plan.subscriptions,
|
|
4352
|
+
ttlSeconds: plan.ttlSeconds
|
|
4353
|
+
}),
|
|
4354
|
+
signal: request.signal
|
|
4355
|
+
})
|
|
4280
4356
|
);
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4357
|
+
if (!owns()) return { kind: "stopped" };
|
|
4358
|
+
if (!minted.ok) {
|
|
4359
|
+
const status = minted.status;
|
|
4360
|
+
const error = await awaitOwned(
|
|
4361
|
+
request.signal,
|
|
4362
|
+
() => responseError("hooks", "failed to mint hook ticket", minted)
|
|
4363
|
+
);
|
|
4364
|
+
if (!owns()) return { kind: "stopped" };
|
|
4365
|
+
return { kind: "refused", terminal: isTerminalMintRefusal(status), error };
|
|
4366
|
+
}
|
|
4367
|
+
const body = await awaitOwned(request.signal, () => minted.json());
|
|
4368
|
+
if (!owns()) return { kind: "stopped" };
|
|
4369
|
+
const ticket = body !== null && typeof body === "object" && "ticket" in body ? body.ticket : void 0;
|
|
4370
|
+
if (typeof ticket !== "string" || ticket.length === 0) {
|
|
4371
|
+
throw new Error("Hook ticket response did not include a ticket");
|
|
4372
|
+
}
|
|
4373
|
+
phase = "open";
|
|
4374
|
+
if (!owns()) return { kind: "stopped" };
|
|
4375
|
+
const stream = await awaitOwned(
|
|
4376
|
+
request.signal,
|
|
4377
|
+
() => context.fetch(`${host}/hooks/events?ticket=${encodeURIComponent(ticket)}`, {
|
|
4378
|
+
method: "GET",
|
|
4379
|
+
headers: { accept: "text/event-stream" },
|
|
4380
|
+
signal: request.signal
|
|
4381
|
+
})
|
|
4285
4382
|
);
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
if (
|
|
4296
|
-
|
|
4297
|
-
|
|
4383
|
+
if (!owns()) return { kind: "stopped" };
|
|
4384
|
+
if (!stream.ok) {
|
|
4385
|
+
return { kind: "refused", terminal: false, error: hookStreamRefusal(stream.status) };
|
|
4386
|
+
}
|
|
4387
|
+
health.openedAt = performance.now();
|
|
4388
|
+
const iterator = parseSseStream(stream.body, request.signal)[Symbol.asyncIterator]();
|
|
4389
|
+
try {
|
|
4390
|
+
for (; ; ) {
|
|
4391
|
+
const next = await awaitOwned(request.signal, () => iterator.next());
|
|
4392
|
+
if (!owns()) return { kind: "stopped" };
|
|
4393
|
+
if (next.done) break;
|
|
4394
|
+
const message = next.value;
|
|
4395
|
+
if (!message.data) continue;
|
|
4396
|
+
const event = parseHookEvent(message);
|
|
4397
|
+
if (!owns()) return { kind: "stopped" };
|
|
4398
|
+
this.dispatch(event);
|
|
4399
|
+
health.delivered = true;
|
|
4298
4400
|
}
|
|
4401
|
+
} finally {
|
|
4402
|
+
const closing = iterator.return?.();
|
|
4403
|
+
if (closing) void Promise.resolve(closing).then(() => void 0, () => void 0);
|
|
4299
4404
|
}
|
|
4405
|
+
return owns() ? { kind: "ended" } : { kind: "stopped" };
|
|
4406
|
+
} catch (thrown) {
|
|
4407
|
+
return request?.signal.aborted || thrown === SUPERSEDED ? { kind: "stopped" } : { kind: "failed", error: hookStreamFailure(phase, thrown) };
|
|
4408
|
+
}
|
|
4409
|
+
}
|
|
4410
|
+
/**
|
|
4411
|
+
* Back off before the next attempt. `config.streamRetry` overrides the
|
|
4412
|
+
* delay and the clock; whatever they do, the pause ends by the delay or the
|
|
4413
|
+
* interrupt, then yields one timer turn so an instant clock cannot starve
|
|
4414
|
+
* due timers or the process.
|
|
4415
|
+
*/
|
|
4416
|
+
async pause(attempt, signal) {
|
|
4417
|
+
try {
|
|
4418
|
+
this._lastContextSignal = this.context.abortSignal;
|
|
4419
|
+
} catch {
|
|
4420
|
+
}
|
|
4421
|
+
const contextSignal = this._lastContextSignal;
|
|
4422
|
+
const waits = [];
|
|
4423
|
+
const aborts = [];
|
|
4424
|
+
try {
|
|
4425
|
+
const retry = this._config.streamRetry;
|
|
4426
|
+
const delayMs = retry?.delay?.(attempt) ?? hookStreamRetryDelay(attempt);
|
|
4427
|
+
waits.push((retry?.wait ?? sleep)(delayMs, attempt, signal));
|
|
4428
|
+
} catch {
|
|
4429
|
+
waits.push(Promise.resolve());
|
|
4430
|
+
}
|
|
4431
|
+
aborts.push(abortPromise(signal));
|
|
4432
|
+
if (contextSignal) aborts.push(abortPromise(contextSignal));
|
|
4433
|
+
try {
|
|
4434
|
+
await Promise.race([...waits, ...aborts.map(({ promise }) => promise)]);
|
|
4435
|
+
} catch {
|
|
4300
4436
|
} finally {
|
|
4301
|
-
|
|
4302
|
-
|
|
4437
|
+
for (const abort of aborts) abort.dispose();
|
|
4438
|
+
}
|
|
4439
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
4440
|
+
}
|
|
4441
|
+
/** The stream the current subscribers need, or undefined when none remain. */
|
|
4442
|
+
plan() {
|
|
4443
|
+
if (this._subscribers.size === 0) {
|
|
4444
|
+
return void 0;
|
|
4445
|
+
}
|
|
4446
|
+
const merged = /* @__PURE__ */ new Map();
|
|
4447
|
+
let ttlSeconds;
|
|
4448
|
+
for (const subscriber of this._subscribers) {
|
|
4449
|
+
subscriber.requested.forEach(
|
|
4450
|
+
(subscription, index) => merged.set(subscriber.signatures[index], subscription)
|
|
4451
|
+
);
|
|
4452
|
+
if (typeof subscriber.ttlSeconds === "number") {
|
|
4453
|
+
ttlSeconds = Math.min(subscriber.ttlSeconds, ttlSeconds ?? Infinity);
|
|
4303
4454
|
}
|
|
4304
4455
|
}
|
|
4456
|
+
const signatures = [...merged.keys()].sort();
|
|
4457
|
+
return {
|
|
4458
|
+
key: JSON.stringify([this._sessionGeneration, signatures, ttlSeconds ?? null]),
|
|
4459
|
+
subscriptions: signatures.map((signature) => merged.get(signature)),
|
|
4460
|
+
ttlSeconds
|
|
4461
|
+
};
|
|
4305
4462
|
}
|
|
4306
|
-
|
|
4307
|
-
this.
|
|
4463
|
+
dispatch(event) {
|
|
4464
|
+
for (const subscriber of this._subscribers) {
|
|
4465
|
+
if (matchesAnySubscription(event, subscriber.requested)) {
|
|
4466
|
+
subscriber.queue.push(event);
|
|
4467
|
+
}
|
|
4468
|
+
}
|
|
4308
4469
|
}
|
|
4309
|
-
|
|
4310
|
-
|
|
4470
|
+
/**
|
|
4471
|
+
* Detach every subscriber. With `error`, their iterators throw it after
|
|
4472
|
+
* draining buffered events; without, they complete.
|
|
4473
|
+
*/
|
|
4474
|
+
release(error) {
|
|
4475
|
+
const subscribers = [...this._subscribers];
|
|
4476
|
+
this._subscribers.clear();
|
|
4477
|
+
for (const { queue } of subscribers) {
|
|
4478
|
+
if (error) {
|
|
4479
|
+
queue.fail(error);
|
|
4480
|
+
} else {
|
|
4481
|
+
queue.close();
|
|
4482
|
+
}
|
|
4483
|
+
}
|
|
4311
4484
|
}
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
const ticketResponse = await this.context.fetch(`${host}/hooks/tickets`, {
|
|
4316
|
-
method: "POST",
|
|
4317
|
-
headers: {
|
|
4318
|
-
...serviceHeadersToRecord(headers),
|
|
4319
|
-
"content-type": "application/json"
|
|
4320
|
-
},
|
|
4321
|
-
body: JSON.stringify({
|
|
4322
|
-
subscriptions,
|
|
4323
|
-
ttlSeconds
|
|
4324
|
-
}),
|
|
4325
|
-
signal
|
|
4326
|
-
});
|
|
4327
|
-
if (!ticketResponse.ok) {
|
|
4328
|
-
throw await responseError(
|
|
4329
|
-
"hooks",
|
|
4330
|
-
"failed to mint hook ticket",
|
|
4331
|
-
ticketResponse
|
|
4332
|
-
);
|
|
4485
|
+
report(error) {
|
|
4486
|
+
if (error.meta && typeof error.meta === "object") {
|
|
4487
|
+
Object.freeze(error.meta);
|
|
4333
4488
|
}
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4489
|
+
Object.freeze(error);
|
|
4490
|
+
try {
|
|
4491
|
+
this.emitError(error, "stream");
|
|
4492
|
+
} catch {
|
|
4337
4493
|
}
|
|
4338
|
-
return ticketJson;
|
|
4339
4494
|
}
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4495
|
+
/**
|
|
4496
|
+
* Unreachable by construction. If the loop ever rejects anyway, fail its
|
|
4497
|
+
* subscribers visibly instead of stranding them or crashing the process.
|
|
4498
|
+
*/
|
|
4499
|
+
collapse() {
|
|
4500
|
+
this._supervising = false;
|
|
4501
|
+
this._phase?.interrupt.abort();
|
|
4502
|
+
this._phase = void 0;
|
|
4503
|
+
const error = serviceError(
|
|
4504
|
+
ErrorCodes.NETWORK_ERROR,
|
|
4505
|
+
"hook stream supervisor stopped unexpectedly",
|
|
4506
|
+
"hooks"
|
|
4348
4507
|
);
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4508
|
+
this.report(error);
|
|
4509
|
+
this.release(error);
|
|
4510
|
+
}
|
|
4511
|
+
onSessionChange(session) {
|
|
4512
|
+
super.onSessionChange(session);
|
|
4513
|
+
this._sessionGeneration += 1;
|
|
4514
|
+
this.wake();
|
|
4515
|
+
}
|
|
4516
|
+
onSignOut() {
|
|
4517
|
+
super.onSignOut();
|
|
4518
|
+
this.release();
|
|
4519
|
+
this.wake();
|
|
4520
|
+
}
|
|
4521
|
+
createHookHeaders(action, path) {
|
|
4522
|
+
return this.context.invoke(this.session, "hooks", path, action);
|
|
4357
4523
|
}
|
|
4358
4524
|
createInvokeHeaders(subscriptions) {
|
|
4359
4525
|
const entries = subscriptions.map((subscription) => ({
|
|
@@ -4384,11 +4550,168 @@ function buildScopePath(service, pathPrefix) {
|
|
|
4384
4550
|
const normalized = normalizePathPrefix(pathPrefix);
|
|
4385
4551
|
return normalized ? `${service}/${normalized}` : service;
|
|
4386
4552
|
}
|
|
4553
|
+
function hookStreamRetryDelay(attempt) {
|
|
4554
|
+
const cap = Math.min(
|
|
4555
|
+
HOOK_STREAM_RETRY_BASE_DELAY_MS * 2 ** Math.max(0, attempt - 1),
|
|
4556
|
+
HOOK_STREAM_RETRY_MAX_DELAY_MS
|
|
4557
|
+
);
|
|
4558
|
+
return cap / 2 + Math.random() * (cap / 2);
|
|
4559
|
+
}
|
|
4560
|
+
function isTerminalMintRefusal(status) {
|
|
4561
|
+
return typeof status === "number" && status >= 400 && status < 500 && status !== 408 && status !== 429;
|
|
4562
|
+
}
|
|
4563
|
+
var SUPERSEDED = /* @__PURE__ */ Symbol("hooks attempt superseded");
|
|
4564
|
+
async function awaitOwned(signal, operation) {
|
|
4565
|
+
if (signal.aborted) {
|
|
4566
|
+
if (typeof operation !== "function") {
|
|
4567
|
+
void operation.then(cancelLateResponse, () => void 0);
|
|
4568
|
+
}
|
|
4569
|
+
throw SUPERSEDED;
|
|
4570
|
+
}
|
|
4571
|
+
const promise = typeof operation === "function" ? operation() : operation;
|
|
4572
|
+
return new Promise((resolve, reject) => {
|
|
4573
|
+
let settled = false;
|
|
4574
|
+
const cleanup = () => signal.removeEventListener("abort", onAbort);
|
|
4575
|
+
const onAbort = () => {
|
|
4576
|
+
if (settled) return;
|
|
4577
|
+
settled = true;
|
|
4578
|
+
cleanup();
|
|
4579
|
+
reject(SUPERSEDED);
|
|
4580
|
+
};
|
|
4581
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
4582
|
+
promise.then(
|
|
4583
|
+
(value) => {
|
|
4584
|
+
if (settled) {
|
|
4585
|
+
cancelLateResponse(value);
|
|
4586
|
+
return;
|
|
4587
|
+
}
|
|
4588
|
+
settled = true;
|
|
4589
|
+
cleanup();
|
|
4590
|
+
resolve(value);
|
|
4591
|
+
},
|
|
4592
|
+
(error) => {
|
|
4593
|
+
if (settled) return;
|
|
4594
|
+
settled = true;
|
|
4595
|
+
cleanup();
|
|
4596
|
+
reject(error);
|
|
4597
|
+
}
|
|
4598
|
+
);
|
|
4599
|
+
});
|
|
4600
|
+
}
|
|
4601
|
+
function cancelLateResponse(value) {
|
|
4602
|
+
if (!(value instanceof Response) || !value.body) return;
|
|
4603
|
+
try {
|
|
4604
|
+
const cancellation = value.body.cancel();
|
|
4605
|
+
void cancellation.then(() => void 0, () => void 0);
|
|
4606
|
+
} catch {
|
|
4607
|
+
}
|
|
4608
|
+
}
|
|
4609
|
+
function hookStreamRefusal(status) {
|
|
4610
|
+
const code = typeof status === "number" ? status : void 0;
|
|
4611
|
+
return serviceError(
|
|
4612
|
+
ErrorCodes.NETWORK_ERROR,
|
|
4613
|
+
`hook stream open refused: ${code ?? "unknown status"}`,
|
|
4614
|
+
"hooks",
|
|
4615
|
+
{ meta: { phase: "open", status: code } }
|
|
4616
|
+
);
|
|
4617
|
+
}
|
|
4618
|
+
var STREAM_ERROR_NAMES = /* @__PURE__ */ new Set([
|
|
4619
|
+
"Error",
|
|
4620
|
+
"TypeError",
|
|
4621
|
+
"AbortError",
|
|
4622
|
+
"TimeoutError",
|
|
4623
|
+
"SyntaxError",
|
|
4624
|
+
"RangeError",
|
|
4625
|
+
"NetworkError"
|
|
4626
|
+
]);
|
|
4627
|
+
function hookStreamFailure(phase, thrown) {
|
|
4628
|
+
const candidateName = readProperty(thrown, "name");
|
|
4629
|
+
const name = typeof candidateName === "string" && STREAM_ERROR_NAMES.has(candidateName) ? candidateName : "Error";
|
|
4630
|
+
const code = identifier(readProperty(thrown, "code")) ?? identifier(readProperty(readProperty(thrown, "cause"), "code"));
|
|
4631
|
+
return serviceError(
|
|
4632
|
+
ErrorCodes.NETWORK_ERROR,
|
|
4633
|
+
`hook stream ${phase} failed: ${name}${code ? ` (${code})` : ""}`,
|
|
4634
|
+
"hooks",
|
|
4635
|
+
{ meta: { phase, errorName: name, ...code ? { errorCode: code } : {} } }
|
|
4636
|
+
);
|
|
4637
|
+
}
|
|
4638
|
+
var STREAM_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
4639
|
+
"ECONNREFUSED",
|
|
4640
|
+
"ECONNRESET",
|
|
4641
|
+
"ECONNABORTED",
|
|
4642
|
+
"ETIMEDOUT",
|
|
4643
|
+
"EPIPE",
|
|
4644
|
+
"ENOTFOUND",
|
|
4645
|
+
"EAI_AGAIN",
|
|
4646
|
+
"EHOSTUNREACH",
|
|
4647
|
+
"ENETUNREACH",
|
|
4648
|
+
"ENETDOWN",
|
|
4649
|
+
"EADDRNOTAVAIL",
|
|
4650
|
+
"UND_ERR_SOCKET",
|
|
4651
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
4652
|
+
"UND_ERR_HEADERS_TIMEOUT",
|
|
4653
|
+
"UND_ERR_BODY_TIMEOUT",
|
|
4654
|
+
"UND_ERR_CLOSED",
|
|
4655
|
+
"UND_ERR_ABORTED",
|
|
4656
|
+
"UND_ERR_RESPONSE_STATUS_CODE",
|
|
4657
|
+
"ERR_TLS_CERT_ALTNAME_INVALID",
|
|
4658
|
+
"CERT_HAS_EXPIRED",
|
|
4659
|
+
"DEPTH_ZERO_SELF_SIGNED_CERT",
|
|
4660
|
+
"UNABLE_TO_VERIFY_LEAF_SIGNATURE",
|
|
4661
|
+
"SELF_SIGNED_CERT_IN_CHAIN",
|
|
4662
|
+
"ERR_SSL_WRONG_VERSION_NUMBER"
|
|
4663
|
+
]);
|
|
4664
|
+
function identifier(value) {
|
|
4665
|
+
return typeof value === "string" && STREAM_ERROR_CODES.has(value) ? value : void 0;
|
|
4666
|
+
}
|
|
4667
|
+
function readProperty(value, key) {
|
|
4668
|
+
if (value === null || typeof value !== "object" && typeof value !== "function") {
|
|
4669
|
+
return void 0;
|
|
4670
|
+
}
|
|
4671
|
+
try {
|
|
4672
|
+
return Reflect.get(value, key);
|
|
4673
|
+
} catch {
|
|
4674
|
+
return void 0;
|
|
4675
|
+
}
|
|
4676
|
+
}
|
|
4677
|
+
function sleep(delayMs, _attempt, signal) {
|
|
4678
|
+
return new Promise((resolve) => {
|
|
4679
|
+
const finish = () => {
|
|
4680
|
+
clearTimeout(timer);
|
|
4681
|
+
signal.removeEventListener("abort", stop);
|
|
4682
|
+
resolve();
|
|
4683
|
+
};
|
|
4684
|
+
const timer = setTimeout(finish, delayMs);
|
|
4685
|
+
timer.unref?.();
|
|
4686
|
+
const stop = () => finish();
|
|
4687
|
+
if (signal.aborted) {
|
|
4688
|
+
finish();
|
|
4689
|
+
} else {
|
|
4690
|
+
signal.addEventListener("abort", stop, { once: true });
|
|
4691
|
+
}
|
|
4692
|
+
});
|
|
4693
|
+
}
|
|
4694
|
+
function abortPromise(signal) {
|
|
4695
|
+
let resolve;
|
|
4696
|
+
const promise = new Promise((done) => {
|
|
4697
|
+
resolve = done;
|
|
4698
|
+
});
|
|
4699
|
+
const onAbort = () => resolve();
|
|
4700
|
+
if (signal.aborted) {
|
|
4701
|
+
resolve();
|
|
4702
|
+
} else {
|
|
4703
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
4704
|
+
}
|
|
4705
|
+
return {
|
|
4706
|
+
promise,
|
|
4707
|
+
dispose: () => signal.removeEventListener("abort", onAbort)
|
|
4708
|
+
};
|
|
4709
|
+
}
|
|
4387
4710
|
function normalizeSubscription(subscription) {
|
|
4388
4711
|
return {
|
|
4389
4712
|
...subscription,
|
|
4390
4713
|
pathPrefix: normalizePathPrefix(subscription.pathPrefix),
|
|
4391
|
-
abilities: subscription.abilities
|
|
4714
|
+
abilities: subscription.abilities ? [...subscription.abilities] : []
|
|
4392
4715
|
};
|
|
4393
4716
|
}
|
|
4394
4717
|
function subscriptionSignature(subscription) {
|
|
@@ -4454,9 +4777,6 @@ async function responseError(service, message, response) {
|
|
|
4454
4777
|
meta: { ...error.meta, status: response.status, statusText: response.statusText }
|
|
4455
4778
|
};
|
|
4456
4779
|
}
|
|
4457
|
-
function isAbortError(error) {
|
|
4458
|
-
return error instanceof DOMException && error.name === "AbortError";
|
|
4459
|
-
}
|
|
4460
4780
|
async function* parseSseStream(body, signal) {
|
|
4461
4781
|
if (!body) {
|
|
4462
4782
|
throw new Error("Hook stream response does not expose a readable body");
|
|
@@ -4485,11 +4805,24 @@ async function* parseSseStream(body, signal) {
|
|
|
4485
4805
|
async function* readBodyChunks(body, signal) {
|
|
4486
4806
|
const asyncIterable = body;
|
|
4487
4807
|
if (typeof asyncIterable?.[Symbol.asyncIterator] === "function") {
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4808
|
+
const iterator = asyncIterable[Symbol.asyncIterator]();
|
|
4809
|
+
const cancel = () => {
|
|
4810
|
+
try {
|
|
4811
|
+
const closing = iterator.return?.();
|
|
4812
|
+
if (closing) void Promise.resolve(closing).then(() => void 0, () => void 0);
|
|
4813
|
+
} catch {
|
|
4814
|
+
}
|
|
4815
|
+
};
|
|
4816
|
+
signal?.addEventListener("abort", cancel, { once: true });
|
|
4817
|
+
try {
|
|
4818
|
+
while (!signal?.aborted) {
|
|
4819
|
+
const next = await iterator.next();
|
|
4820
|
+
if (next.done) break;
|
|
4821
|
+
yield next.value;
|
|
4491
4822
|
}
|
|
4492
|
-
|
|
4823
|
+
} finally {
|
|
4824
|
+
signal?.removeEventListener("abort", cancel);
|
|
4825
|
+
cancel();
|
|
4493
4826
|
}
|
|
4494
4827
|
return;
|
|
4495
4828
|
}
|
|
@@ -4498,21 +4831,23 @@ async function* readBodyChunks(body, signal) {
|
|
|
4498
4831
|
throw new Error("Unsupported hook stream body type");
|
|
4499
4832
|
}
|
|
4500
4833
|
const reader = stream.getReader();
|
|
4834
|
+
const cancelReader = () => {
|
|
4835
|
+
try {
|
|
4836
|
+
const cancellation = reader.cancel?.();
|
|
4837
|
+
if (cancellation) void cancellation.then(() => void 0, () => void 0);
|
|
4838
|
+
} catch {
|
|
4839
|
+
}
|
|
4840
|
+
};
|
|
4841
|
+
signal?.addEventListener("abort", cancelReader, { once: true });
|
|
4501
4842
|
try {
|
|
4502
4843
|
while (!signal?.aborted) {
|
|
4503
4844
|
const { done, value } = await reader.read();
|
|
4504
|
-
if (done)
|
|
4505
|
-
|
|
4506
|
-
}
|
|
4507
|
-
if (value) {
|
|
4508
|
-
yield value;
|
|
4509
|
-
}
|
|
4845
|
+
if (done) break;
|
|
4846
|
+
if (value) yield value;
|
|
4510
4847
|
}
|
|
4511
4848
|
} finally {
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
} catch {
|
|
4515
|
-
}
|
|
4849
|
+
signal?.removeEventListener("abort", cancelReader);
|
|
4850
|
+
cancelReader();
|
|
4516
4851
|
reader.releaseLock?.();
|
|
4517
4852
|
}
|
|
4518
4853
|
}
|
|
@@ -7156,10 +7491,10 @@ var EncryptionService = class extends BaseService {
|
|
|
7156
7491
|
get crypto() {
|
|
7157
7492
|
return this._config.crypto;
|
|
7158
7493
|
}
|
|
7159
|
-
async discoverNetwork(
|
|
7494
|
+
async discoverNetwork(identifier2, ownerDid) {
|
|
7160
7495
|
this.assertActive();
|
|
7161
7496
|
const result = await discoverNetwork({
|
|
7162
|
-
identifier,
|
|
7497
|
+
identifier: identifier2,
|
|
7163
7498
|
...ownerDid !== void 0 ? { ownerDid } : {},
|
|
7164
7499
|
...this._config.node !== void 0 ? { node: this._config.node } : {},
|
|
7165
7500
|
...this._config.wellKnown !== void 0 ? { wellKnown: this._config.wellKnown } : {}
|