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
@@ -4392,14 +4392,28 @@ var MemoryDeviceCloudTokenStorage = class {
4392
4392
  loadConnectionGrant(input) {
4393
4393
  return this.grants.get(connectionGrantCacheKey(input));
4394
4394
  }
4395
- saveConnectionGrant(input, grant) {
4396
- this.grants.set(connectionGrantCacheKey(input), grant);
4395
+ saveConnectionGrant(input, grant, fence) {
4396
+ const write = () => {
4397
+ this.grants.set(connectionGrantCacheKey(input), grant);
4398
+ };
4399
+ if (!fence) {
4400
+ write();
4401
+ return;
4402
+ }
4403
+ if (!fence.commitSynchronous(write)) fence.throwIfStale();
4397
4404
  }
4398
4405
  loadRelayReservation(peerId) {
4399
4406
  return this.relayReservations.get(peerId);
4400
4407
  }
4401
- saveRelayReservation(peerId, token) {
4402
- this.relayReservations.set(peerId, token);
4408
+ saveRelayReservation(peerId, token, fence) {
4409
+ const write = () => {
4410
+ this.relayReservations.set(peerId, token);
4411
+ };
4412
+ if (!fence) {
4413
+ write();
4414
+ return;
4415
+ }
4416
+ if (!fence.commitSynchronous(write)) fence.throwIfStale();
4403
4417
  }
4404
4418
  clear() {
4405
4419
  this.grants.clear();
@@ -4514,13 +4528,16 @@ var CloudDeviceFetchClient = class {
4514
4528
  }
4515
4529
  return { issuer: value.issuer, publicKeyMultibase: value.publicKeyMultibase };
4516
4530
  }
4517
- async createConnectionGrant(input, signal) {
4531
+ async createConnectionGrant(input, signal, fence) {
4532
+ assertFenceSignal(signal, fence);
4533
+ fence?.throwIfStale();
4518
4534
  if (!isCanonicalConnectionGrantRequest(input)) {
4519
4535
  throw new TypeError("invalid_connection_grant_scope");
4520
4536
  }
4521
4537
  const cacheInput = normalizedConnectionGrantRequest(input);
4522
4538
  const cached = await this.loadCachedGrant(cacheInput);
4523
4539
  throwIfAborted(signal);
4540
+ fence?.throwIfStale();
4524
4541
  if (isUsableConnectionGrant(cached, cacheInput, this.now() + this.tokenSafetyMarginMs)) {
4525
4542
  return cached;
4526
4543
  }
@@ -4529,12 +4546,17 @@ var CloudDeviceFetchClient = class {
4529
4546
  body: JSON.stringify(input)
4530
4547
  }, signal);
4531
4548
  if (!isUsableConnectionGrant(value, cacheInput, this.now())) throw invalidShape();
4532
- await this.saveCachedGrant(cacheInput, value);
4549
+ fence?.throwIfStale();
4550
+ await this.saveCachedGrant(cacheInput, value, fence);
4551
+ fence?.throwIfStale();
4533
4552
  return value;
4534
4553
  }
4535
- async createRelayReservation(input, signal) {
4554
+ async createRelayReservation(input, signal, fence) {
4555
+ assertFenceSignal(signal, fence);
4556
+ fence?.throwIfStale();
4536
4557
  const cached = await this.loadCachedRelayReservation(input.peerId);
4537
4558
  throwIfAborted(signal);
4559
+ fence?.throwIfStale();
4538
4560
  if (isUsableRelayReservation(cached, input.peerId, this.now() + this.tokenSafetyMarginMs)) {
4539
4561
  return cached;
4540
4562
  }
@@ -4543,7 +4565,9 @@ var CloudDeviceFetchClient = class {
4543
4565
  body: JSON.stringify(input)
4544
4566
  }, signal);
4545
4567
  if (!isUsableRelayReservation(value, input.peerId, this.now())) throw invalidShape();
4546
- await this.saveCachedRelayReservation(input.peerId, value);
4568
+ fence?.throwIfStale();
4569
+ await this.saveCachedRelayReservation(input.peerId, value, fence);
4570
+ fence?.throwIfStale();
4547
4571
  return value;
4548
4572
  }
4549
4573
  async heartbeat(input, signal) {
@@ -4617,10 +4641,11 @@ var CloudDeviceFetchClient = class {
4617
4641
  return void 0;
4618
4642
  }
4619
4643
  }
4620
- async saveCachedGrant(input, grant) {
4644
+ async saveCachedGrant(input, grant, fence) {
4621
4645
  try {
4622
- await this.tokenStorage.saveConnectionGrant(input, grant);
4646
+ await this.tokenStorage.saveConnectionGrant(input, grant, fence);
4623
4647
  } catch (error) {
4648
+ if (fence && !fence.isCurrent()) fence.throwIfStale();
4624
4649
  this.options.onTokenStorageError?.("save", error);
4625
4650
  }
4626
4651
  }
@@ -4632,14 +4657,20 @@ var CloudDeviceFetchClient = class {
4632
4657
  return void 0;
4633
4658
  }
4634
4659
  }
4635
- async saveCachedRelayReservation(peerId, token) {
4660
+ async saveCachedRelayReservation(peerId, token, fence) {
4636
4661
  try {
4637
- await this.tokenStorage.saveRelayReservation(peerId, token);
4662
+ await this.tokenStorage.saveRelayReservation(peerId, token, fence);
4638
4663
  } catch (error) {
4664
+ if (fence && !fence.isCurrent()) fence.throwIfStale();
4639
4665
  this.options.onTokenStorageError?.("save", error);
4640
4666
  }
4641
4667
  }
4642
4668
  };
4669
+ function assertFenceSignal(signal, fence) {
4670
+ if (fence && signal && fence.signal !== signal) {
4671
+ throw new TypeError("device_cloud_generation_signal_mismatch");
4672
+ }
4673
+ }
4643
4674
  function normalizeCloudDeviceBaseUrl(value) {
4644
4675
  const baseUrl = value.trim();
4645
4676
  if (!baseUrl || baseUrl.length > CLOUD_URL_MAX_CHARACTERS) throw new Error("invalid_cloud_url");
@@ -5066,16 +5097,16 @@ var DeviceCloudConnectionCoordinator = class {
5066
5097
  try {
5067
5098
  if (this.snapshotValue.components.authorizer !== "ready") {
5068
5099
  activeComponent = "authorizer";
5069
- await this.runStep(activeComponent, generation, signal, () => this.options.adapter.ensureAuthorizer(configuration, signal));
5100
+ await this.runStep(activeComponent, generation, signal, (fence) => this.options.adapter.ensureAuthorizer(configuration, signal, fence));
5070
5101
  }
5071
5102
  if (this.snapshotValue.components.registration !== "ready") {
5072
5103
  activeComponent = "registration";
5073
- await this.runStep(activeComponent, generation, signal, () => this.options.adapter.registerDevice(configuration, signal));
5104
+ await this.runStep(activeComponent, generation, signal, (fence) => this.options.adapter.registerDevice(configuration, signal, fence));
5074
5105
  }
5075
5106
  let relayFailure;
5076
5107
  try {
5077
5108
  activeComponent = "relay";
5078
- await this.runStep(activeComponent, generation, signal, () => this.options.adapter.ensureRelay(configuration, signal));
5109
+ await this.runStep(activeComponent, generation, signal, (fence) => this.options.adapter.ensureRelay(configuration, signal, fence));
5079
5110
  } catch (error) {
5080
5111
  const classification = this.classify(error);
5081
5112
  if (classification === "registration-invalid") throw error;
@@ -5083,11 +5114,11 @@ var DeviceCloudConnectionCoordinator = class {
5083
5114
  this.warn("Device Cloud relay maintenance failed", relayFailure);
5084
5115
  }
5085
5116
  activeComponent = "heartbeat";
5086
- await this.runStep(activeComponent, generation, signal, () => this.options.adapter.heartbeat(configuration, signal));
5117
+ await this.runStep(activeComponent, generation, signal, (fence) => this.options.adapter.heartbeat(configuration, signal, fence));
5087
5118
  let directoryFailure;
5088
5119
  try {
5089
5120
  activeComponent = "directory";
5090
- await this.runStep(activeComponent, generation, signal, () => this.options.adapter.syncDirectory(configuration, signal));
5121
+ await this.runStep(activeComponent, generation, signal, (fence) => this.options.adapter.syncDirectory(configuration, signal, fence));
5091
5122
  } catch (error) {
5092
5123
  const classification = this.classify(error);
5093
5124
  if (classification === "registration-invalid") throw error;
@@ -5127,9 +5158,9 @@ var DeviceCloudConnectionCoordinator = class {
5127
5158
  if (!this.isCurrent(generation, signal)) return;
5128
5159
  await this.setComponent(component, "pending", generation);
5129
5160
  try {
5130
- const result = await operation();
5131
- if (!this.isCurrent(generation, signal)) return;
5132
5161
  const fence = this.createCommitFence(generation, signal);
5162
+ const result = await operation(fence);
5163
+ if (!this.isCurrent(generation, signal)) return;
5133
5164
  await result?.commit?.(fence);
5134
5165
  if (!this.isCurrent(generation, signal)) return;
5135
5166
  await this.setComponent(component, "ready", generation);
@@ -5857,11 +5888,18 @@ function createJsonFrameReader(source, options) {
5857
5888
 
5858
5889
  // src/device-network/deviceOrchestrationTransport.ts
5859
5890
  var DEVICE_ORCHESTRATION_PROTOCOL = "/memeloop/orchestration/2.0.0";
5860
- var DEFAULT_MAX_FRAME_BYTES = 1024 * 1024;
5861
- var REQUEST_IDLE_TIMEOUT_MS = 1e4;
5862
- var REQUEST_TOTAL_TIMEOUT_MS = 3e4;
5863
- var WATCH_IDLE_TIMEOUT_MS = 9e4;
5864
- var WATCH_BOOKMARK_INTERVAL_MS = 3e4;
5891
+ var DEVICE_ORCHESTRATION_FRAME_LIMITS = Object.freeze({
5892
+ maxPayloadBytes: 1024 * 1024,
5893
+ requestIdleTimeoutMs: 1e4,
5894
+ requestTotalTimeoutMs: 3e4,
5895
+ watchIdleTimeoutMs: 9e4,
5896
+ watchBookmarkIntervalMs: 3e4
5897
+ });
5898
+ var DEFAULT_MAX_FRAME_BYTES = DEVICE_ORCHESTRATION_FRAME_LIMITS.maxPayloadBytes;
5899
+ var REQUEST_IDLE_TIMEOUT_MS = DEVICE_ORCHESTRATION_FRAME_LIMITS.requestIdleTimeoutMs;
5900
+ var REQUEST_TOTAL_TIMEOUT_MS = DEVICE_ORCHESTRATION_FRAME_LIMITS.requestTotalTimeoutMs;
5901
+ var WATCH_IDLE_TIMEOUT_MS = DEVICE_ORCHESTRATION_FRAME_LIMITS.watchIdleTimeoutMs;
5902
+ var WATCH_BOOKMARK_INTERVAL_MS = DEVICE_ORCHESTRATION_FRAME_LIMITS.watchBookmarkIntervalMs;
5865
5903
  function orchestrationTransportError(code, message) {
5866
5904
  return new OrchestrationError({
5867
5905
  code,
@@ -6142,6 +6180,13 @@ function createDeviceOrchestrationStreamHandler(options) {
6142
6180
  assertFrameLimit(maxFrameBytes);
6143
6181
  return async ({ remotePeerId, stream, authorize }) => {
6144
6182
  const abort = new AbortController();
6183
+ const abortFromStream = () => {
6184
+ if (!abort.signal.aborted) {
6185
+ abort.abort(stream.signal?.reason ?? new Error("device orchestration stream closed"));
6186
+ }
6187
+ };
6188
+ stream.signal?.addEventListener("abort", abortFromStream, { once: true });
6189
+ if (stream.signal?.aborted) abortFromStream();
6145
6190
  try {
6146
6191
  const envelope = await readSingleRequest(stream, maxFrameBytes);
6147
6192
  if (authorize && !await authorize(envelope.grant)) {
@@ -6165,7 +6210,8 @@ function createDeviceOrchestrationStreamHandler(options) {
6165
6210
  await stream.sink(encodeJsonFrames([response], maxFrameBytes));
6166
6211
  }
6167
6212
  } finally {
6168
- abort.abort();
6213
+ stream.signal?.removeEventListener("abort", abortFromStream);
6214
+ if (!abort.signal.aborted) abort.abort();
6169
6215
  await stream.close().catch(() => void 0);
6170
6216
  }
6171
6217
  };
@@ -6700,6 +6746,23 @@ function sameTrustedDevice(left, right) {
6700
6746
 
6701
6747
  // src/device-network/standardDeviceCloudConnectionAdapter.ts
6702
6748
  var DEFAULT_RELAY_TOKEN_SAFETY_MARGIN_MS = 2 * 6e4;
6749
+ var REGISTRATION_INVALID_ERROR_CODES = /* @__PURE__ */ new Set([
6750
+ "device_not_found",
6751
+ "device_registration_invalid",
6752
+ "device_registration_not_found",
6753
+ "invalid_device_registration",
6754
+ "registration_invalid"
6755
+ ]);
6756
+ var DeviceCloudRegistrationInvalidError = class extends Error {
6757
+ code = "DEVICE_CLOUD_REGISTRATION_INVALID";
6758
+ constructor(cause) {
6759
+ super(
6760
+ "cloud device heartbeat rejected",
6761
+ cause === void 0 ? void 0 : { cause }
6762
+ );
6763
+ this.name = "DeviceCloudRegistrationInvalidError";
6764
+ }
6765
+ };
6703
6766
  var StandardDeviceCloudConnectionAdapter = class {
6704
6767
  constructor(options) {
6705
6768
  this.options = options;
@@ -6719,9 +6782,9 @@ var StandardDeviceCloudConnectionAdapter = class {
6719
6782
  }
6720
6783
  relayRequiredForOnline(_configuration) {
6721
6784
  const addresses = this.options.network.getMultiaddrs();
6722
- return this.options.relayRequiredForOnline?.(addresses) ?? !hasValidDirectCloudDeviceAddress(addresses);
6785
+ return this.options.relayRequiredForOnline?.(addresses) ?? !hasValidDirectCloudDeviceAddress(addresses, this.options.identity.peerId);
6723
6786
  }
6724
- async ensureAuthorizer(client, signal) {
6787
+ async ensureAuthorizer(client, signal, _fence) {
6725
6788
  const publicKey = await client.getConnectionGrantPublicKey(signal);
6726
6789
  return {
6727
6790
  commit: async (fence) => {
@@ -6731,7 +6794,7 @@ var StandardDeviceCloudConnectionAdapter = class {
6731
6794
  }
6732
6795
  };
6733
6796
  }
6734
- async registerDevice(client, signal) {
6797
+ async registerDevice(client, signal, _fence) {
6735
6798
  const nonce = await client.createBindingNonce(signal);
6736
6799
  throwIfAborted3(signal);
6737
6800
  const signature = await this.options.signDeviceBinding({
@@ -6759,27 +6822,28 @@ var StandardDeviceCloudConnectionAdapter = class {
6759
6822
  }
6760
6823
  return void 0;
6761
6824
  }
6762
- async ensureRelay(client, signal) {
6825
+ async ensureRelay(client, signal, fence) {
6763
6826
  if (this.relayReservationClient === client && this.relayReservation && this.relayReservation.expiresAt > this.now() + this.relayTokenSafetyMarginMs) {
6764
6827
  return void 0;
6765
6828
  }
6766
6829
  const relayReservation = await client.createRelayReservation(
6767
6830
  { peerId: this.options.identity.peerId },
6768
- signal
6831
+ signal,
6832
+ fence
6769
6833
  );
6770
6834
  return {
6771
- commit: async (fence) => {
6772
- fence.throwIfStale();
6773
- await this.options.network.configureRelayReservation(relayReservation, signal, fence);
6774
- fence.throwIfStale();
6775
- fence.commitSynchronous(() => {
6835
+ commit: async (fence2) => {
6836
+ fence2.throwIfStale();
6837
+ await this.options.network.configureRelayReservation(relayReservation, signal, fence2);
6838
+ fence2.throwIfStale();
6839
+ fence2.commitSynchronous(() => {
6776
6840
  this.relayReservation = relayReservation;
6777
6841
  this.relayReservationClient = client;
6778
6842
  });
6779
6843
  }
6780
6844
  };
6781
6845
  }
6782
- async heartbeat(client, signal) {
6846
+ async heartbeat(client, signal, _fence) {
6783
6847
  throwIfAborted3(signal);
6784
6848
  const capabilities = await this.options.capabilities();
6785
6849
  throwIfAborted3(signal);
@@ -6797,8 +6861,16 @@ var StandardDeviceCloudConnectionAdapter = class {
6797
6861
  if (!boundedProofSignature(heartbeat.signature)) {
6798
6862
  throw new Error("invalid device heartbeat signature");
6799
6863
  }
6800
- const result = await client.heartbeat(heartbeat, signal);
6801
- if (!result.ok) throw new Error("cloud device heartbeat rejected");
6864
+ let result;
6865
+ try {
6866
+ result = await client.heartbeat(heartbeat, signal);
6867
+ } catch (error) {
6868
+ if (isHeartbeatRegistrationInvalid(error)) {
6869
+ throw new DeviceCloudRegistrationInvalidError(error);
6870
+ }
6871
+ throw error;
6872
+ }
6873
+ if (!result.ok) throw new DeviceCloudRegistrationInvalidError();
6802
6874
  return void 0;
6803
6875
  }
6804
6876
  async dispose(client, signal) {
@@ -6834,7 +6906,7 @@ var StandardDeviceCloudConnectionAdapter = class {
6834
6906
  throw new AggregateError(errors, "device cloud generation cleanup failed");
6835
6907
  }
6836
6908
  }
6837
- async syncDirectory(client, signal) {
6909
+ async syncDirectory(client, signal, _fence) {
6838
6910
  const cloudDevices = await client.listDevices(signal);
6839
6911
  return {
6840
6912
  commit: async (fence) => {
@@ -6862,6 +6934,7 @@ var StandardDeviceCloudConnectionAdapter = class {
6862
6934
  return this.options.syncDevice(client, peerId, signal);
6863
6935
  }
6864
6936
  classifyError(error) {
6937
+ if (error instanceof DeviceCloudRegistrationInvalidError) return "registration-invalid";
6865
6938
  if (error instanceof CloudDeviceFetchError) {
6866
6939
  return error.code === "cloud_request_failed" ? "offline" : "error";
6867
6940
  }
@@ -6872,6 +6945,35 @@ var StandardDeviceCloudConnectionAdapter = class {
6872
6945
  return active.length > 0 ? [...active] : [...this.relayReservation?.relayMultiaddrs ?? []];
6873
6946
  }
6874
6947
  };
6948
+ function isHeartbeatRegistrationInvalid(error) {
6949
+ if (!(error instanceof CloudDeviceFetchError) || error.code !== "cloud_http_error") {
6950
+ return false;
6951
+ }
6952
+ if (error.status === 404) return true;
6953
+ return error.status === 401 && hasExplicitRegistrationInvalidCode(error.responseBody);
6954
+ }
6955
+ function hasExplicitRegistrationInvalidCode(responseBody) {
6956
+ const body = responseBody?.trim();
6957
+ if (!body) return false;
6958
+ let value;
6959
+ try {
6960
+ value = JSON.parse(body);
6961
+ } catch {
6962
+ return REGISTRATION_INVALID_ERROR_CODES.has(normalizeErrorCode(body));
6963
+ }
6964
+ return registrationInvalidCodeFromValue(value);
6965
+ }
6966
+ function registrationInvalidCodeFromValue(value) {
6967
+ if (typeof value === "string") {
6968
+ return REGISTRATION_INVALID_ERROR_CODES.has(normalizeErrorCode(value));
6969
+ }
6970
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
6971
+ const record = value;
6972
+ return registrationInvalidCodeFromValue(record.code) || registrationInvalidCodeFromValue(record.errorCode) || registrationInvalidCodeFromValue(record.error);
6973
+ }
6974
+ function normalizeErrorCode(value) {
6975
+ return value.trim().toLowerCase().replaceAll("-", "_");
6976
+ }
6875
6977
  function toPublicDeviceIdentity(identity) {
6876
6978
  return {
6877
6979
  peerId: identity.peerId,
@@ -6881,21 +6983,42 @@ function toPublicDeviceIdentity(identity) {
6881
6983
  platform: identity.platform
6882
6984
  };
6883
6985
  }
6884
- function hasValidDirectCloudDeviceAddress(addresses) {
6885
- return addresses.some((address) => {
6886
- if (address.includes("/p2p-circuit")) return false;
6887
- const parts = address.split("/");
6888
- const protocolIndex = parts.findIndex(
6889
- (part) => part === "ip4" || part === "ip6" || part === "dns" || part === "dns4" || part === "dns6"
6890
- );
6891
- if (protocolIndex < 0) return false;
6892
- const host = parts[protocolIndex + 1]?.toLowerCase();
6893
- if (!host) return false;
6894
- const protocol = parts[protocolIndex];
6895
- if (protocol === "ip4") return isPublicIpv4(host);
6896
- if (protocol === "ip6") return isPublicIpv6(host);
6897
- return isPlausiblePublicDnsHost(host);
6898
- });
6986
+ function hasValidDirectCloudDeviceAddress(addresses, expectedPeerId) {
6987
+ return addresses.some((address) => isDialableDirectMultiaddr(address, expectedPeerId));
6988
+ }
6989
+ function isDialableDirectMultiaddr(address, expectedPeerId) {
6990
+ if (address.length === 0 || address.length > 4096 || address !== address.trim() || !address.startsWith("/") || address.endsWith("/") || address.includes("//") || address.includes("/p2p-circuit")) return false;
6991
+ const parts = address.split("/").slice(1);
6992
+ if (parts.some((part) => part.length === 0 || containsAsciiControlOrSpace3(part))) return false;
6993
+ const hostProtocol = parts[0];
6994
+ const host = parts[1]?.toLowerCase();
6995
+ if (host === void 0 || hostProtocol !== "ip4" && hostProtocol !== "ip6" && hostProtocol !== "dns" && hostProtocol !== "dns4" && hostProtocol !== "dns6") return false;
6996
+ const publicHost = hostProtocol === "ip4" ? isPublicIpv4(host) : hostProtocol === "ip6" ? isPublicIpv6(host) : isPlausiblePublicDnsHost(host);
6997
+ if (!publicHost) return false;
6998
+ const transport = parts[2];
6999
+ const port = parsePort(parts[3]);
7000
+ if (transport !== "tcp" || port === void 0) return false;
7001
+ let index = 4;
7002
+ if (parts[index] === "ws" || parts[index] === "wss") index += 1;
7003
+ if (parts[index] === "p2p") {
7004
+ const peerId = parts[index + 1];
7005
+ if (!peerId || containsAsciiControlOrSpace3(peerId)) return false;
7006
+ if (expectedPeerId !== void 0 && peerId !== expectedPeerId) return false;
7007
+ index += 2;
7008
+ }
7009
+ return index === parts.length;
7010
+ }
7011
+ function parsePort(value) {
7012
+ if (value === void 0 || !/^\d{1,5}$/u.test(value)) return void 0;
7013
+ const port = Number(value);
7014
+ return port >= 1 && port <= 65535 ? port : void 0;
7015
+ }
7016
+ function containsAsciiControlOrSpace3(value) {
7017
+ for (let index = 0; index < value.length; index += 1) {
7018
+ const code = value.charCodeAt(index);
7019
+ if (code <= 32 || code === 127) return true;
7020
+ }
7021
+ return false;
6899
7022
  }
6900
7023
  function isPublicIpv4(host) {
6901
7024
  const octets = host.split(".").map((part) => Number(part));
@@ -6935,7 +7058,17 @@ function isPlausiblePublicDnsHost(host) {
6935
7058
  const normalized = host.endsWith(".") ? host.slice(0, -1) : host;
6936
7059
  if (isIpv4Syntax(normalized)) return isPublicIpv4(normalized);
6937
7060
  if (normalized.includes(":")) return isPublicIpv6(normalized);
6938
- if (normalized === "localhost" || normalized.endsWith(".localhost") || normalized.endsWith(".local") || normalized.endsWith(".internal")) return false;
7061
+ const specialUseSuffixes = [
7062
+ "example",
7063
+ "home.arpa",
7064
+ "internal",
7065
+ "invalid",
7066
+ "local",
7067
+ "localhost",
7068
+ "onion",
7069
+ "test"
7070
+ ];
7071
+ if (specialUseSuffixes.some((suffix) => normalized === suffix || normalized.endsWith(`.${suffix}`))) return false;
6939
7072
  const labels = normalized.split(".");
6940
7073
  return labels.length > 1 && labels.every(
6941
7074
  (label) => label.length > 0 && label.length <= 63 && /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/u.test(label)
@@ -7905,6 +8038,7 @@ export {
7905
8038
  DEVICE_HEARTBEAT_DEFAULT_MAX_CLOCK_SKEW_MS,
7906
8039
  DEVICE_HEARTBEAT_LIMITS,
7907
8040
  DEVICE_HEARTBEAT_SIGNATURE_DOMAIN,
8041
+ DEVICE_ORCHESTRATION_FRAME_LIMITS,
7908
8042
  DEVICE_ORCHESTRATION_PROTOCOL,
7909
8043
  DEVICE_PAIRING_INVITE_MAX_CLOCK_SKEW_MS,
7910
8044
  DEVICE_PAIRING_INVITE_PROTOCOL,