@tinycloud/sdk-services 3.1.0-beta.11 → 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 +1 -1
package/dist/index.cjs
CHANGED
|
@@ -4146,7 +4146,7 @@ var AsyncQueue = class {
|
|
|
4146
4146
|
}
|
|
4147
4147
|
const waiter = this.waiters.shift();
|
|
4148
4148
|
if (waiter) {
|
|
4149
|
-
waiter({ value, done: false });
|
|
4149
|
+
waiter.resolve({ value, done: false });
|
|
4150
4150
|
return;
|
|
4151
4151
|
}
|
|
4152
4152
|
this.values.push(value);
|
|
@@ -4158,7 +4158,22 @@ var AsyncQueue = class {
|
|
|
4158
4158
|
this.closed = true;
|
|
4159
4159
|
while (this.waiters.length > 0) {
|
|
4160
4160
|
const waiter = this.waiters.shift();
|
|
4161
|
-
waiter?.({ value: void 0, done: true });
|
|
4161
|
+
waiter?.resolve({ value: void 0, done: true });
|
|
4162
|
+
}
|
|
4163
|
+
}
|
|
4164
|
+
/**
|
|
4165
|
+
* Fail the queue: pending and future `next()` calls reject with `error`,
|
|
4166
|
+
* so a `for await` consumer sees the iterator throw.
|
|
4167
|
+
*/
|
|
4168
|
+
fail(error) {
|
|
4169
|
+
if (this.closed) {
|
|
4170
|
+
return;
|
|
4171
|
+
}
|
|
4172
|
+
this.closed = true;
|
|
4173
|
+
this.failure = error;
|
|
4174
|
+
while (this.waiters.length > 0) {
|
|
4175
|
+
const waiter = this.waiters.shift();
|
|
4176
|
+
waiter?.reject(error);
|
|
4162
4177
|
}
|
|
4163
4178
|
}
|
|
4164
4179
|
next() {
|
|
@@ -4166,20 +4181,28 @@ var AsyncQueue = class {
|
|
|
4166
4181
|
const value = this.values.shift();
|
|
4167
4182
|
return Promise.resolve({ value, done: false });
|
|
4168
4183
|
}
|
|
4184
|
+
if (this.failure !== void 0) {
|
|
4185
|
+
return Promise.reject(this.failure);
|
|
4186
|
+
}
|
|
4169
4187
|
if (this.closed) {
|
|
4170
4188
|
return Promise.resolve({ value: void 0, done: true });
|
|
4171
4189
|
}
|
|
4172
|
-
return new Promise((resolve) => {
|
|
4173
|
-
this.waiters.push(resolve);
|
|
4190
|
+
return new Promise((resolve, reject) => {
|
|
4191
|
+
this.waiters.push({ resolve, reject });
|
|
4174
4192
|
});
|
|
4175
4193
|
}
|
|
4176
4194
|
};
|
|
4195
|
+
var HOOK_STREAM_RETRY_BASE_DELAY_MS = 250;
|
|
4196
|
+
var HOOK_STREAM_RETRY_MAX_DELAY_MS = 3e4;
|
|
4197
|
+
var HOOK_STREAM_HEALTHY_MS = 5e3;
|
|
4177
4198
|
var HooksService = class extends BaseService {
|
|
4178
4199
|
constructor(config = {}) {
|
|
4179
4200
|
super();
|
|
4180
4201
|
this._subscribers = /* @__PURE__ */ new Set();
|
|
4181
|
-
|
|
4182
|
-
this.
|
|
4202
|
+
/** True while the one supervisor loop runs. Set only by wake(), cleared only at loop exit. */
|
|
4203
|
+
this._supervising = false;
|
|
4204
|
+
/** Bumped on every session change; part of the plan key. */
|
|
4205
|
+
this._sessionGeneration = 0;
|
|
4183
4206
|
this._config = config;
|
|
4184
4207
|
}
|
|
4185
4208
|
get config() {
|
|
@@ -4195,35 +4218,32 @@ var HooksService = class extends BaseService {
|
|
|
4195
4218
|
if (subscriptions.length === 0) {
|
|
4196
4219
|
throw new Error("At least one hook subscription is required");
|
|
4197
4220
|
}
|
|
4198
|
-
const
|
|
4221
|
+
const requested = subscriptions.map(normalizeSubscription);
|
|
4199
4222
|
const subscriber = {
|
|
4200
|
-
requested
|
|
4223
|
+
requested,
|
|
4224
|
+
signatures: requested.map(subscriptionSignature),
|
|
4201
4225
|
ttlSeconds: options.ttlSeconds,
|
|
4202
4226
|
queue: new AsyncQueue()
|
|
4203
4227
|
};
|
|
4204
|
-
|
|
4205
|
-
const abortHandler = () => {
|
|
4228
|
+
const leave = () => {
|
|
4206
4229
|
this._subscribers.delete(subscriber);
|
|
4207
4230
|
subscriber.queue.close();
|
|
4208
|
-
|
|
4231
|
+
this.wake();
|
|
4209
4232
|
};
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
} else {
|
|
4214
|
-
options.signal.addEventListener("abort", abortHandler, { once: true });
|
|
4215
|
-
}
|
|
4233
|
+
const { signal } = options;
|
|
4234
|
+
if (signal?.aborted) {
|
|
4235
|
+
return;
|
|
4216
4236
|
}
|
|
4217
|
-
|
|
4237
|
+
signal?.addEventListener("abort", leave, { once: true });
|
|
4238
|
+
this._subscribers.add(subscriber);
|
|
4239
|
+
this.wake();
|
|
4218
4240
|
try {
|
|
4219
4241
|
for await (const event of subscriber.queue) {
|
|
4220
4242
|
yield event;
|
|
4221
4243
|
}
|
|
4222
4244
|
} finally {
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
}
|
|
4226
|
-
abortHandler();
|
|
4245
|
+
signal?.removeEventListener("abort", leave);
|
|
4246
|
+
leave();
|
|
4227
4247
|
}
|
|
4228
4248
|
}
|
|
4229
4249
|
async register(webhook) {
|
|
@@ -4364,146 +4384,292 @@ var HooksService = class extends BaseService {
|
|
|
4364
4384
|
return err(wrapError("hooks", error));
|
|
4365
4385
|
}
|
|
4366
4386
|
}
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
if (state.signature !== this._activeSignature) {
|
|
4379
|
-
this._activeSignature = state.signature;
|
|
4380
|
-
this.abortSharedStream();
|
|
4387
|
+
/**
|
|
4388
|
+
* Called after anything that may change what the supervisor should do:
|
|
4389
|
+
* subscribe, unsubscribe, session change, sign-out. Interrupts the running
|
|
4390
|
+
* step if the plan moved away from it, and starts the supervisor if none
|
|
4391
|
+
* runs. Touches only SDK-owned state, so it never throws into callers such
|
|
4392
|
+
* as `ServiceContext.setSession`.
|
|
4393
|
+
*/
|
|
4394
|
+
wake() {
|
|
4395
|
+
const phase = this._phase;
|
|
4396
|
+
if (phase && phase.key !== this.plan()?.key) {
|
|
4397
|
+
phase.interrupt.abort();
|
|
4381
4398
|
}
|
|
4382
|
-
if (
|
|
4383
|
-
|
|
4384
|
-
if (!isAbortError(error)) {
|
|
4385
|
-
throw error;
|
|
4386
|
-
}
|
|
4387
|
-
}).finally(() => {
|
|
4388
|
-
this._sharedStreamTask = void 0;
|
|
4389
|
-
this._sharedStreamAbort = void 0;
|
|
4390
|
-
if (this._subscribers.size > 0) {
|
|
4391
|
-
void this.scheduleSharedStreamRefresh();
|
|
4392
|
-
}
|
|
4393
|
-
});
|
|
4399
|
+
if (this._supervising || this._subscribers.size === 0) {
|
|
4400
|
+
return;
|
|
4394
4401
|
}
|
|
4402
|
+
this._supervising = true;
|
|
4403
|
+
this.supervise().catch(() => this.collapse());
|
|
4395
4404
|
}
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4405
|
+
/**
|
|
4406
|
+
* The only owner of the shared stream. Each iteration is one attempt
|
|
4407
|
+
* (mint → open → drain), then a classification and, unless the attempt was
|
|
4408
|
+
* superseded or released its subscribers, a backoff. Every await is a
|
|
4409
|
+
* `phase` that wake() can interrupt; an interrupted phase yields
|
|
4410
|
+
* `undefined`, so a superseded mint can never act. Outside `attempt` and
|
|
4411
|
+
* `pause`, which each own one catch, the loop touches only SDK state.
|
|
4412
|
+
*/
|
|
4413
|
+
async supervise() {
|
|
4414
|
+
let streak = 0;
|
|
4415
|
+
for (; ; ) {
|
|
4416
|
+
const plan = this.plan();
|
|
4417
|
+
if (!plan) {
|
|
4418
|
+
this._supervising = false;
|
|
4419
|
+
return;
|
|
4420
|
+
}
|
|
4421
|
+
const health = { openedAt: 0, delivered: false };
|
|
4422
|
+
const outcome = await this.phase(
|
|
4423
|
+
plan.key,
|
|
4424
|
+
(signal) => this.attempt(plan, signal, health)
|
|
4425
|
+
);
|
|
4426
|
+
if (health.delivered || health.openedAt > 0 && performance.now() - health.openedAt >= HOOK_STREAM_HEALTHY_MS) {
|
|
4427
|
+
streak = 0;
|
|
4402
4428
|
}
|
|
4403
|
-
|
|
4404
|
-
|
|
4429
|
+
if (!outcome) {
|
|
4430
|
+
continue;
|
|
4431
|
+
}
|
|
4432
|
+
if (this.plan()?.key !== plan.key) {
|
|
4433
|
+
continue;
|
|
4405
4434
|
}
|
|
4435
|
+
if (outcome.kind === "stopped") {
|
|
4436
|
+
this.release();
|
|
4437
|
+
continue;
|
|
4438
|
+
}
|
|
4439
|
+
if (outcome.kind === "refused" && outcome.terminal) {
|
|
4440
|
+
this.release(outcome.error);
|
|
4441
|
+
this.report(outcome.error);
|
|
4442
|
+
continue;
|
|
4443
|
+
}
|
|
4444
|
+
if (outcome.kind !== "ended") {
|
|
4445
|
+
this.report(outcome.error);
|
|
4446
|
+
}
|
|
4447
|
+
streak += 1;
|
|
4448
|
+
await this.phase(
|
|
4449
|
+
plan.key,
|
|
4450
|
+
(signal) => this.pause(streak, signal)
|
|
4451
|
+
);
|
|
4406
4452
|
}
|
|
4407
|
-
const subscriptions = [...merged.values()].sort(
|
|
4408
|
-
(left, right) => subscriptionSignature(left).localeCompare(subscriptionSignature(right))
|
|
4409
|
-
);
|
|
4410
|
-
const ttlSeconds = ttlCandidates.length > 0 ? Math.min(...ttlCandidates) : void 0;
|
|
4411
|
-
const signature = JSON.stringify({
|
|
4412
|
-
subscriptions: subscriptions.map(subscriptionSignature),
|
|
4413
|
-
ttlSeconds
|
|
4414
|
-
});
|
|
4415
|
-
return {
|
|
4416
|
-
subscriptions,
|
|
4417
|
-
ttlSeconds,
|
|
4418
|
-
signature
|
|
4419
|
-
};
|
|
4420
4453
|
}
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4454
|
+
/**
|
|
4455
|
+
* Run one step under an interrupt that wake() fires when the plan key moves
|
|
4456
|
+
* on. An interrupted step's result is discarded. The interrupt is also
|
|
4457
|
+
* fired when the step returns, which releases whatever it left open: the
|
|
4458
|
+
* request signal and its listeners, an unread response body, a timer.
|
|
4459
|
+
*/
|
|
4460
|
+
async phase(key, step) {
|
|
4461
|
+
const interrupt = new AbortController();
|
|
4462
|
+
this._phase = { key, interrupt };
|
|
4463
|
+
try {
|
|
4464
|
+
if (this.plan()?.key !== key) {
|
|
4465
|
+
interrupt.abort();
|
|
4466
|
+
}
|
|
4467
|
+
const result = await step(interrupt.signal);
|
|
4468
|
+
return interrupt.signal.aborted ? void 0 : result;
|
|
4469
|
+
} finally {
|
|
4470
|
+
this._phase = void 0;
|
|
4471
|
+
interrupt.abort();
|
|
4472
|
+
}
|
|
4473
|
+
}
|
|
4474
|
+
/**
|
|
4475
|
+
* One mint → open → drain. The single catch site for everything the
|
|
4476
|
+
* attempt touches that the SDK does not own: context accessors, invoke,
|
|
4477
|
+
* fetch, response bodies, the SSE parser. Never rejects.
|
|
4478
|
+
*/
|
|
4479
|
+
async attempt(plan, signal, health) {
|
|
4480
|
+
let phase = "mint";
|
|
4481
|
+
let request;
|
|
4482
|
+
const owns = () => !signal.aborted && !request?.signal.aborted && this.plan()?.key === plan.key;
|
|
4424
4483
|
try {
|
|
4425
|
-
const
|
|
4426
|
-
const
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4484
|
+
const context = this.context;
|
|
4485
|
+
const contextSignal = context.abortSignal;
|
|
4486
|
+
this._lastContextSignal = contextSignal;
|
|
4487
|
+
if (!context.isAuthenticated || contextSignal?.aborted) {
|
|
4488
|
+
return { kind: "stopped" };
|
|
4489
|
+
}
|
|
4490
|
+
request = this.createRequestSignal(signal, 0);
|
|
4491
|
+
const host = this.host;
|
|
4492
|
+
const minted = await awaitOwned(
|
|
4493
|
+
request.signal,
|
|
4494
|
+
() => context.fetch(`${host}/hooks/tickets`, {
|
|
4495
|
+
method: "POST",
|
|
4496
|
+
headers: {
|
|
4497
|
+
...serviceHeadersToRecord(this.createInvokeHeaders(plan.subscriptions)),
|
|
4498
|
+
"content-type": "application/json"
|
|
4499
|
+
},
|
|
4500
|
+
body: JSON.stringify({
|
|
4501
|
+
subscriptions: plan.subscriptions,
|
|
4502
|
+
ttlSeconds: plan.ttlSeconds
|
|
4503
|
+
}),
|
|
4504
|
+
signal: request.signal
|
|
4505
|
+
})
|
|
4430
4506
|
);
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4507
|
+
if (!owns()) return { kind: "stopped" };
|
|
4508
|
+
if (!minted.ok) {
|
|
4509
|
+
const status = minted.status;
|
|
4510
|
+
const error = await awaitOwned(
|
|
4511
|
+
request.signal,
|
|
4512
|
+
() => responseError("hooks", "failed to mint hook ticket", minted)
|
|
4513
|
+
);
|
|
4514
|
+
if (!owns()) return { kind: "stopped" };
|
|
4515
|
+
return { kind: "refused", terminal: isTerminalMintRefusal(status), error };
|
|
4516
|
+
}
|
|
4517
|
+
const body = await awaitOwned(request.signal, () => minted.json());
|
|
4518
|
+
if (!owns()) return { kind: "stopped" };
|
|
4519
|
+
const ticket = body !== null && typeof body === "object" && "ticket" in body ? body.ticket : void 0;
|
|
4520
|
+
if (typeof ticket !== "string" || ticket.length === 0) {
|
|
4521
|
+
throw new Error("Hook ticket response did not include a ticket");
|
|
4522
|
+
}
|
|
4523
|
+
phase = "open";
|
|
4524
|
+
if (!owns()) return { kind: "stopped" };
|
|
4525
|
+
const stream = await awaitOwned(
|
|
4526
|
+
request.signal,
|
|
4527
|
+
() => context.fetch(`${host}/hooks/events?ticket=${encodeURIComponent(ticket)}`, {
|
|
4528
|
+
method: "GET",
|
|
4529
|
+
headers: { accept: "text/event-stream" },
|
|
4530
|
+
signal: request.signal
|
|
4531
|
+
})
|
|
4435
4532
|
);
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
if (
|
|
4446
|
-
|
|
4447
|
-
|
|
4533
|
+
if (!owns()) return { kind: "stopped" };
|
|
4534
|
+
if (!stream.ok) {
|
|
4535
|
+
return { kind: "refused", terminal: false, error: hookStreamRefusal(stream.status) };
|
|
4536
|
+
}
|
|
4537
|
+
health.openedAt = performance.now();
|
|
4538
|
+
const iterator = parseSseStream(stream.body, request.signal)[Symbol.asyncIterator]();
|
|
4539
|
+
try {
|
|
4540
|
+
for (; ; ) {
|
|
4541
|
+
const next = await awaitOwned(request.signal, () => iterator.next());
|
|
4542
|
+
if (!owns()) return { kind: "stopped" };
|
|
4543
|
+
if (next.done) break;
|
|
4544
|
+
const message = next.value;
|
|
4545
|
+
if (!message.data) continue;
|
|
4546
|
+
const event = parseHookEvent(message);
|
|
4547
|
+
if (!owns()) return { kind: "stopped" };
|
|
4548
|
+
this.dispatch(event);
|
|
4549
|
+
health.delivered = true;
|
|
4448
4550
|
}
|
|
4551
|
+
} finally {
|
|
4552
|
+
const closing = iterator.return?.();
|
|
4553
|
+
if (closing) void Promise.resolve(closing).then(() => void 0, () => void 0);
|
|
4449
4554
|
}
|
|
4555
|
+
return owns() ? { kind: "ended" } : { kind: "stopped" };
|
|
4556
|
+
} catch (thrown) {
|
|
4557
|
+
return request?.signal.aborted || thrown === SUPERSEDED ? { kind: "stopped" } : { kind: "failed", error: hookStreamFailure(phase, thrown) };
|
|
4558
|
+
}
|
|
4559
|
+
}
|
|
4560
|
+
/**
|
|
4561
|
+
* Back off before the next attempt. `config.streamRetry` overrides the
|
|
4562
|
+
* delay and the clock; whatever they do, the pause ends by the delay or the
|
|
4563
|
+
* interrupt, then yields one timer turn so an instant clock cannot starve
|
|
4564
|
+
* due timers or the process.
|
|
4565
|
+
*/
|
|
4566
|
+
async pause(attempt, signal) {
|
|
4567
|
+
try {
|
|
4568
|
+
this._lastContextSignal = this.context.abortSignal;
|
|
4569
|
+
} catch {
|
|
4570
|
+
}
|
|
4571
|
+
const contextSignal = this._lastContextSignal;
|
|
4572
|
+
const waits = [];
|
|
4573
|
+
const aborts = [];
|
|
4574
|
+
try {
|
|
4575
|
+
const retry = this._config.streamRetry;
|
|
4576
|
+
const delayMs = retry?.delay?.(attempt) ?? hookStreamRetryDelay(attempt);
|
|
4577
|
+
waits.push((retry?.wait ?? sleep)(delayMs, attempt, signal));
|
|
4578
|
+
} catch {
|
|
4579
|
+
waits.push(Promise.resolve());
|
|
4580
|
+
}
|
|
4581
|
+
aborts.push(abortPromise(signal));
|
|
4582
|
+
if (contextSignal) aborts.push(abortPromise(contextSignal));
|
|
4583
|
+
try {
|
|
4584
|
+
await Promise.race([...waits, ...aborts.map(({ promise }) => promise)]);
|
|
4585
|
+
} catch {
|
|
4450
4586
|
} finally {
|
|
4451
|
-
|
|
4452
|
-
|
|
4587
|
+
for (const abort of aborts) abort.dispose();
|
|
4588
|
+
}
|
|
4589
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
4590
|
+
}
|
|
4591
|
+
/** The stream the current subscribers need, or undefined when none remain. */
|
|
4592
|
+
plan() {
|
|
4593
|
+
if (this._subscribers.size === 0) {
|
|
4594
|
+
return void 0;
|
|
4595
|
+
}
|
|
4596
|
+
const merged = /* @__PURE__ */ new Map();
|
|
4597
|
+
let ttlSeconds;
|
|
4598
|
+
for (const subscriber of this._subscribers) {
|
|
4599
|
+
subscriber.requested.forEach(
|
|
4600
|
+
(subscription, index) => merged.set(subscriber.signatures[index], subscription)
|
|
4601
|
+
);
|
|
4602
|
+
if (typeof subscriber.ttlSeconds === "number") {
|
|
4603
|
+
ttlSeconds = Math.min(subscriber.ttlSeconds, ttlSeconds ?? Infinity);
|
|
4453
4604
|
}
|
|
4454
4605
|
}
|
|
4606
|
+
const signatures = [...merged.keys()].sort();
|
|
4607
|
+
return {
|
|
4608
|
+
key: JSON.stringify([this._sessionGeneration, signatures, ttlSeconds ?? null]),
|
|
4609
|
+
subscriptions: signatures.map((signature) => merged.get(signature)),
|
|
4610
|
+
ttlSeconds
|
|
4611
|
+
};
|
|
4455
4612
|
}
|
|
4456
|
-
|
|
4457
|
-
this.
|
|
4613
|
+
dispatch(event) {
|
|
4614
|
+
for (const subscriber of this._subscribers) {
|
|
4615
|
+
if (matchesAnySubscription(event, subscriber.requested)) {
|
|
4616
|
+
subscriber.queue.push(event);
|
|
4617
|
+
}
|
|
4618
|
+
}
|
|
4458
4619
|
}
|
|
4459
|
-
|
|
4460
|
-
|
|
4620
|
+
/**
|
|
4621
|
+
* Detach every subscriber. With `error`, their iterators throw it after
|
|
4622
|
+
* draining buffered events; without, they complete.
|
|
4623
|
+
*/
|
|
4624
|
+
release(error) {
|
|
4625
|
+
const subscribers = [...this._subscribers];
|
|
4626
|
+
this._subscribers.clear();
|
|
4627
|
+
for (const { queue } of subscribers) {
|
|
4628
|
+
if (error) {
|
|
4629
|
+
queue.fail(error);
|
|
4630
|
+
} else {
|
|
4631
|
+
queue.close();
|
|
4632
|
+
}
|
|
4633
|
+
}
|
|
4461
4634
|
}
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
const ticketResponse = await this.context.fetch(`${host}/hooks/tickets`, {
|
|
4466
|
-
method: "POST",
|
|
4467
|
-
headers: {
|
|
4468
|
-
...serviceHeadersToRecord(headers),
|
|
4469
|
-
"content-type": "application/json"
|
|
4470
|
-
},
|
|
4471
|
-
body: JSON.stringify({
|
|
4472
|
-
subscriptions,
|
|
4473
|
-
ttlSeconds
|
|
4474
|
-
}),
|
|
4475
|
-
signal
|
|
4476
|
-
});
|
|
4477
|
-
if (!ticketResponse.ok) {
|
|
4478
|
-
throw await responseError(
|
|
4479
|
-
"hooks",
|
|
4480
|
-
"failed to mint hook ticket",
|
|
4481
|
-
ticketResponse
|
|
4482
|
-
);
|
|
4635
|
+
report(error) {
|
|
4636
|
+
if (error.meta && typeof error.meta === "object") {
|
|
4637
|
+
Object.freeze(error.meta);
|
|
4483
4638
|
}
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4639
|
+
Object.freeze(error);
|
|
4640
|
+
try {
|
|
4641
|
+
this.emitError(error, "stream");
|
|
4642
|
+
} catch {
|
|
4487
4643
|
}
|
|
4488
|
-
return ticketJson;
|
|
4489
4644
|
}
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4645
|
+
/**
|
|
4646
|
+
* Unreachable by construction. If the loop ever rejects anyway, fail its
|
|
4647
|
+
* subscribers visibly instead of stranding them or crashing the process.
|
|
4648
|
+
*/
|
|
4649
|
+
collapse() {
|
|
4650
|
+
this._supervising = false;
|
|
4651
|
+
this._phase?.interrupt.abort();
|
|
4652
|
+
this._phase = void 0;
|
|
4653
|
+
const error = serviceError(
|
|
4654
|
+
ErrorCodes.NETWORK_ERROR,
|
|
4655
|
+
"hook stream supervisor stopped unexpectedly",
|
|
4656
|
+
"hooks"
|
|
4498
4657
|
);
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4658
|
+
this.report(error);
|
|
4659
|
+
this.release(error);
|
|
4660
|
+
}
|
|
4661
|
+
onSessionChange(session) {
|
|
4662
|
+
super.onSessionChange(session);
|
|
4663
|
+
this._sessionGeneration += 1;
|
|
4664
|
+
this.wake();
|
|
4665
|
+
}
|
|
4666
|
+
onSignOut() {
|
|
4667
|
+
super.onSignOut();
|
|
4668
|
+
this.release();
|
|
4669
|
+
this.wake();
|
|
4670
|
+
}
|
|
4671
|
+
createHookHeaders(action, path) {
|
|
4672
|
+
return this.context.invoke(this.session, "hooks", path, action);
|
|
4507
4673
|
}
|
|
4508
4674
|
createInvokeHeaders(subscriptions) {
|
|
4509
4675
|
const entries = subscriptions.map((subscription) => ({
|
|
@@ -4534,11 +4700,168 @@ function buildScopePath(service, pathPrefix) {
|
|
|
4534
4700
|
const normalized = normalizePathPrefix(pathPrefix);
|
|
4535
4701
|
return normalized ? `${service}/${normalized}` : service;
|
|
4536
4702
|
}
|
|
4703
|
+
function hookStreamRetryDelay(attempt) {
|
|
4704
|
+
const cap = Math.min(
|
|
4705
|
+
HOOK_STREAM_RETRY_BASE_DELAY_MS * 2 ** Math.max(0, attempt - 1),
|
|
4706
|
+
HOOK_STREAM_RETRY_MAX_DELAY_MS
|
|
4707
|
+
);
|
|
4708
|
+
return cap / 2 + Math.random() * (cap / 2);
|
|
4709
|
+
}
|
|
4710
|
+
function isTerminalMintRefusal(status) {
|
|
4711
|
+
return typeof status === "number" && status >= 400 && status < 500 && status !== 408 && status !== 429;
|
|
4712
|
+
}
|
|
4713
|
+
var SUPERSEDED = /* @__PURE__ */ Symbol("hooks attempt superseded");
|
|
4714
|
+
async function awaitOwned(signal, operation) {
|
|
4715
|
+
if (signal.aborted) {
|
|
4716
|
+
if (typeof operation !== "function") {
|
|
4717
|
+
void operation.then(cancelLateResponse, () => void 0);
|
|
4718
|
+
}
|
|
4719
|
+
throw SUPERSEDED;
|
|
4720
|
+
}
|
|
4721
|
+
const promise = typeof operation === "function" ? operation() : operation;
|
|
4722
|
+
return new Promise((resolve, reject) => {
|
|
4723
|
+
let settled = false;
|
|
4724
|
+
const cleanup = () => signal.removeEventListener("abort", onAbort);
|
|
4725
|
+
const onAbort = () => {
|
|
4726
|
+
if (settled) return;
|
|
4727
|
+
settled = true;
|
|
4728
|
+
cleanup();
|
|
4729
|
+
reject(SUPERSEDED);
|
|
4730
|
+
};
|
|
4731
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
4732
|
+
promise.then(
|
|
4733
|
+
(value) => {
|
|
4734
|
+
if (settled) {
|
|
4735
|
+
cancelLateResponse(value);
|
|
4736
|
+
return;
|
|
4737
|
+
}
|
|
4738
|
+
settled = true;
|
|
4739
|
+
cleanup();
|
|
4740
|
+
resolve(value);
|
|
4741
|
+
},
|
|
4742
|
+
(error) => {
|
|
4743
|
+
if (settled) return;
|
|
4744
|
+
settled = true;
|
|
4745
|
+
cleanup();
|
|
4746
|
+
reject(error);
|
|
4747
|
+
}
|
|
4748
|
+
);
|
|
4749
|
+
});
|
|
4750
|
+
}
|
|
4751
|
+
function cancelLateResponse(value) {
|
|
4752
|
+
if (!(value instanceof Response) || !value.body) return;
|
|
4753
|
+
try {
|
|
4754
|
+
const cancellation = value.body.cancel();
|
|
4755
|
+
void cancellation.then(() => void 0, () => void 0);
|
|
4756
|
+
} catch {
|
|
4757
|
+
}
|
|
4758
|
+
}
|
|
4759
|
+
function hookStreamRefusal(status) {
|
|
4760
|
+
const code = typeof status === "number" ? status : void 0;
|
|
4761
|
+
return serviceError(
|
|
4762
|
+
ErrorCodes.NETWORK_ERROR,
|
|
4763
|
+
`hook stream open refused: ${code ?? "unknown status"}`,
|
|
4764
|
+
"hooks",
|
|
4765
|
+
{ meta: { phase: "open", status: code } }
|
|
4766
|
+
);
|
|
4767
|
+
}
|
|
4768
|
+
var STREAM_ERROR_NAMES = /* @__PURE__ */ new Set([
|
|
4769
|
+
"Error",
|
|
4770
|
+
"TypeError",
|
|
4771
|
+
"AbortError",
|
|
4772
|
+
"TimeoutError",
|
|
4773
|
+
"SyntaxError",
|
|
4774
|
+
"RangeError",
|
|
4775
|
+
"NetworkError"
|
|
4776
|
+
]);
|
|
4777
|
+
function hookStreamFailure(phase, thrown) {
|
|
4778
|
+
const candidateName = readProperty(thrown, "name");
|
|
4779
|
+
const name = typeof candidateName === "string" && STREAM_ERROR_NAMES.has(candidateName) ? candidateName : "Error";
|
|
4780
|
+
const code = identifier(readProperty(thrown, "code")) ?? identifier(readProperty(readProperty(thrown, "cause"), "code"));
|
|
4781
|
+
return serviceError(
|
|
4782
|
+
ErrorCodes.NETWORK_ERROR,
|
|
4783
|
+
`hook stream ${phase} failed: ${name}${code ? ` (${code})` : ""}`,
|
|
4784
|
+
"hooks",
|
|
4785
|
+
{ meta: { phase, errorName: name, ...code ? { errorCode: code } : {} } }
|
|
4786
|
+
);
|
|
4787
|
+
}
|
|
4788
|
+
var STREAM_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
4789
|
+
"ECONNREFUSED",
|
|
4790
|
+
"ECONNRESET",
|
|
4791
|
+
"ECONNABORTED",
|
|
4792
|
+
"ETIMEDOUT",
|
|
4793
|
+
"EPIPE",
|
|
4794
|
+
"ENOTFOUND",
|
|
4795
|
+
"EAI_AGAIN",
|
|
4796
|
+
"EHOSTUNREACH",
|
|
4797
|
+
"ENETUNREACH",
|
|
4798
|
+
"ENETDOWN",
|
|
4799
|
+
"EADDRNOTAVAIL",
|
|
4800
|
+
"UND_ERR_SOCKET",
|
|
4801
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
4802
|
+
"UND_ERR_HEADERS_TIMEOUT",
|
|
4803
|
+
"UND_ERR_BODY_TIMEOUT",
|
|
4804
|
+
"UND_ERR_CLOSED",
|
|
4805
|
+
"UND_ERR_ABORTED",
|
|
4806
|
+
"UND_ERR_RESPONSE_STATUS_CODE",
|
|
4807
|
+
"ERR_TLS_CERT_ALTNAME_INVALID",
|
|
4808
|
+
"CERT_HAS_EXPIRED",
|
|
4809
|
+
"DEPTH_ZERO_SELF_SIGNED_CERT",
|
|
4810
|
+
"UNABLE_TO_VERIFY_LEAF_SIGNATURE",
|
|
4811
|
+
"SELF_SIGNED_CERT_IN_CHAIN",
|
|
4812
|
+
"ERR_SSL_WRONG_VERSION_NUMBER"
|
|
4813
|
+
]);
|
|
4814
|
+
function identifier(value) {
|
|
4815
|
+
return typeof value === "string" && STREAM_ERROR_CODES.has(value) ? value : void 0;
|
|
4816
|
+
}
|
|
4817
|
+
function readProperty(value, key) {
|
|
4818
|
+
if (value === null || typeof value !== "object" && typeof value !== "function") {
|
|
4819
|
+
return void 0;
|
|
4820
|
+
}
|
|
4821
|
+
try {
|
|
4822
|
+
return Reflect.get(value, key);
|
|
4823
|
+
} catch {
|
|
4824
|
+
return void 0;
|
|
4825
|
+
}
|
|
4826
|
+
}
|
|
4827
|
+
function sleep(delayMs, _attempt, signal) {
|
|
4828
|
+
return new Promise((resolve) => {
|
|
4829
|
+
const finish = () => {
|
|
4830
|
+
clearTimeout(timer);
|
|
4831
|
+
signal.removeEventListener("abort", stop);
|
|
4832
|
+
resolve();
|
|
4833
|
+
};
|
|
4834
|
+
const timer = setTimeout(finish, delayMs);
|
|
4835
|
+
timer.unref?.();
|
|
4836
|
+
const stop = () => finish();
|
|
4837
|
+
if (signal.aborted) {
|
|
4838
|
+
finish();
|
|
4839
|
+
} else {
|
|
4840
|
+
signal.addEventListener("abort", stop, { once: true });
|
|
4841
|
+
}
|
|
4842
|
+
});
|
|
4843
|
+
}
|
|
4844
|
+
function abortPromise(signal) {
|
|
4845
|
+
let resolve;
|
|
4846
|
+
const promise = new Promise((done) => {
|
|
4847
|
+
resolve = done;
|
|
4848
|
+
});
|
|
4849
|
+
const onAbort = () => resolve();
|
|
4850
|
+
if (signal.aborted) {
|
|
4851
|
+
resolve();
|
|
4852
|
+
} else {
|
|
4853
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
4854
|
+
}
|
|
4855
|
+
return {
|
|
4856
|
+
promise,
|
|
4857
|
+
dispose: () => signal.removeEventListener("abort", onAbort)
|
|
4858
|
+
};
|
|
4859
|
+
}
|
|
4537
4860
|
function normalizeSubscription(subscription) {
|
|
4538
4861
|
return {
|
|
4539
4862
|
...subscription,
|
|
4540
4863
|
pathPrefix: normalizePathPrefix(subscription.pathPrefix),
|
|
4541
|
-
abilities: subscription.abilities
|
|
4864
|
+
abilities: subscription.abilities ? [...subscription.abilities] : []
|
|
4542
4865
|
};
|
|
4543
4866
|
}
|
|
4544
4867
|
function subscriptionSignature(subscription) {
|
|
@@ -4604,9 +4927,6 @@ async function responseError(service, message, response) {
|
|
|
4604
4927
|
meta: { ...error.meta, status: response.status, statusText: response.statusText }
|
|
4605
4928
|
};
|
|
4606
4929
|
}
|
|
4607
|
-
function isAbortError(error) {
|
|
4608
|
-
return error instanceof DOMException && error.name === "AbortError";
|
|
4609
|
-
}
|
|
4610
4930
|
async function* parseSseStream(body, signal) {
|
|
4611
4931
|
if (!body) {
|
|
4612
4932
|
throw new Error("Hook stream response does not expose a readable body");
|
|
@@ -4635,11 +4955,24 @@ async function* parseSseStream(body, signal) {
|
|
|
4635
4955
|
async function* readBodyChunks(body, signal) {
|
|
4636
4956
|
const asyncIterable = body;
|
|
4637
4957
|
if (typeof asyncIterable?.[Symbol.asyncIterator] === "function") {
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4958
|
+
const iterator = asyncIterable[Symbol.asyncIterator]();
|
|
4959
|
+
const cancel = () => {
|
|
4960
|
+
try {
|
|
4961
|
+
const closing = iterator.return?.();
|
|
4962
|
+
if (closing) void Promise.resolve(closing).then(() => void 0, () => void 0);
|
|
4963
|
+
} catch {
|
|
4964
|
+
}
|
|
4965
|
+
};
|
|
4966
|
+
signal?.addEventListener("abort", cancel, { once: true });
|
|
4967
|
+
try {
|
|
4968
|
+
while (!signal?.aborted) {
|
|
4969
|
+
const next = await iterator.next();
|
|
4970
|
+
if (next.done) break;
|
|
4971
|
+
yield next.value;
|
|
4641
4972
|
}
|
|
4642
|
-
|
|
4973
|
+
} finally {
|
|
4974
|
+
signal?.removeEventListener("abort", cancel);
|
|
4975
|
+
cancel();
|
|
4643
4976
|
}
|
|
4644
4977
|
return;
|
|
4645
4978
|
}
|
|
@@ -4648,21 +4981,23 @@ async function* readBodyChunks(body, signal) {
|
|
|
4648
4981
|
throw new Error("Unsupported hook stream body type");
|
|
4649
4982
|
}
|
|
4650
4983
|
const reader = stream.getReader();
|
|
4984
|
+
const cancelReader = () => {
|
|
4985
|
+
try {
|
|
4986
|
+
const cancellation = reader.cancel?.();
|
|
4987
|
+
if (cancellation) void cancellation.then(() => void 0, () => void 0);
|
|
4988
|
+
} catch {
|
|
4989
|
+
}
|
|
4990
|
+
};
|
|
4991
|
+
signal?.addEventListener("abort", cancelReader, { once: true });
|
|
4651
4992
|
try {
|
|
4652
4993
|
while (!signal?.aborted) {
|
|
4653
4994
|
const { done, value } = await reader.read();
|
|
4654
|
-
if (done)
|
|
4655
|
-
|
|
4656
|
-
}
|
|
4657
|
-
if (value) {
|
|
4658
|
-
yield value;
|
|
4659
|
-
}
|
|
4995
|
+
if (done) break;
|
|
4996
|
+
if (value) yield value;
|
|
4660
4997
|
}
|
|
4661
4998
|
} finally {
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
} catch {
|
|
4665
|
-
}
|
|
4999
|
+
signal?.removeEventListener("abort", cancelReader);
|
|
5000
|
+
cancelReader();
|
|
4666
5001
|
reader.releaseLock?.();
|
|
4667
5002
|
}
|
|
4668
5003
|
}
|
|
@@ -7306,10 +7641,10 @@ var EncryptionService = class extends BaseService {
|
|
|
7306
7641
|
get crypto() {
|
|
7307
7642
|
return this._config.crypto;
|
|
7308
7643
|
}
|
|
7309
|
-
async discoverNetwork(
|
|
7644
|
+
async discoverNetwork(identifier2, ownerDid) {
|
|
7310
7645
|
this.assertActive();
|
|
7311
7646
|
const result = await discoverNetwork({
|
|
7312
|
-
identifier,
|
|
7647
|
+
identifier: identifier2,
|
|
7313
7648
|
...ownerDid !== void 0 ? { ownerDid } : {},
|
|
7314
7649
|
...this._config.node !== void 0 ? { node: this._config.node } : {},
|
|
7315
7650
|
...this._config.wellKnown !== void 0 ? { wellKnown: this._config.wellKnown } : {}
|