@tinycloud/cli 1.1.0-beta.19 → 1.1.0-beta.20

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.
@@ -591,8 +591,8 @@ function canonicalizeAddress(address) {
591
591
  const trimmed = address.trim();
592
592
  return trimmed.startsWith("0x") ? `0x${trimmed.slice(2).toLowerCase()}` : trimmed.toLowerCase();
593
593
  }
594
- function normalizePkhIdentifier(identifier) {
595
- return identifier.replace(
594
+ function normalizePkhIdentifier(identifier2) {
595
+ return identifier2.replace(
596
596
  /(eip155:\d+:)(0x[0-9a-fA-F]{40})(?=[:#]|$)/,
597
597
  (_match, prefix, address) => prefix + address.toLowerCase()
598
598
  );
@@ -10000,7 +10000,7 @@ var AsyncQueue = class {
10000
10000
  }
10001
10001
  const waiter = this.waiters.shift();
10002
10002
  if (waiter) {
10003
- waiter({ value, done: false });
10003
+ waiter.resolve({ value, done: false });
10004
10004
  return;
10005
10005
  }
10006
10006
  this.values.push(value);
@@ -10012,7 +10012,22 @@ var AsyncQueue = class {
10012
10012
  this.closed = true;
10013
10013
  while (this.waiters.length > 0) {
10014
10014
  const waiter = this.waiters.shift();
10015
- waiter?.({ value: void 0, done: true });
10015
+ waiter?.resolve({ value: void 0, done: true });
10016
+ }
10017
+ }
10018
+ /**
10019
+ * Fail the queue: pending and future `next()` calls reject with `error`,
10020
+ * so a `for await` consumer sees the iterator throw.
10021
+ */
10022
+ fail(error) {
10023
+ if (this.closed) {
10024
+ return;
10025
+ }
10026
+ this.closed = true;
10027
+ this.failure = error;
10028
+ while (this.waiters.length > 0) {
10029
+ const waiter = this.waiters.shift();
10030
+ waiter?.reject(error);
10016
10031
  }
10017
10032
  }
10018
10033
  next() {
@@ -10020,20 +10035,26 @@ var AsyncQueue = class {
10020
10035
  const value = this.values.shift();
10021
10036
  return Promise.resolve({ value, done: false });
10022
10037
  }
10038
+ if (this.failure !== void 0) {
10039
+ return Promise.reject(this.failure);
10040
+ }
10023
10041
  if (this.closed) {
10024
10042
  return Promise.resolve({ value: void 0, done: true });
10025
10043
  }
10026
- return new Promise((resolve5) => {
10027
- this.waiters.push(resolve5);
10044
+ return new Promise((resolve5, reject) => {
10045
+ this.waiters.push({ resolve: resolve5, reject });
10028
10046
  });
10029
10047
  }
10030
10048
  };
10049
+ var HOOK_STREAM_RETRY_BASE_DELAY_MS = 250;
10050
+ var HOOK_STREAM_RETRY_MAX_DELAY_MS = 3e4;
10051
+ var HOOK_STREAM_HEALTHY_MS = 5e3;
10031
10052
  var HooksService = class extends BaseService {
10032
10053
  constructor(config = {}) {
10033
10054
  super();
10034
10055
  this._subscribers = /* @__PURE__ */ new Set();
10035
- this._refreshChain = Promise.resolve();
10036
- this._activeSignature = "";
10056
+ this._supervising = false;
10057
+ this._sessionGeneration = 0;
10037
10058
  this._config = config;
10038
10059
  }
10039
10060
  get config() {
@@ -10049,35 +10070,32 @@ var HooksService = class extends BaseService {
10049
10070
  if (subscriptions.length === 0) {
10050
10071
  throw new Error("At least one hook subscription is required");
10051
10072
  }
10052
- const normalized = subscriptions.map(normalizeSubscription);
10073
+ const requested = subscriptions.map(normalizeSubscription);
10053
10074
  const subscriber = {
10054
- requested: normalized,
10075
+ requested,
10076
+ signatures: requested.map(subscriptionSignature),
10055
10077
  ttlSeconds: options.ttlSeconds,
10056
10078
  queue: new AsyncQueue()
10057
10079
  };
10058
- this._subscribers.add(subscriber);
10059
- const abortHandler = () => {
10080
+ const leave = () => {
10060
10081
  this._subscribers.delete(subscriber);
10061
10082
  subscriber.queue.close();
10062
- void this.scheduleSharedStreamRefresh();
10083
+ this.wake();
10063
10084
  };
10064
- if (options.signal) {
10065
- if (options.signal.aborted) {
10066
- abortHandler();
10067
- } else {
10068
- options.signal.addEventListener("abort", abortHandler, { once: true });
10069
- }
10085
+ const { signal } = options;
10086
+ if (signal?.aborted) {
10087
+ return;
10070
10088
  }
10071
- void this.scheduleSharedStreamRefresh();
10089
+ signal?.addEventListener("abort", leave, { once: true });
10090
+ this._subscribers.add(subscriber);
10091
+ this.wake();
10072
10092
  try {
10073
10093
  for await (const event of subscriber.queue) {
10074
10094
  yield event;
10075
10095
  }
10076
10096
  } finally {
10077
- if (options.signal) {
10078
- options.signal.removeEventListener("abort", abortHandler);
10079
- }
10080
- abortHandler();
10097
+ signal?.removeEventListener("abort", leave);
10098
+ leave();
10081
10099
  }
10082
10100
  }
10083
10101
  async register(webhook) {
@@ -10218,146 +10236,292 @@ var HooksService = class extends BaseService {
10218
10236
  return err(wrapError2("hooks", error));
10219
10237
  }
10220
10238
  }
10221
- async scheduleSharedStreamRefresh() {
10222
- this._refreshChain = this._refreshChain.then(() => this.refreshSharedStream()).catch(() => void 0);
10223
- await this._refreshChain;
10224
- }
10225
- async refreshSharedStream() {
10226
- if (!this.requireAuth() || this._subscribers.size === 0) {
10227
- this.abortSharedStream();
10228
- this._activeSignature = "";
10229
- return;
10230
- }
10231
- const state = this.collectSharedStreamState();
10232
- if (state.signature !== this._activeSignature) {
10233
- this._activeSignature = state.signature;
10234
- this.abortSharedStream();
10239
+ /**
10240
+ * Called after anything that may change what the supervisor should do:
10241
+ * subscribe, unsubscribe, session change, sign-out. Interrupts the running
10242
+ * step if the plan moved away from it, and starts the supervisor if none
10243
+ * runs. Touches only SDK-owned state, so it never throws into callers such
10244
+ * as `ServiceContext.setSession`.
10245
+ */
10246
+ wake() {
10247
+ const phase = this._phase;
10248
+ if (phase && phase.key !== this.plan()?.key) {
10249
+ phase.interrupt.abort();
10235
10250
  }
10236
- if (!this._sharedStreamTask) {
10237
- this._sharedStreamTask = this.runSharedStream(state).catch((error) => {
10238
- if (!isAbortError(error)) {
10239
- throw error;
10240
- }
10241
- }).finally(() => {
10242
- this._sharedStreamTask = void 0;
10243
- this._sharedStreamAbort = void 0;
10244
- if (this._subscribers.size > 0) {
10245
- void this.scheduleSharedStreamRefresh();
10246
- }
10247
- });
10251
+ if (this._supervising || this._subscribers.size === 0) {
10252
+ return;
10248
10253
  }
10254
+ this._supervising = true;
10255
+ this.supervise().catch(() => this.collapse());
10249
10256
  }
10250
- collectSharedStreamState() {
10251
- const merged = /* @__PURE__ */ new Map();
10252
- const ttlCandidates = [];
10253
- for (const subscriber of this._subscribers) {
10254
- if (typeof subscriber.ttlSeconds === "number") {
10255
- ttlCandidates.push(subscriber.ttlSeconds);
10257
+ /**
10258
+ * The only owner of the shared stream. Each iteration is one attempt
10259
+ * (mint → open → drain), then a classification and, unless the attempt was
10260
+ * superseded or released its subscribers, a backoff. Every await is a
10261
+ * `phase` that wake() can interrupt; an interrupted phase yields
10262
+ * `undefined`, so a superseded mint can never act. Outside `attempt` and
10263
+ * `pause`, which each own one catch, the loop touches only SDK state.
10264
+ */
10265
+ async supervise() {
10266
+ let streak = 0;
10267
+ for (; ; ) {
10268
+ const plan = this.plan();
10269
+ if (!plan) {
10270
+ this._supervising = false;
10271
+ return;
10272
+ }
10273
+ const health = { openedAt: 0, delivered: false };
10274
+ const outcome = await this.phase(
10275
+ plan.key,
10276
+ (signal) => this.attempt(plan, signal, health)
10277
+ );
10278
+ if (health.delivered || health.openedAt > 0 && performance.now() - health.openedAt >= HOOK_STREAM_HEALTHY_MS) {
10279
+ streak = 0;
10256
10280
  }
10257
- for (const subscription of subscriber.requested) {
10258
- merged.set(subscriptionSignature(subscription), subscription);
10281
+ if (!outcome) {
10282
+ continue;
10259
10283
  }
10284
+ if (this.plan()?.key !== plan.key) {
10285
+ continue;
10286
+ }
10287
+ if (outcome.kind === "stopped") {
10288
+ this.release();
10289
+ continue;
10290
+ }
10291
+ if (outcome.kind === "refused" && outcome.terminal) {
10292
+ this.release(outcome.error);
10293
+ this.report(outcome.error);
10294
+ continue;
10295
+ }
10296
+ if (outcome.kind !== "ended") {
10297
+ this.report(outcome.error);
10298
+ }
10299
+ streak += 1;
10300
+ await this.phase(
10301
+ plan.key,
10302
+ (signal) => this.pause(streak, signal)
10303
+ );
10260
10304
  }
10261
- const subscriptions = [...merged.values()].sort(
10262
- (left, right) => subscriptionSignature(left).localeCompare(subscriptionSignature(right))
10263
- );
10264
- const ttlSeconds = ttlCandidates.length > 0 ? Math.min(...ttlCandidates) : void 0;
10265
- const signature = JSON.stringify({
10266
- subscriptions: subscriptions.map(subscriptionSignature),
10267
- ttlSeconds
10268
- });
10269
- return {
10270
- subscriptions,
10271
- ttlSeconds,
10272
- signature
10273
- };
10274
10305
  }
10275
- async runSharedStream(state) {
10276
- const abortController = new AbortController();
10277
- this._sharedStreamAbort = abortController;
10306
+ /**
10307
+ * Run one step under an interrupt that wake() fires when the plan key moves
10308
+ * on. An interrupted step's result is discarded. The interrupt is also
10309
+ * fired when the step returns, which releases whatever it left open: the
10310
+ * request signal and its listeners, an unread response body, a timer.
10311
+ */
10312
+ async phase(key, step) {
10313
+ const interrupt = new AbortController();
10314
+ this._phase = { key, interrupt };
10278
10315
  try {
10279
- const host = this._config.host ?? this.context.hosts[0];
10280
- const ticketResponse = await this.mintHookTicket(
10281
- state.subscriptions,
10282
- state.ttlSeconds,
10283
- abortController.signal
10316
+ if (this.plan()?.key !== key) {
10317
+ interrupt.abort();
10318
+ }
10319
+ const result = await step(interrupt.signal);
10320
+ return interrupt.signal.aborted ? void 0 : result;
10321
+ } finally {
10322
+ this._phase = void 0;
10323
+ interrupt.abort();
10324
+ }
10325
+ }
10326
+ /**
10327
+ * One mint → open → drain. The single catch site for everything the
10328
+ * attempt touches that the SDK does not own: context accessors, invoke,
10329
+ * fetch, response bodies, the SSE parser. Never rejects.
10330
+ */
10331
+ async attempt(plan, signal, health) {
10332
+ let phase = "mint";
10333
+ let request;
10334
+ const owns = () => !signal.aborted && !request?.signal.aborted && this.plan()?.key === plan.key;
10335
+ try {
10336
+ const context = this.context;
10337
+ const contextSignal = context.abortSignal;
10338
+ this._lastContextSignal = contextSignal;
10339
+ if (!context.isAuthenticated || contextSignal?.aborted) {
10340
+ return { kind: "stopped" };
10341
+ }
10342
+ request = this.createRequestSignal(signal, 0);
10343
+ const host = this.host;
10344
+ const minted = await awaitOwned(
10345
+ request.signal,
10346
+ () => context.fetch(`${host}/hooks/tickets`, {
10347
+ method: "POST",
10348
+ headers: {
10349
+ ...serviceHeadersToRecord(this.createInvokeHeaders(plan.subscriptions)),
10350
+ "content-type": "application/json"
10351
+ },
10352
+ body: JSON.stringify({
10353
+ subscriptions: plan.subscriptions,
10354
+ ttlSeconds: plan.ttlSeconds
10355
+ }),
10356
+ signal: request.signal
10357
+ })
10284
10358
  );
10285
- const streamResponse = await this.openHookStream(
10286
- host,
10287
- ticketResponse.ticket,
10288
- abortController.signal
10359
+ if (!owns()) return { kind: "stopped" };
10360
+ if (!minted.ok) {
10361
+ const status = minted.status;
10362
+ const error = await awaitOwned(
10363
+ request.signal,
10364
+ () => responseError("hooks", "failed to mint hook ticket", minted)
10365
+ );
10366
+ if (!owns()) return { kind: "stopped" };
10367
+ return { kind: "refused", terminal: isTerminalMintRefusal(status), error };
10368
+ }
10369
+ const body = await awaitOwned(request.signal, () => minted.json());
10370
+ if (!owns()) return { kind: "stopped" };
10371
+ const ticket = body !== null && typeof body === "object" && "ticket" in body ? body.ticket : void 0;
10372
+ if (typeof ticket !== "string" || ticket.length === 0) {
10373
+ throw new Error("Hook ticket response did not include a ticket");
10374
+ }
10375
+ phase = "open";
10376
+ if (!owns()) return { kind: "stopped" };
10377
+ const stream = await awaitOwned(
10378
+ request.signal,
10379
+ () => context.fetch(`${host}/hooks/events?ticket=${encodeURIComponent(ticket)}`, {
10380
+ method: "GET",
10381
+ headers: { accept: "text/event-stream" },
10382
+ signal: request.signal
10383
+ })
10289
10384
  );
10290
- for await (const message of parseSseStream(
10291
- streamResponse.body,
10292
- abortController.signal
10293
- )) {
10294
- if (!message.data) {
10295
- continue;
10296
- }
10297
- const event = parseHookEvent(message);
10298
- for (const subscriber of this._subscribers) {
10299
- if (matchesAnySubscription(event, subscriber.requested)) {
10300
- subscriber.queue.push(event);
10301
- }
10385
+ if (!owns()) return { kind: "stopped" };
10386
+ if (!stream.ok) {
10387
+ return { kind: "refused", terminal: false, error: hookStreamRefusal(stream.status) };
10388
+ }
10389
+ health.openedAt = performance.now();
10390
+ const iterator = parseSseStream(stream.body, request.signal)[Symbol.asyncIterator]();
10391
+ try {
10392
+ for (; ; ) {
10393
+ const next = await awaitOwned(request.signal, () => iterator.next());
10394
+ if (!owns()) return { kind: "stopped" };
10395
+ if (next.done) break;
10396
+ const message = next.value;
10397
+ if (!message.data) continue;
10398
+ const event = parseHookEvent(message);
10399
+ if (!owns()) return { kind: "stopped" };
10400
+ this.dispatch(event);
10401
+ health.delivered = true;
10302
10402
  }
10403
+ } finally {
10404
+ const closing = iterator.return?.();
10405
+ if (closing) void Promise.resolve(closing).then(() => void 0, () => void 0);
10303
10406
  }
10407
+ return owns() ? { kind: "ended" } : { kind: "stopped" };
10408
+ } catch (thrown) {
10409
+ return request?.signal.aborted || thrown === SUPERSEDED ? { kind: "stopped" } : { kind: "failed", error: hookStreamFailure(phase, thrown) };
10410
+ }
10411
+ }
10412
+ /**
10413
+ * Back off before the next attempt. `config.streamRetry` overrides the
10414
+ * delay and the clock; whatever they do, the pause ends by the delay or the
10415
+ * interrupt, then yields one timer turn so an instant clock cannot starve
10416
+ * due timers or the process.
10417
+ */
10418
+ async pause(attempt, signal) {
10419
+ try {
10420
+ this._lastContextSignal = this.context.abortSignal;
10421
+ } catch {
10422
+ }
10423
+ const contextSignal = this._lastContextSignal;
10424
+ const waits = [];
10425
+ const aborts = [];
10426
+ try {
10427
+ const retry = this._config.streamRetry;
10428
+ const delayMs = retry?.delay?.(attempt) ?? hookStreamRetryDelay(attempt);
10429
+ waits.push((retry?.wait ?? sleep)(delayMs, attempt, signal));
10430
+ } catch {
10431
+ waits.push(Promise.resolve());
10432
+ }
10433
+ aborts.push(abortPromise(signal));
10434
+ if (contextSignal) aborts.push(abortPromise(contextSignal));
10435
+ try {
10436
+ await Promise.race([...waits, ...aborts.map(({ promise }) => promise)]);
10437
+ } catch {
10304
10438
  } finally {
10305
- if (this._sharedStreamAbort === abortController) {
10306
- this._sharedStreamAbort = void 0;
10439
+ for (const abort of aborts) abort.dispose();
10440
+ }
10441
+ await new Promise((resolve5) => setTimeout(resolve5, 0));
10442
+ }
10443
+ /** The stream the current subscribers need, or undefined when none remain. */
10444
+ plan() {
10445
+ if (this._subscribers.size === 0) {
10446
+ return void 0;
10447
+ }
10448
+ const merged = /* @__PURE__ */ new Map();
10449
+ let ttlSeconds;
10450
+ for (const subscriber of this._subscribers) {
10451
+ subscriber.requested.forEach(
10452
+ (subscription, index) => merged.set(subscriber.signatures[index], subscription)
10453
+ );
10454
+ if (typeof subscriber.ttlSeconds === "number") {
10455
+ ttlSeconds = Math.min(subscriber.ttlSeconds, ttlSeconds ?? Infinity);
10307
10456
  }
10308
10457
  }
10458
+ const signatures = [...merged.keys()].sort();
10459
+ return {
10460
+ key: JSON.stringify([this._sessionGeneration, signatures, ttlSeconds ?? null]),
10461
+ subscriptions: signatures.map((signature) => merged.get(signature)),
10462
+ ttlSeconds
10463
+ };
10309
10464
  }
10310
- abortSharedStream() {
10311
- this._sharedStreamAbort?.abort();
10465
+ dispatch(event) {
10466
+ for (const subscriber of this._subscribers) {
10467
+ if (matchesAnySubscription(event, subscriber.requested)) {
10468
+ subscriber.queue.push(event);
10469
+ }
10470
+ }
10312
10471
  }
10313
- createHookHeaders(action, path) {
10314
- return this.context.invoke(this.session, "hooks", path, action);
10472
+ /**
10473
+ * Detach every subscriber. With `error`, their iterators throw it after
10474
+ * draining buffered events; without, they complete.
10475
+ */
10476
+ release(error) {
10477
+ const subscribers = [...this._subscribers];
10478
+ this._subscribers.clear();
10479
+ for (const { queue } of subscribers) {
10480
+ if (error) {
10481
+ queue.fail(error);
10482
+ } else {
10483
+ queue.close();
10484
+ }
10485
+ }
10315
10486
  }
10316
- async mintHookTicket(subscriptions, ttlSeconds, signal) {
10317
- const host = this._config.host ?? this.context.hosts[0];
10318
- const headers = this.createInvokeHeaders(subscriptions);
10319
- const ticketResponse = await this.context.fetch(`${host}/hooks/tickets`, {
10320
- method: "POST",
10321
- headers: {
10322
- ...serviceHeadersToRecord(headers),
10323
- "content-type": "application/json"
10324
- },
10325
- body: JSON.stringify({
10326
- subscriptions,
10327
- ttlSeconds
10328
- }),
10329
- signal
10330
- });
10331
- if (!ticketResponse.ok) {
10332
- throw await responseError(
10333
- "hooks",
10334
- "failed to mint hook ticket",
10335
- ticketResponse
10336
- );
10487
+ report(error) {
10488
+ if (error.meta && typeof error.meta === "object") {
10489
+ Object.freeze(error.meta);
10337
10490
  }
10338
- const ticketJson = await ticketResponse.json();
10339
- if (!ticketJson?.ticket) {
10340
- throw new Error("Hook ticket response did not include a ticket");
10491
+ Object.freeze(error);
10492
+ try {
10493
+ this.emitError(error, "stream");
10494
+ } catch {
10341
10495
  }
10342
- return ticketJson;
10343
10496
  }
10344
- async openHookStream(host, ticket, signal) {
10345
- const streamResponse = await this.context.fetch(
10346
- `${host}/hooks/events?ticket=${encodeURIComponent(ticket)}`,
10347
- {
10348
- method: "GET",
10349
- headers: { accept: "text/event-stream" },
10350
- signal
10351
- }
10497
+ /**
10498
+ * Unreachable by construction. If the loop ever rejects anyway, fail its
10499
+ * subscribers visibly instead of stranding them or crashing the process.
10500
+ */
10501
+ collapse() {
10502
+ this._supervising = false;
10503
+ this._phase?.interrupt.abort();
10504
+ this._phase = void 0;
10505
+ const error = serviceError(
10506
+ ErrorCodes.NETWORK_ERROR,
10507
+ "hook stream supervisor stopped unexpectedly",
10508
+ "hooks"
10352
10509
  );
10353
- if (!streamResponse.ok) {
10354
- throw await responseError(
10355
- "hooks",
10356
- "failed to open hook stream",
10357
- streamResponse
10358
- );
10359
- }
10360
- return streamResponse;
10510
+ this.report(error);
10511
+ this.release(error);
10512
+ }
10513
+ onSessionChange(session) {
10514
+ super.onSessionChange(session);
10515
+ this._sessionGeneration += 1;
10516
+ this.wake();
10517
+ }
10518
+ onSignOut() {
10519
+ super.onSignOut();
10520
+ this.release();
10521
+ this.wake();
10522
+ }
10523
+ createHookHeaders(action, path) {
10524
+ return this.context.invoke(this.session, "hooks", path, action);
10361
10525
  }
10362
10526
  createInvokeHeaders(subscriptions) {
10363
10527
  const entries = subscriptions.map((subscription) => ({
@@ -10388,11 +10552,168 @@ function buildScopePath(service, pathPrefix) {
10388
10552
  const normalized = normalizePathPrefix(pathPrefix);
10389
10553
  return normalized ? `${service}/${normalized}` : service;
10390
10554
  }
10555
+ function hookStreamRetryDelay(attempt) {
10556
+ const cap = Math.min(
10557
+ HOOK_STREAM_RETRY_BASE_DELAY_MS * 2 ** Math.max(0, attempt - 1),
10558
+ HOOK_STREAM_RETRY_MAX_DELAY_MS
10559
+ );
10560
+ return cap / 2 + Math.random() * (cap / 2);
10561
+ }
10562
+ function isTerminalMintRefusal(status) {
10563
+ return typeof status === "number" && status >= 400 && status < 500 && status !== 408 && status !== 429;
10564
+ }
10565
+ var SUPERSEDED = /* @__PURE__ */ Symbol("hooks attempt superseded");
10566
+ async function awaitOwned(signal, operation) {
10567
+ if (signal.aborted) {
10568
+ if (typeof operation !== "function") {
10569
+ void operation.then(cancelLateResponse, () => void 0);
10570
+ }
10571
+ throw SUPERSEDED;
10572
+ }
10573
+ const promise = typeof operation === "function" ? operation() : operation;
10574
+ return new Promise((resolve5, reject) => {
10575
+ let settled = false;
10576
+ const cleanup = () => signal.removeEventListener("abort", onAbort);
10577
+ const onAbort = () => {
10578
+ if (settled) return;
10579
+ settled = true;
10580
+ cleanup();
10581
+ reject(SUPERSEDED);
10582
+ };
10583
+ signal.addEventListener("abort", onAbort, { once: true });
10584
+ promise.then(
10585
+ (value) => {
10586
+ if (settled) {
10587
+ cancelLateResponse(value);
10588
+ return;
10589
+ }
10590
+ settled = true;
10591
+ cleanup();
10592
+ resolve5(value);
10593
+ },
10594
+ (error) => {
10595
+ if (settled) return;
10596
+ settled = true;
10597
+ cleanup();
10598
+ reject(error);
10599
+ }
10600
+ );
10601
+ });
10602
+ }
10603
+ function cancelLateResponse(value) {
10604
+ if (!(value instanceof Response) || !value.body) return;
10605
+ try {
10606
+ const cancellation = value.body.cancel();
10607
+ void cancellation.then(() => void 0, () => void 0);
10608
+ } catch {
10609
+ }
10610
+ }
10611
+ function hookStreamRefusal(status) {
10612
+ const code3 = typeof status === "number" ? status : void 0;
10613
+ return serviceError(
10614
+ ErrorCodes.NETWORK_ERROR,
10615
+ `hook stream open refused: ${code3 ?? "unknown status"}`,
10616
+ "hooks",
10617
+ { meta: { phase: "open", status: code3 } }
10618
+ );
10619
+ }
10620
+ var STREAM_ERROR_NAMES = /* @__PURE__ */ new Set([
10621
+ "Error",
10622
+ "TypeError",
10623
+ "AbortError",
10624
+ "TimeoutError",
10625
+ "SyntaxError",
10626
+ "RangeError",
10627
+ "NetworkError"
10628
+ ]);
10629
+ function hookStreamFailure(phase, thrown) {
10630
+ const candidateName = readProperty(thrown, "name");
10631
+ const name = typeof candidateName === "string" && STREAM_ERROR_NAMES.has(candidateName) ? candidateName : "Error";
10632
+ const code3 = identifier(readProperty(thrown, "code")) ?? identifier(readProperty(readProperty(thrown, "cause"), "code"));
10633
+ return serviceError(
10634
+ ErrorCodes.NETWORK_ERROR,
10635
+ `hook stream ${phase} failed: ${name}${code3 ? ` (${code3})` : ""}`,
10636
+ "hooks",
10637
+ { meta: { phase, errorName: name, ...code3 ? { errorCode: code3 } : {} } }
10638
+ );
10639
+ }
10640
+ var STREAM_ERROR_CODES = /* @__PURE__ */ new Set([
10641
+ "ECONNREFUSED",
10642
+ "ECONNRESET",
10643
+ "ECONNABORTED",
10644
+ "ETIMEDOUT",
10645
+ "EPIPE",
10646
+ "ENOTFOUND",
10647
+ "EAI_AGAIN",
10648
+ "EHOSTUNREACH",
10649
+ "ENETUNREACH",
10650
+ "ENETDOWN",
10651
+ "EADDRNOTAVAIL",
10652
+ "UND_ERR_SOCKET",
10653
+ "UND_ERR_CONNECT_TIMEOUT",
10654
+ "UND_ERR_HEADERS_TIMEOUT",
10655
+ "UND_ERR_BODY_TIMEOUT",
10656
+ "UND_ERR_CLOSED",
10657
+ "UND_ERR_ABORTED",
10658
+ "UND_ERR_RESPONSE_STATUS_CODE",
10659
+ "ERR_TLS_CERT_ALTNAME_INVALID",
10660
+ "CERT_HAS_EXPIRED",
10661
+ "DEPTH_ZERO_SELF_SIGNED_CERT",
10662
+ "UNABLE_TO_VERIFY_LEAF_SIGNATURE",
10663
+ "SELF_SIGNED_CERT_IN_CHAIN",
10664
+ "ERR_SSL_WRONG_VERSION_NUMBER"
10665
+ ]);
10666
+ function identifier(value) {
10667
+ return typeof value === "string" && STREAM_ERROR_CODES.has(value) ? value : void 0;
10668
+ }
10669
+ function readProperty(value, key) {
10670
+ if (value === null || typeof value !== "object" && typeof value !== "function") {
10671
+ return void 0;
10672
+ }
10673
+ try {
10674
+ return Reflect.get(value, key);
10675
+ } catch {
10676
+ return void 0;
10677
+ }
10678
+ }
10679
+ function sleep(delayMs, _attempt, signal) {
10680
+ return new Promise((resolve5) => {
10681
+ const finish = () => {
10682
+ clearTimeout(timer);
10683
+ signal.removeEventListener("abort", stop);
10684
+ resolve5();
10685
+ };
10686
+ const timer = setTimeout(finish, delayMs);
10687
+ timer.unref?.();
10688
+ const stop = () => finish();
10689
+ if (signal.aborted) {
10690
+ finish();
10691
+ } else {
10692
+ signal.addEventListener("abort", stop, { once: true });
10693
+ }
10694
+ });
10695
+ }
10696
+ function abortPromise(signal) {
10697
+ let resolve5;
10698
+ const promise = new Promise((done) => {
10699
+ resolve5 = done;
10700
+ });
10701
+ const onAbort = () => resolve5();
10702
+ if (signal.aborted) {
10703
+ resolve5();
10704
+ } else {
10705
+ signal.addEventListener("abort", onAbort, { once: true });
10706
+ }
10707
+ return {
10708
+ promise,
10709
+ dispose: () => signal.removeEventListener("abort", onAbort)
10710
+ };
10711
+ }
10391
10712
  function normalizeSubscription(subscription) {
10392
10713
  return {
10393
10714
  ...subscription,
10394
10715
  pathPrefix: normalizePathPrefix(subscription.pathPrefix),
10395
- abilities: subscription.abilities ?? []
10716
+ abilities: subscription.abilities ? [...subscription.abilities] : []
10396
10717
  };
10397
10718
  }
10398
10719
  function subscriptionSignature(subscription) {
@@ -10458,9 +10779,6 @@ async function responseError(service, message, response) {
10458
10779
  meta: { ...error.meta, status: response.status, statusText: response.statusText }
10459
10780
  };
10460
10781
  }
10461
- function isAbortError(error) {
10462
- return error instanceof DOMException && error.name === "AbortError";
10463
- }
10464
10782
  async function* parseSseStream(body, signal) {
10465
10783
  if (!body) {
10466
10784
  throw new Error("Hook stream response does not expose a readable body");
@@ -10489,11 +10807,24 @@ async function* parseSseStream(body, signal) {
10489
10807
  async function* readBodyChunks(body, signal) {
10490
10808
  const asyncIterable = body;
10491
10809
  if (typeof asyncIterable?.[Symbol.asyncIterator] === "function") {
10492
- for await (const chunk of asyncIterable) {
10493
- if (signal?.aborted) {
10494
- break;
10810
+ const iterator = asyncIterable[Symbol.asyncIterator]();
10811
+ const cancel = () => {
10812
+ try {
10813
+ const closing = iterator.return?.();
10814
+ if (closing) void Promise.resolve(closing).then(() => void 0, () => void 0);
10815
+ } catch {
10495
10816
  }
10496
- yield chunk;
10817
+ };
10818
+ signal?.addEventListener("abort", cancel, { once: true });
10819
+ try {
10820
+ while (!signal?.aborted) {
10821
+ const next = await iterator.next();
10822
+ if (next.done) break;
10823
+ yield next.value;
10824
+ }
10825
+ } finally {
10826
+ signal?.removeEventListener("abort", cancel);
10827
+ cancel();
10497
10828
  }
10498
10829
  return;
10499
10830
  }
@@ -10502,21 +10833,23 @@ async function* readBodyChunks(body, signal) {
10502
10833
  throw new Error("Unsupported hook stream body type");
10503
10834
  }
10504
10835
  const reader = stream.getReader();
10836
+ const cancelReader = () => {
10837
+ try {
10838
+ const cancellation = reader.cancel?.();
10839
+ if (cancellation) void cancellation.then(() => void 0, () => void 0);
10840
+ } catch {
10841
+ }
10842
+ };
10843
+ signal?.addEventListener("abort", cancelReader, { once: true });
10505
10844
  try {
10506
10845
  while (!signal?.aborted) {
10507
10846
  const { done, value } = await reader.read();
10508
- if (done) {
10509
- break;
10510
- }
10511
- if (value) {
10512
- yield value;
10513
- }
10847
+ if (done) break;
10848
+ if (value) yield value;
10514
10849
  }
10515
10850
  } finally {
10516
- try {
10517
- await reader.cancel?.();
10518
- } catch {
10519
- }
10851
+ signal?.removeEventListener("abort", cancelReader);
10852
+ cancelReader();
10520
10853
  reader.releaseLock?.();
10521
10854
  }
10522
10855
  }
@@ -12897,10 +13230,10 @@ var EncryptionService = class extends BaseService {
12897
13230
  get crypto() {
12898
13231
  return this._config.crypto;
12899
13232
  }
12900
- async discoverNetwork(identifier, ownerDid) {
13233
+ async discoverNetwork(identifier2, ownerDid) {
12901
13234
  this.assertActive();
12902
13235
  const result = await discoverNetwork({
12903
- identifier,
13236
+ identifier: identifier2,
12904
13237
  ...ownerDid !== void 0 ? { ownerDid } : {},
12905
13238
  ...this._config.node !== void 0 ? { node: this._config.node } : {},
12906
13239
  ...this._config.wellKnown !== void 0 ? { wellKnown: this._config.wellKnown } : {}
@@ -30222,17 +30555,17 @@ function validRecipientDid(value) {
30222
30555
  if (value.length === 0 || value.length > 2048 || /[\u0000-\u0020\u007f]/.test(value)) return false;
30223
30556
  const parts = value.split(":");
30224
30557
  if (parts.length < 3 || parts[0] !== "did" || !/^[a-z0-9]+$/.test(parts[1] ?? "")) return false;
30225
- const identifier = parts.slice(2);
30226
- if (identifier.some((part) => part.length === 0)) return false;
30558
+ const identifier2 = parts.slice(2);
30559
+ if (identifier2.some((part) => part.length === 0)) return false;
30227
30560
  if (parts[1] === "web") {
30228
- const host = identifier[0] ?? "";
30561
+ const host = identifier2[0] ?? "";
30229
30562
  if (host.length > 253 || host.split(".").some((label) => !label || label.length > 63 || !/^[A-Za-z0-9-]+$/.test(label) || label.startsWith("-") || label.endsWith("-"))) return false;
30230
- return identifier.slice(1).every((part) => /^[A-Za-z0-9._%-]+$/.test(part));
30563
+ return identifier2.slice(1).every((part) => /^[A-Za-z0-9._%-]+$/.test(part));
30231
30564
  }
30232
- if (parts[1] === "pkh") return identifier.length >= 3 && identifier.every((part) => /^[A-Za-z0-9._%-]+$/.test(part));
30565
+ if (parts[1] === "pkh") return identifier2.length >= 3 && identifier2.every((part) => /^[A-Za-z0-9._%-]+$/.test(part));
30233
30566
  if (parts[1] === "key") {
30234
30567
  try {
30235
- const bytes3 = base58btc2.decode(identifier.join(":"));
30568
+ const bytes3 = base58btc2.decode(identifier2.join(":"));
30236
30569
  return bytes3.length === 34 && bytes3[0] === 237 && bytes3[1] === 1;
30237
30570
  } catch {
30238
30571
  return false;