memeloop 0.2.8 → 0.2.9

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.
Files changed (36) hide show
  1. package/dist/{chunk-NVJCRIEK.js → chunk-34B7MUI6.js} +2 -2
  2. package/dist/{chunk-DACM5AMS.js → chunk-7W4FMKMO.js} +190 -56
  3. package/dist/chunk-7W4FMKMO.js.map +1 -0
  4. package/dist/{chunk-OD6U6VAR.js → chunk-GH5RDPLJ.js} +2 -2
  5. package/dist/{chunk-OD6U6VAR.js.map → chunk-GH5RDPLJ.js.map} +1 -1
  6. package/dist/device-network/cloudDeviceFetchClient.d.ts +9 -7
  7. package/dist/device-network/cloudDeviceFetchClient.d.ts.map +1 -1
  8. package/dist/device-network/deviceCloudConnectionCoordinator.d.ts +5 -5
  9. package/dist/device-network/deviceCloudConnectionCoordinator.d.ts.map +1 -1
  10. package/dist/device-network/deviceOrchestrationTransport.d.ts +7 -0
  11. package/dist/device-network/deviceOrchestrationTransport.d.ts.map +1 -1
  12. package/dist/device-network/standardDeviceCloudConnectionAdapter.d.ts +16 -8
  13. package/dist/device-network/standardDeviceCloudConnectionAdapter.d.ts.map +1 -1
  14. package/dist/device-network/types.d.ts +4 -2
  15. package/dist/device-network/types.d.ts.map +1 -1
  16. package/dist/device-network-portable.cjs +190 -55
  17. package/dist/device-network-portable.cjs.map +1 -1
  18. package/dist/device-network-portable.js +189 -55
  19. package/dist/device-network-portable.js.map +1 -1
  20. package/dist/device-network.cjs +190 -55
  21. package/dist/device-network.cjs.map +1 -1
  22. package/dist/device-network.js +4 -2
  23. package/dist/index.cjs +488 -180
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.js +302 -127
  26. package/dist/index.js.map +1 -1
  27. package/dist/loop-api.cjs +1 -1
  28. package/dist/loop-api.cjs.map +1 -1
  29. package/dist/loop-api.js +1 -1
  30. package/dist/mobile.cjs.map +1 -1
  31. package/dist/orchestration/externalOrchestrationController.d.ts.map +1 -1
  32. package/dist/orchestration/workloadExecutionController.d.ts.map +1 -1
  33. package/dist/plugin/loader.d.ts +1 -1
  34. package/package.json +2 -1
  35. package/dist/chunk-DACM5AMS.js.map +0 -1
  36. /package/dist/{chunk-NVJCRIEK.js.map → chunk-34B7MUI6.js.map} +0 -0
@@ -42,6 +42,7 @@ __export(device_network_entry_exports, {
42
42
  DEVICE_HEARTBEAT_DEFAULT_MAX_CLOCK_SKEW_MS: () => DEVICE_HEARTBEAT_DEFAULT_MAX_CLOCK_SKEW_MS,
43
43
  DEVICE_HEARTBEAT_LIMITS: () => DEVICE_HEARTBEAT_LIMITS,
44
44
  DEVICE_HEARTBEAT_SIGNATURE_DOMAIN: () => DEVICE_HEARTBEAT_SIGNATURE_DOMAIN,
45
+ DEVICE_ORCHESTRATION_FRAME_LIMITS: () => DEVICE_ORCHESTRATION_FRAME_LIMITS,
45
46
  DEVICE_ORCHESTRATION_PROTOCOL: () => DEVICE_ORCHESTRATION_PROTOCOL,
46
47
  DEVICE_PAIRING_INVITE_MAX_CLOCK_SKEW_MS: () => DEVICE_PAIRING_INVITE_MAX_CLOCK_SKEW_MS,
47
48
  DEVICE_PAIRING_INVITE_PROTOCOL: () => DEVICE_PAIRING_INVITE_PROTOCOL,
@@ -4640,14 +4641,28 @@ var MemoryDeviceCloudTokenStorage = class {
4640
4641
  loadConnectionGrant(input) {
4641
4642
  return this.grants.get(connectionGrantCacheKey(input));
4642
4643
  }
4643
- saveConnectionGrant(input, grant) {
4644
- this.grants.set(connectionGrantCacheKey(input), grant);
4644
+ saveConnectionGrant(input, grant, fence) {
4645
+ const write = () => {
4646
+ this.grants.set(connectionGrantCacheKey(input), grant);
4647
+ };
4648
+ if (!fence) {
4649
+ write();
4650
+ return;
4651
+ }
4652
+ if (!fence.commitSynchronous(write)) fence.throwIfStale();
4645
4653
  }
4646
4654
  loadRelayReservation(peerId) {
4647
4655
  return this.relayReservations.get(peerId);
4648
4656
  }
4649
- saveRelayReservation(peerId, token) {
4650
- this.relayReservations.set(peerId, token);
4657
+ saveRelayReservation(peerId, token, fence) {
4658
+ const write = () => {
4659
+ this.relayReservations.set(peerId, token);
4660
+ };
4661
+ if (!fence) {
4662
+ write();
4663
+ return;
4664
+ }
4665
+ if (!fence.commitSynchronous(write)) fence.throwIfStale();
4651
4666
  }
4652
4667
  clear() {
4653
4668
  this.grants.clear();
@@ -4762,13 +4777,16 @@ var CloudDeviceFetchClient = class {
4762
4777
  }
4763
4778
  return { issuer: value.issuer, publicKeyMultibase: value.publicKeyMultibase };
4764
4779
  }
4765
- async createConnectionGrant(input, signal) {
4780
+ async createConnectionGrant(input, signal, fence) {
4781
+ assertFenceSignal(signal, fence);
4782
+ fence?.throwIfStale();
4766
4783
  if (!isCanonicalConnectionGrantRequest(input)) {
4767
4784
  throw new TypeError("invalid_connection_grant_scope");
4768
4785
  }
4769
4786
  const cacheInput = normalizedConnectionGrantRequest(input);
4770
4787
  const cached = await this.loadCachedGrant(cacheInput);
4771
4788
  throwIfAborted(signal);
4789
+ fence?.throwIfStale();
4772
4790
  if (isUsableConnectionGrant(cached, cacheInput, this.now() + this.tokenSafetyMarginMs)) {
4773
4791
  return cached;
4774
4792
  }
@@ -4777,12 +4795,17 @@ var CloudDeviceFetchClient = class {
4777
4795
  body: JSON.stringify(input)
4778
4796
  }, signal);
4779
4797
  if (!isUsableConnectionGrant(value, cacheInput, this.now())) throw invalidShape();
4780
- await this.saveCachedGrant(cacheInput, value);
4798
+ fence?.throwIfStale();
4799
+ await this.saveCachedGrant(cacheInput, value, fence);
4800
+ fence?.throwIfStale();
4781
4801
  return value;
4782
4802
  }
4783
- async createRelayReservation(input, signal) {
4803
+ async createRelayReservation(input, signal, fence) {
4804
+ assertFenceSignal(signal, fence);
4805
+ fence?.throwIfStale();
4784
4806
  const cached = await this.loadCachedRelayReservation(input.peerId);
4785
4807
  throwIfAborted(signal);
4808
+ fence?.throwIfStale();
4786
4809
  if (isUsableRelayReservation(cached, input.peerId, this.now() + this.tokenSafetyMarginMs)) {
4787
4810
  return cached;
4788
4811
  }
@@ -4791,7 +4814,9 @@ var CloudDeviceFetchClient = class {
4791
4814
  body: JSON.stringify(input)
4792
4815
  }, signal);
4793
4816
  if (!isUsableRelayReservation(value, input.peerId, this.now())) throw invalidShape();
4794
- await this.saveCachedRelayReservation(input.peerId, value);
4817
+ fence?.throwIfStale();
4818
+ await this.saveCachedRelayReservation(input.peerId, value, fence);
4819
+ fence?.throwIfStale();
4795
4820
  return value;
4796
4821
  }
4797
4822
  async heartbeat(input, signal) {
@@ -4865,10 +4890,11 @@ var CloudDeviceFetchClient = class {
4865
4890
  return void 0;
4866
4891
  }
4867
4892
  }
4868
- async saveCachedGrant(input, grant) {
4893
+ async saveCachedGrant(input, grant, fence) {
4869
4894
  try {
4870
- await this.tokenStorage.saveConnectionGrant(input, grant);
4895
+ await this.tokenStorage.saveConnectionGrant(input, grant, fence);
4871
4896
  } catch (error) {
4897
+ if (fence && !fence.isCurrent()) fence.throwIfStale();
4872
4898
  this.options.onTokenStorageError?.("save", error);
4873
4899
  }
4874
4900
  }
@@ -4880,14 +4906,20 @@ var CloudDeviceFetchClient = class {
4880
4906
  return void 0;
4881
4907
  }
4882
4908
  }
4883
- async saveCachedRelayReservation(peerId, token) {
4909
+ async saveCachedRelayReservation(peerId, token, fence) {
4884
4910
  try {
4885
- await this.tokenStorage.saveRelayReservation(peerId, token);
4911
+ await this.tokenStorage.saveRelayReservation(peerId, token, fence);
4886
4912
  } catch (error) {
4913
+ if (fence && !fence.isCurrent()) fence.throwIfStale();
4887
4914
  this.options.onTokenStorageError?.("save", error);
4888
4915
  }
4889
4916
  }
4890
4917
  };
4918
+ function assertFenceSignal(signal, fence) {
4919
+ if (fence && signal && fence.signal !== signal) {
4920
+ throw new TypeError("device_cloud_generation_signal_mismatch");
4921
+ }
4922
+ }
4891
4923
  function normalizeCloudDeviceBaseUrl(value) {
4892
4924
  const baseUrl = value.trim();
4893
4925
  if (!baseUrl || baseUrl.length > CLOUD_URL_MAX_CHARACTERS) throw new Error("invalid_cloud_url");
@@ -5314,16 +5346,16 @@ var DeviceCloudConnectionCoordinator = class {
5314
5346
  try {
5315
5347
  if (this.snapshotValue.components.authorizer !== "ready") {
5316
5348
  activeComponent = "authorizer";
5317
- await this.runStep(activeComponent, generation, signal, () => this.options.adapter.ensureAuthorizer(configuration, signal));
5349
+ await this.runStep(activeComponent, generation, signal, (fence) => this.options.adapter.ensureAuthorizer(configuration, signal, fence));
5318
5350
  }
5319
5351
  if (this.snapshotValue.components.registration !== "ready") {
5320
5352
  activeComponent = "registration";
5321
- await this.runStep(activeComponent, generation, signal, () => this.options.adapter.registerDevice(configuration, signal));
5353
+ await this.runStep(activeComponent, generation, signal, (fence) => this.options.adapter.registerDevice(configuration, signal, fence));
5322
5354
  }
5323
5355
  let relayFailure;
5324
5356
  try {
5325
5357
  activeComponent = "relay";
5326
- await this.runStep(activeComponent, generation, signal, () => this.options.adapter.ensureRelay(configuration, signal));
5358
+ await this.runStep(activeComponent, generation, signal, (fence) => this.options.adapter.ensureRelay(configuration, signal, fence));
5327
5359
  } catch (error) {
5328
5360
  const classification = this.classify(error);
5329
5361
  if (classification === "registration-invalid") throw error;
@@ -5331,11 +5363,11 @@ var DeviceCloudConnectionCoordinator = class {
5331
5363
  this.warn("Device Cloud relay maintenance failed", relayFailure);
5332
5364
  }
5333
5365
  activeComponent = "heartbeat";
5334
- await this.runStep(activeComponent, generation, signal, () => this.options.adapter.heartbeat(configuration, signal));
5366
+ await this.runStep(activeComponent, generation, signal, (fence) => this.options.adapter.heartbeat(configuration, signal, fence));
5335
5367
  let directoryFailure;
5336
5368
  try {
5337
5369
  activeComponent = "directory";
5338
- await this.runStep(activeComponent, generation, signal, () => this.options.adapter.syncDirectory(configuration, signal));
5370
+ await this.runStep(activeComponent, generation, signal, (fence) => this.options.adapter.syncDirectory(configuration, signal, fence));
5339
5371
  } catch (error) {
5340
5372
  const classification = this.classify(error);
5341
5373
  if (classification === "registration-invalid") throw error;
@@ -5375,9 +5407,9 @@ var DeviceCloudConnectionCoordinator = class {
5375
5407
  if (!this.isCurrent(generation, signal)) return;
5376
5408
  await this.setComponent(component, "pending", generation);
5377
5409
  try {
5378
- const result = await operation();
5379
- if (!this.isCurrent(generation, signal)) return;
5380
5410
  const fence = this.createCommitFence(generation, signal);
5411
+ const result = await operation(fence);
5412
+ if (!this.isCurrent(generation, signal)) return;
5381
5413
  await result?.commit?.(fence);
5382
5414
  if (!this.isCurrent(generation, signal)) return;
5383
5415
  await this.setComponent(component, "ready", generation);
@@ -6105,11 +6137,18 @@ function createJsonFrameReader(source, options) {
6105
6137
 
6106
6138
  // src/device-network/deviceOrchestrationTransport.ts
6107
6139
  var DEVICE_ORCHESTRATION_PROTOCOL = "/memeloop/orchestration/2.0.0";
6108
- var DEFAULT_MAX_FRAME_BYTES = 1024 * 1024;
6109
- var REQUEST_IDLE_TIMEOUT_MS = 1e4;
6110
- var REQUEST_TOTAL_TIMEOUT_MS = 3e4;
6111
- var WATCH_IDLE_TIMEOUT_MS = 9e4;
6112
- var WATCH_BOOKMARK_INTERVAL_MS = 3e4;
6140
+ var DEVICE_ORCHESTRATION_FRAME_LIMITS = Object.freeze({
6141
+ maxPayloadBytes: 1024 * 1024,
6142
+ requestIdleTimeoutMs: 1e4,
6143
+ requestTotalTimeoutMs: 3e4,
6144
+ watchIdleTimeoutMs: 9e4,
6145
+ watchBookmarkIntervalMs: 3e4
6146
+ });
6147
+ var DEFAULT_MAX_FRAME_BYTES = DEVICE_ORCHESTRATION_FRAME_LIMITS.maxPayloadBytes;
6148
+ var REQUEST_IDLE_TIMEOUT_MS = DEVICE_ORCHESTRATION_FRAME_LIMITS.requestIdleTimeoutMs;
6149
+ var REQUEST_TOTAL_TIMEOUT_MS = DEVICE_ORCHESTRATION_FRAME_LIMITS.requestTotalTimeoutMs;
6150
+ var WATCH_IDLE_TIMEOUT_MS = DEVICE_ORCHESTRATION_FRAME_LIMITS.watchIdleTimeoutMs;
6151
+ var WATCH_BOOKMARK_INTERVAL_MS = DEVICE_ORCHESTRATION_FRAME_LIMITS.watchBookmarkIntervalMs;
6113
6152
  function orchestrationTransportError(code, message) {
6114
6153
  return new OrchestrationError({
6115
6154
  code,
@@ -6390,6 +6429,13 @@ function createDeviceOrchestrationStreamHandler(options) {
6390
6429
  assertFrameLimit(maxFrameBytes);
6391
6430
  return async ({ remotePeerId, stream, authorize }) => {
6392
6431
  const abort = new AbortController();
6432
+ const abortFromStream = () => {
6433
+ if (!abort.signal.aborted) {
6434
+ abort.abort(stream.signal?.reason ?? new Error("device orchestration stream closed"));
6435
+ }
6436
+ };
6437
+ stream.signal?.addEventListener("abort", abortFromStream, { once: true });
6438
+ if (stream.signal?.aborted) abortFromStream();
6393
6439
  try {
6394
6440
  const envelope = await readSingleRequest(stream, maxFrameBytes);
6395
6441
  if (authorize && !await authorize(envelope.grant)) {
@@ -6413,7 +6459,8 @@ function createDeviceOrchestrationStreamHandler(options) {
6413
6459
  await stream.sink(encodeJsonFrames([response], maxFrameBytes));
6414
6460
  }
6415
6461
  } finally {
6416
- abort.abort();
6462
+ stream.signal?.removeEventListener("abort", abortFromStream);
6463
+ if (!abort.signal.aborted) abort.abort();
6417
6464
  await stream.close().catch(() => void 0);
6418
6465
  }
6419
6466
  };
@@ -6948,6 +6995,23 @@ function sameTrustedDevice(left, right) {
6948
6995
 
6949
6996
  // src/device-network/standardDeviceCloudConnectionAdapter.ts
6950
6997
  var DEFAULT_RELAY_TOKEN_SAFETY_MARGIN_MS = 2 * 6e4;
6998
+ var REGISTRATION_INVALID_ERROR_CODES = /* @__PURE__ */ new Set([
6999
+ "device_not_found",
7000
+ "device_registration_invalid",
7001
+ "device_registration_not_found",
7002
+ "invalid_device_registration",
7003
+ "registration_invalid"
7004
+ ]);
7005
+ var DeviceCloudRegistrationInvalidError = class extends Error {
7006
+ code = "DEVICE_CLOUD_REGISTRATION_INVALID";
7007
+ constructor(cause) {
7008
+ super(
7009
+ "cloud device heartbeat rejected",
7010
+ cause === void 0 ? void 0 : { cause }
7011
+ );
7012
+ this.name = "DeviceCloudRegistrationInvalidError";
7013
+ }
7014
+ };
6951
7015
  var StandardDeviceCloudConnectionAdapter = class {
6952
7016
  constructor(options) {
6953
7017
  this.options = options;
@@ -6967,9 +7031,9 @@ var StandardDeviceCloudConnectionAdapter = class {
6967
7031
  }
6968
7032
  relayRequiredForOnline(_configuration) {
6969
7033
  const addresses = this.options.network.getMultiaddrs();
6970
- return this.options.relayRequiredForOnline?.(addresses) ?? !hasValidDirectCloudDeviceAddress(addresses);
7034
+ return this.options.relayRequiredForOnline?.(addresses) ?? !hasValidDirectCloudDeviceAddress(addresses, this.options.identity.peerId);
6971
7035
  }
6972
- async ensureAuthorizer(client, signal) {
7036
+ async ensureAuthorizer(client, signal, _fence) {
6973
7037
  const publicKey = await client.getConnectionGrantPublicKey(signal);
6974
7038
  return {
6975
7039
  commit: async (fence) => {
@@ -6979,7 +7043,7 @@ var StandardDeviceCloudConnectionAdapter = class {
6979
7043
  }
6980
7044
  };
6981
7045
  }
6982
- async registerDevice(client, signal) {
7046
+ async registerDevice(client, signal, _fence) {
6983
7047
  const nonce = await client.createBindingNonce(signal);
6984
7048
  throwIfAborted3(signal);
6985
7049
  const signature = await this.options.signDeviceBinding({
@@ -7007,27 +7071,28 @@ var StandardDeviceCloudConnectionAdapter = class {
7007
7071
  }
7008
7072
  return void 0;
7009
7073
  }
7010
- async ensureRelay(client, signal) {
7074
+ async ensureRelay(client, signal, fence) {
7011
7075
  if (this.relayReservationClient === client && this.relayReservation && this.relayReservation.expiresAt > this.now() + this.relayTokenSafetyMarginMs) {
7012
7076
  return void 0;
7013
7077
  }
7014
7078
  const relayReservation = await client.createRelayReservation(
7015
7079
  { peerId: this.options.identity.peerId },
7016
- signal
7080
+ signal,
7081
+ fence
7017
7082
  );
7018
7083
  return {
7019
- commit: async (fence) => {
7020
- fence.throwIfStale();
7021
- await this.options.network.configureRelayReservation(relayReservation, signal, fence);
7022
- fence.throwIfStale();
7023
- fence.commitSynchronous(() => {
7084
+ commit: async (fence2) => {
7085
+ fence2.throwIfStale();
7086
+ await this.options.network.configureRelayReservation(relayReservation, signal, fence2);
7087
+ fence2.throwIfStale();
7088
+ fence2.commitSynchronous(() => {
7024
7089
  this.relayReservation = relayReservation;
7025
7090
  this.relayReservationClient = client;
7026
7091
  });
7027
7092
  }
7028
7093
  };
7029
7094
  }
7030
- async heartbeat(client, signal) {
7095
+ async heartbeat(client, signal, _fence) {
7031
7096
  throwIfAborted3(signal);
7032
7097
  const capabilities = await this.options.capabilities();
7033
7098
  throwIfAborted3(signal);
@@ -7045,8 +7110,16 @@ var StandardDeviceCloudConnectionAdapter = class {
7045
7110
  if (!boundedProofSignature(heartbeat.signature)) {
7046
7111
  throw new Error("invalid device heartbeat signature");
7047
7112
  }
7048
- const result = await client.heartbeat(heartbeat, signal);
7049
- if (!result.ok) throw new Error("cloud device heartbeat rejected");
7113
+ let result;
7114
+ try {
7115
+ result = await client.heartbeat(heartbeat, signal);
7116
+ } catch (error) {
7117
+ if (isHeartbeatRegistrationInvalid(error)) {
7118
+ throw new DeviceCloudRegistrationInvalidError(error);
7119
+ }
7120
+ throw error;
7121
+ }
7122
+ if (!result.ok) throw new DeviceCloudRegistrationInvalidError();
7050
7123
  return void 0;
7051
7124
  }
7052
7125
  async dispose(client, signal) {
@@ -7082,7 +7155,7 @@ var StandardDeviceCloudConnectionAdapter = class {
7082
7155
  throw new AggregateError(errors, "device cloud generation cleanup failed");
7083
7156
  }
7084
7157
  }
7085
- async syncDirectory(client, signal) {
7158
+ async syncDirectory(client, signal, _fence) {
7086
7159
  const cloudDevices = await client.listDevices(signal);
7087
7160
  return {
7088
7161
  commit: async (fence) => {
@@ -7110,6 +7183,7 @@ var StandardDeviceCloudConnectionAdapter = class {
7110
7183
  return this.options.syncDevice(client, peerId, signal);
7111
7184
  }
7112
7185
  classifyError(error) {
7186
+ if (error instanceof DeviceCloudRegistrationInvalidError) return "registration-invalid";
7113
7187
  if (error instanceof CloudDeviceFetchError) {
7114
7188
  return error.code === "cloud_request_failed" ? "offline" : "error";
7115
7189
  }
@@ -7120,6 +7194,35 @@ var StandardDeviceCloudConnectionAdapter = class {
7120
7194
  return active.length > 0 ? [...active] : [...this.relayReservation?.relayMultiaddrs ?? []];
7121
7195
  }
7122
7196
  };
7197
+ function isHeartbeatRegistrationInvalid(error) {
7198
+ if (!(error instanceof CloudDeviceFetchError) || error.code !== "cloud_http_error") {
7199
+ return false;
7200
+ }
7201
+ if (error.status === 404) return true;
7202
+ return error.status === 401 && hasExplicitRegistrationInvalidCode(error.responseBody);
7203
+ }
7204
+ function hasExplicitRegistrationInvalidCode(responseBody) {
7205
+ const body = responseBody?.trim();
7206
+ if (!body) return false;
7207
+ let value;
7208
+ try {
7209
+ value = JSON.parse(body);
7210
+ } catch {
7211
+ return REGISTRATION_INVALID_ERROR_CODES.has(normalizeErrorCode(body));
7212
+ }
7213
+ return registrationInvalidCodeFromValue(value);
7214
+ }
7215
+ function registrationInvalidCodeFromValue(value) {
7216
+ if (typeof value === "string") {
7217
+ return REGISTRATION_INVALID_ERROR_CODES.has(normalizeErrorCode(value));
7218
+ }
7219
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
7220
+ const record = value;
7221
+ return registrationInvalidCodeFromValue(record.code) || registrationInvalidCodeFromValue(record.errorCode) || registrationInvalidCodeFromValue(record.error);
7222
+ }
7223
+ function normalizeErrorCode(value) {
7224
+ return value.trim().toLowerCase().replaceAll("-", "_");
7225
+ }
7123
7226
  function toPublicDeviceIdentity(identity) {
7124
7227
  return {
7125
7228
  peerId: identity.peerId,
@@ -7129,21 +7232,42 @@ function toPublicDeviceIdentity(identity) {
7129
7232
  platform: identity.platform
7130
7233
  };
7131
7234
  }
7132
- function hasValidDirectCloudDeviceAddress(addresses) {
7133
- return addresses.some((address) => {
7134
- if (address.includes("/p2p-circuit")) return false;
7135
- const parts = address.split("/");
7136
- const protocolIndex = parts.findIndex(
7137
- (part) => part === "ip4" || part === "ip6" || part === "dns" || part === "dns4" || part === "dns6"
7138
- );
7139
- if (protocolIndex < 0) return false;
7140
- const host = parts[protocolIndex + 1]?.toLowerCase();
7141
- if (!host) return false;
7142
- const protocol = parts[protocolIndex];
7143
- if (protocol === "ip4") return isPublicIpv4(host);
7144
- if (protocol === "ip6") return isPublicIpv6(host);
7145
- return isPlausiblePublicDnsHost(host);
7146
- });
7235
+ function hasValidDirectCloudDeviceAddress(addresses, expectedPeerId) {
7236
+ return addresses.some((address) => isDialableDirectMultiaddr(address, expectedPeerId));
7237
+ }
7238
+ function isDialableDirectMultiaddr(address, expectedPeerId) {
7239
+ if (address.length === 0 || address.length > 4096 || address !== address.trim() || !address.startsWith("/") || address.endsWith("/") || address.includes("//") || address.includes("/p2p-circuit")) return false;
7240
+ const parts = address.split("/").slice(1);
7241
+ if (parts.some((part) => part.length === 0 || containsAsciiControlOrSpace3(part))) return false;
7242
+ const hostProtocol = parts[0];
7243
+ const host = parts[1]?.toLowerCase();
7244
+ if (host === void 0 || hostProtocol !== "ip4" && hostProtocol !== "ip6" && hostProtocol !== "dns" && hostProtocol !== "dns4" && hostProtocol !== "dns6") return false;
7245
+ const publicHost = hostProtocol === "ip4" ? isPublicIpv4(host) : hostProtocol === "ip6" ? isPublicIpv6(host) : isPlausiblePublicDnsHost(host);
7246
+ if (!publicHost) return false;
7247
+ const transport = parts[2];
7248
+ const port = parsePort(parts[3]);
7249
+ if (transport !== "tcp" || port === void 0) return false;
7250
+ let index = 4;
7251
+ if (parts[index] === "ws" || parts[index] === "wss") index += 1;
7252
+ if (parts[index] === "p2p") {
7253
+ const peerId = parts[index + 1];
7254
+ if (!peerId || containsAsciiControlOrSpace3(peerId)) return false;
7255
+ if (expectedPeerId !== void 0 && peerId !== expectedPeerId) return false;
7256
+ index += 2;
7257
+ }
7258
+ return index === parts.length;
7259
+ }
7260
+ function parsePort(value) {
7261
+ if (value === void 0 || !/^\d{1,5}$/u.test(value)) return void 0;
7262
+ const port = Number(value);
7263
+ return port >= 1 && port <= 65535 ? port : void 0;
7264
+ }
7265
+ function containsAsciiControlOrSpace3(value) {
7266
+ for (let index = 0; index < value.length; index += 1) {
7267
+ const code = value.charCodeAt(index);
7268
+ if (code <= 32 || code === 127) return true;
7269
+ }
7270
+ return false;
7147
7271
  }
7148
7272
  function isPublicIpv4(host) {
7149
7273
  const octets = host.split(".").map((part) => Number(part));
@@ -7183,7 +7307,17 @@ function isPlausiblePublicDnsHost(host) {
7183
7307
  const normalized = host.endsWith(".") ? host.slice(0, -1) : host;
7184
7308
  if (isIpv4Syntax(normalized)) return isPublicIpv4(normalized);
7185
7309
  if (normalized.includes(":")) return isPublicIpv6(normalized);
7186
- if (normalized === "localhost" || normalized.endsWith(".localhost") || normalized.endsWith(".local") || normalized.endsWith(".internal")) return false;
7310
+ const specialUseSuffixes = [
7311
+ "example",
7312
+ "home.arpa",
7313
+ "internal",
7314
+ "invalid",
7315
+ "local",
7316
+ "localhost",
7317
+ "onion",
7318
+ "test"
7319
+ ];
7320
+ if (specialUseSuffixes.some((suffix) => normalized === suffix || normalized.endsWith(`.${suffix}`))) return false;
7187
7321
  const labels = normalized.split(".");
7188
7322
  return labels.length > 1 && labels.every(
7189
7323
  (label) => label.length > 0 && label.length <= 63 && /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/u.test(label)
@@ -8345,6 +8479,7 @@ function sameTrustedDevice2(left, right) {
8345
8479
  DEVICE_HEARTBEAT_DEFAULT_MAX_CLOCK_SKEW_MS,
8346
8480
  DEVICE_HEARTBEAT_LIMITS,
8347
8481
  DEVICE_HEARTBEAT_SIGNATURE_DOMAIN,
8482
+ DEVICE_ORCHESTRATION_FRAME_LIMITS,
8348
8483
  DEVICE_ORCHESTRATION_PROTOCOL,
8349
8484
  DEVICE_PAIRING_INVITE_MAX_CLOCK_SKEW_MS,
8350
8485
  DEVICE_PAIRING_INVITE_PROTOCOL,