@ibgib/space-gib 0.0.3 → 0.0.5

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 (63) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/IMPLEMENTATION.md +9 -13
  3. package/README.md +7 -0
  4. package/dist/client/bootstrap.mjs +1 -1
  5. package/dist/client/bootstrap.mjs.map +1 -1
  6. package/dist/client/chunk-ANGVYAEK.mjs +42 -0
  7. package/dist/client/chunk-ANGVYAEK.mjs.map +7 -0
  8. package/dist/client/chunk-IRGFDQRD.mjs +1920 -0
  9. package/dist/client/chunk-IRGFDQRD.mjs.map +7 -0
  10. package/dist/client/index.html +103 -5
  11. package/dist/client/index.mjs +1 -1
  12. package/dist/client/script.mjs +1 -1
  13. package/dist/client/style.css +466 -61
  14. package/dist/respec-gib.node.mjs +5 -0
  15. package/dist/server/server.mjs +533 -233
  16. package/dist/server/server.mjs.map +2 -2
  17. package/package.json +6 -6
  18. package/src/client/AUTO-GENERATED-version.mts +1 -1
  19. package/src/client/components/identity-header/IMPLEMENTATION.md +45 -0
  20. package/src/client/components/identity-header/identity-header.css +74 -0
  21. package/src/client/components/identity-header/identity-header.html +10 -0
  22. package/src/client/components/identity-header/identity-header.mts +361 -0
  23. package/src/client/components/identity-manager/IMPLEMENTATION.md +100 -0
  24. package/src/client/components/identity-manager/identity-manager.css +467 -0
  25. package/src/client/components/identity-manager/identity-manager.html +113 -0
  26. package/src/client/components/identity-manager/identity-manager.mts +767 -0
  27. package/src/client/components/keystone-creator/keystone-creator.css +2 -76
  28. package/src/client/components/keystone-creator/keystone-creator.html +41 -26
  29. package/src/client/components/keystone-creator/keystone-creator.mts +178 -41
  30. package/src/client/dev-tools/base-tools.mts +252 -0
  31. package/src/client/dev-tools/common.mts +217 -0
  32. package/src/client/dev-tools/phase-1.mts +156 -0
  33. package/src/client/dev-tools/phase-2.mts +143 -0
  34. package/src/client/dev-tools/phase-3.mts +189 -0
  35. package/src/client/dev-tools/phase-4-1.mts +197 -0
  36. package/src/client/dev-tools/phase-4-10.mts +884 -0
  37. package/src/client/dev-tools/phase-4-2.mts +388 -0
  38. package/src/client/dev-tools/phase-4-3.mts +391 -0
  39. package/src/client/dev-tools/phase-4-4.mts +374 -0
  40. package/src/client/dev-tools/phase-4-5.mts +376 -0
  41. package/src/client/dev-tools/phase-4-6.mts +273 -0
  42. package/src/client/dev-tools/phase-4-7.mts +399 -0
  43. package/src/client/dev-tools/phase-4-8.mts +430 -0
  44. package/src/client/dev-tools/phase-4-9.mts +398 -0
  45. package/src/client/dev-tools/phase-4.mts +1302 -0
  46. package/src/client/dev-tools.mts +52 -1194
  47. package/src/client/index.html +103 -5
  48. package/src/client/style.css +466 -61
  49. package/src/client/ui/shell/space-gib-shell-constants.mts +0 -2
  50. package/src/client/ui/shell/space-gib-shell-service.mts +82 -10
  51. package/src/common/common-constants.mts +0 -0
  52. package/src/common/keystone-policies.json +40 -43
  53. package/src/common/keystone-policies.mts +3 -5
  54. package/src/server/path-helper.respec.mts +99 -94
  55. package/src/server/serve-gib/README.md +9 -0
  56. package/src/server/serve-gib/handlers/api/keystone/keystone-genesis.handler.mts +1 -1
  57. package/src/server/serve-gib/handlers/api/keystone/keystone-get.respec.mts +1 -1
  58. package/src/server/serve-gib/handlers/ws/sync-upgrade-handler-base.mts +31 -3
  59. package/src/server/serve-gib/handlers/ws/ws-helper.mts +73 -45
  60. package/dist/client/chunk-2KJC5XKE.mjs +0 -31
  61. package/dist/client/chunk-2KJC5XKE.mjs.map +0 -7
  62. package/dist/client/chunk-QNIXTRFO.mjs +0 -235
  63. package/dist/client/chunk-QNIXTRFO.mjs.map +0 -7
@@ -7261,7 +7261,7 @@ async function getGraphProjection_Live({ ibGibs, ibGibAddrs, gotten, tjpAddrsAlr
7261
7261
  rel8nNames.forEach((rel8nName) => {
7262
7262
  const rel8dAddrs = rel8ns[rel8nName] ?? [];
7263
7263
  rel8dAddrs.forEach((rel8dAddr) => {
7264
- if (!rel8dAddrsNotYetGotten.includes(rel8dAddr) && !ibGibs.some((x) => getIbGibAddr({ ibGib: x }) === rel8dAddr)) {
7264
+ if (!rel8dAddrsNotYetGotten.includes(rel8dAddr) && !ibGibs.some((x) => getIbGibAddr({ ibGib: x }) === rel8dAddr) && !isPrimitive({ gib: getIbAndGib({ ibGibAddr: rel8dAddr }).gib })) {
7265
7265
  rel8dAddrsNotYetGotten.push(rel8dAddr);
7266
7266
  }
7267
7267
  });
@@ -17650,6 +17650,8 @@ var KEYSTONE_REPLENISH_STRATEGY_VALID_VALUES = Object.values(KeystoneReplenishSt
17650
17650
  var KEYSTONE_ATOM = "keystone";
17651
17651
  var KEYSTONE_POOL_ID_REGEXP = /^\w[\w\-.]*$/;
17652
17652
  var KEYSTONE_SALT_REGEXP = /^[a-zA-Z0-9\-_]{1,64}$/;
17653
+ var KEYSTONE_USERNAME_REGEXP = /^[a-zA-Z0-9_\-.]{1,63}$/;
17654
+ var KEYSTONE_DESCRIPTION_REGEXP = /^[a-zA-Z0-9\s.,!?:;'"\-()[\]_+=@#$%&*\/]{0,128}$/;
17653
17655
  var KEYSTONE_HASH_MAX_ROUNDS = 1e3;
17654
17656
  var KEYSTONE_VERB_REVOKE = "revoke";
17655
17657
  var KEYSTONE_VERB_MANAGE = "manage";
@@ -17691,6 +17693,7 @@ var KeystoneVerb = {
17691
17693
  };
17692
17694
  var KEYSTONE_VERB_VALID_VALUES = Object.values(KeystoneVerb);
17693
17695
  var POOL_ID_REVOKE = KEYSTONE_VERB_REVOKE;
17696
+ var POOL_ID_MANAGE = KEYSTONE_VERB_MANAGE;
17694
17697
  var POOL_ID_CONNECT = KEYSTONE_VERB_CONNECT;
17695
17698
  var POOL_ID_SYNC = KEYSTONE_VERB_SYNC;
17696
17699
  var KEYSTONE_CONFIG_DEFAULT_SIZE = 200;
@@ -17938,7 +17941,15 @@ async function parseKeystoneIb({ ib }) {
17938
17941
  }
17939
17942
  }
17940
17943
  }
17941
- function getDeterministicRequirements({ pool, requiredChallengeIds, targetAddr }) {
17944
+ async function validateKeystoneIb({ ib }) {
17945
+ try {
17946
+ await parseKeystoneIb({ ib });
17947
+ return true;
17948
+ } catch {
17949
+ return false;
17950
+ }
17951
+ }
17952
+ async function getDeterministicRequirements({ pool, requiredChallengeIds, targetAddr }) {
17942
17953
  const behavior = pool.config.behavior;
17943
17954
  const mandatory = /* @__PURE__ */ new Set();
17944
17955
  let available = Object.keys(pool.challenges);
@@ -17954,19 +17965,20 @@ function getDeterministicRequirements({ pool, requiredChallengeIds, targetAddr }
17954
17965
  }
17955
17966
  available = available.filter((id) => !mandatory.has(id));
17956
17967
  }
17957
- if (behavior.targetBindingChars > 0 && targetAddr) {
17958
- const { gib } = getIbAndGib({ ibGibAddr: targetAddr });
17959
- if (gib) {
17960
- const prefixes = gib !== GIB ? gib.substring(0, behavior.targetBindingChars).toLowerCase() : "abc";
17961
- for (const char of prefixes) {
17962
- const bucket = pool.bindingMap[char] || [];
17963
- const match = bucket.find((id) => available.includes(id));
17964
- if (!match) {
17965
- throw new Error(`Entropy Exhaustion. Cannot satisfy binding for char '${char}'. (E: 341b95dc3a58be3e083d1d9c4a0c4925)`);
17966
- }
17967
- mandatory.add(match);
17968
- available = available.filter((id) => id !== match);
17968
+ if (behavior.targetBindingCount > 0 && targetAddr) {
17969
+ let sortedAvailable = [...available].sort();
17970
+ let currentHash = await hash({ s: targetAddr });
17971
+ for (let i = 0; i < behavior.targetBindingCount; i++) {
17972
+ if (sortedAvailable.length === 0) {
17973
+ throw new Error(`Entropy Exhaustion. Cannot satisfy target binding. Sorted available pool is empty. (E: 341b95dc3a58be3e083d1d9c4a0c4925)`);
17969
17974
  }
17975
+ const hashBigInt = BigInt("0x" + currentHash);
17976
+ const index = Number(hashBigInt % BigInt(sortedAvailable.length));
17977
+ const selectedId = sortedAvailable[index];
17978
+ mandatory.add(selectedId);
17979
+ sortedAvailable.splice(index, 1);
17980
+ available = available.filter((id) => id !== selectedId);
17981
+ currentHash = await hash({ s: currentHash });
17970
17982
  }
17971
17983
  }
17972
17984
  if (behavior.selectSequentially > 0) {
@@ -17980,22 +17992,6 @@ function getDeterministicRequirements({ pool, requiredChallengeIds, targetAddr }
17980
17992
  }
17981
17993
  return { mandatoryIds: mandatory, availableIds: available };
17982
17994
  }
17983
- function addToBindingMap(map, challengeId) {
17984
- const firstChar = challengeId.charAt(0).toLowerCase();
17985
- if (/[0-9a-f]/.test(firstChar)) {
17986
- if (!map[firstChar]) {
17987
- map[firstChar] = [];
17988
- }
17989
- map[firstChar].push(challengeId);
17990
- } else {
17991
- throw new Error(`invalid challengeId (${challengeId}). Must start with a hex character. (E: c96ed8460de89e28c801370a0f07f826)`);
17992
- }
17993
- }
17994
- function removeFromBindingMap(map, challengeId) {
17995
- for (const key of Object.keys(map)) {
17996
- map[key] = map[key].filter((id) => id !== challengeId);
17997
- }
17998
- }
17999
17995
  function resolveTargetPool({ pools, poolId, poolFilter, verb }) {
18000
17996
  const lc2 = `[resolveTargetPool]`;
18001
17997
  try {
@@ -18034,10 +18030,10 @@ function resolveTargetPool({ pools, poolId, poolFilter, verb }) {
18034
18030
  throw error;
18035
18031
  }
18036
18032
  }
18037
- function selectChallengeIds({ pool, targetAddr, requiredChallengeIds }) {
18033
+ async function selectChallengeIds({ pool, targetAddr, requiredChallengeIds }) {
18038
18034
  const lc2 = `[selectChallengeIds]`;
18039
18035
  try {
18040
- const { mandatoryIds, availableIds } = getDeterministicRequirements({
18036
+ const { mandatoryIds, availableIds } = await getDeterministicRequirements({
18041
18037
  pool,
18042
18038
  requiredChallengeIds,
18043
18039
  targetAddr
@@ -18073,11 +18069,6 @@ async function applyReplenishmentStrategy({ prevPools, targetPoolId, consumedIds
18073
18069
  const poolSecret = await strategy.derivePoolSecret({ masterSecret });
18074
18070
  const timestamp = Date.now().toString();
18075
18071
  const strategyType = config.behavior.replenish;
18076
- consumedIds.forEach((id) => {
18077
- if (pool.bindingMap) {
18078
- removeFromBindingMap(pool.bindingMap, id);
18079
- }
18080
- });
18081
18072
  if (strategyType === KeystoneReplenishStrategy.topUp) {
18082
18073
  consumedIds.forEach((id) => delete pool.challenges[id]);
18083
18074
  for (let i = 0; i < consumedIds.length; i++) {
@@ -18092,14 +18083,9 @@ async function applyReplenishmentStrategy({ prevPools, targetPoolId, consumedIds
18092
18083
  challengeId: newId
18093
18084
  });
18094
18085
  pool.challenges[newId] = await strategy.generateChallenge({ solution });
18095
- if (!pool.bindingMap) {
18096
- pool.bindingMap = {};
18097
- }
18098
- addToBindingMap(pool.bindingMap, newId);
18099
18086
  }
18100
18087
  } else if (strategyType === KeystoneReplenishStrategy.replaceAll) {
18101
18088
  pool.challenges = {};
18102
- pool.bindingMap = {};
18103
18089
  for (let i = 0; i < config.behavior.size; i++) {
18104
18090
  const newId = await generateOpaqueChallengeId({
18105
18091
  salt: config.salt,
@@ -18112,13 +18098,11 @@ async function applyReplenishmentStrategy({ prevPools, targetPoolId, consumedIds
18112
18098
  challengeId: newId
18113
18099
  });
18114
18100
  pool.challenges[newId] = await strategy.generateChallenge({ solution });
18115
- addToBindingMap(pool.bindingMap, newId);
18116
18101
  }
18117
18102
  } else if (strategyType === KeystoneReplenishStrategy.consume) {
18118
18103
  consumedIds.forEach((id) => delete pool.challenges[id]);
18119
18104
  } else if (strategyType === KeystoneReplenishStrategy.deleteAll) {
18120
18105
  pool.challenges = {};
18121
- pool.bindingMap = {};
18122
18106
  } else {
18123
18107
  throw new Error(`Unknown replenish strategy: ${strategyType}. Valid list: ${pretty(KEYSTONE_REPLENISH_STRATEGY_VALID_VALUES)} (E: 0acf56f1e1486240080e11e8046d0825)`);
18124
18108
  }
@@ -18230,10 +18214,14 @@ async function validateChallengePool({ pool }) {
18230
18214
  errors.push(`${lc2} pool.config.id falsy (E: 31d7943d95f877326d5f4ea14463d626)`);
18231
18215
  }
18232
18216
  if (pool.config.behavior) {
18233
- const { size } = pool.config.behavior;
18217
+ const { size, selectSequentially, selectRandomly, targetBindingCount } = pool.config.behavior;
18234
18218
  if (!size || size === 0) {
18235
18219
  errors.push(`${lc2} invalid pool.config.behavior.size (${size}). Must be positive integer. (E: b221e36ec102bdc944552248ce8fe626)`);
18236
18220
  }
18221
+ const totalRequested = (selectSequentially || 0) + (selectRandomly || 0) + (targetBindingCount || 0);
18222
+ if (totalRequested >= size) {
18223
+ errors.push(`${lc2} Total requested challenges (${totalRequested}) cannot equal or exceed pool size (${size}) to prevent full pool exposure and selection exhaustion. (E: 81cb834f826315264bca81c15fca58f1)`);
18224
+ }
18237
18225
  } else {
18238
18226
  errors.push(`${lc2} pool.config.behavior falsy (E: bede081c066c39732eefe2f92e296326)`);
18239
18227
  }
@@ -18259,6 +18247,27 @@ async function validateChallengePool({ pool }) {
18259
18247
  }
18260
18248
  }
18261
18249
  }
18250
+ function validateKeystoneMetadata({ data }) {
18251
+ const lc2 = `[validateKeystoneMetadata]`;
18252
+ const errors = [];
18253
+ const checkDetails = (details, source) => {
18254
+ if (!details)
18255
+ return;
18256
+ if (details.username !== void 0) {
18257
+ if (typeof details.username !== "string" || !KEYSTONE_USERNAME_REGEXP.test(details.username)) {
18258
+ errors.push(`${lc2} invalid username in ${source} (${details.username}). Must match ${KEYSTONE_USERNAME_REGEXP}`);
18259
+ }
18260
+ }
18261
+ if (details.description !== void 0) {
18262
+ if (typeof details.description !== "string" || !KEYSTONE_DESCRIPTION_REGEXP.test(details.description)) {
18263
+ errors.push(`${lc2} invalid description in ${source} (${details.description}). Must match ${KEYSTONE_DESCRIPTION_REGEXP}`);
18264
+ }
18265
+ }
18266
+ };
18267
+ checkDetails(data.frameDetails, "frameDetails");
18268
+ checkDetails(data.checkpointDetails, "checkpointDetails");
18269
+ return errors;
18270
+ }
18262
18271
  async function validateGenesisKeystone({ keystoneIbGib }) {
18263
18272
  const lc2 = `[${validateGenesisKeystone.name}]`;
18264
18273
  try {
@@ -18267,6 +18276,12 @@ async function validateGenesisKeystone({ keystoneIbGib }) {
18267
18276
  }
18268
18277
  const errors = [];
18269
18278
  const { data, rel8ns } = keystoneIbGib;
18279
+ const isValidIb = await validateKeystoneIb({ ib: keystoneIbGib.ib });
18280
+ if (!isValidIb) {
18281
+ errors.push(`${lc2} invalid keystone ib (${keystoneIbGib.ib}). (E: ad7a88bc38c290a184dfbd42be48f526)`);
18282
+ }
18283
+ const metadataErrors = validateKeystoneMetadata({ data });
18284
+ metadataErrors.forEach((x) => errors.push(x));
18270
18285
  if (data.proofs && data.proofs.length > 0) {
18271
18286
  errors.push(`${lc2} proofs already exist on genesis keystone. (E: 7a5e15f20918f1bbd8ffb62857dcd526)`);
18272
18287
  }
@@ -18290,6 +18305,54 @@ async function validateGenesisKeystone({ keystoneIbGib }) {
18290
18305
  }
18291
18306
  }
18292
18307
  }
18308
+ function isDelegatesChanged({ currDelegates, prevDelegates }) {
18309
+ const curr = currDelegates || {};
18310
+ const prev = prevDelegates || {};
18311
+ const currKeys = Object.keys(curr);
18312
+ const prevKeys = Object.keys(prev);
18313
+ if (currKeys.length !== prevKeys.length) {
18314
+ return true;
18315
+ }
18316
+ for (const key of currKeys) {
18317
+ const currVal = curr[key];
18318
+ const prevVal = prev[key];
18319
+ if (!prevVal) {
18320
+ return true;
18321
+ }
18322
+ if (currVal.delegateTjpAddr !== prevVal.delegateTjpAddr || currVal.delegateAddr !== prevVal.delegateAddr || currVal.thisAddr !== prevVal.thisAddr) {
18323
+ return true;
18324
+ }
18325
+ }
18326
+ return false;
18327
+ }
18328
+ async function validateDelegates({ currDelegates, prevIbGib, lc: lc2, errors }) {
18329
+ const currKeys = Object.keys(currDelegates);
18330
+ const prevAddr = getIbGibAddr({ ibGib: prevIbGib });
18331
+ for (const key of currKeys) {
18332
+ const info = currDelegates[key];
18333
+ if (key !== info.delegateTjpAddr) {
18334
+ errors.push(`${lc2} Invalid delegate mapping: Key '${key}' does not match delegateTjpAddr '${info.delegateTjpAddr}'. (E: 9c8da8bc38c290a184dfbd42be48f526)`);
18335
+ }
18336
+ const [parsedDelegateTjp, parsedDelegate, parsedThis] = await Promise.all([
18337
+ validateKeystoneIb({ ib: info.delegateTjpAddr.split("^")[0] }),
18338
+ validateKeystoneIb({ ib: info.delegateAddr.split("^")[0] }),
18339
+ validateKeystoneIb({ ib: info.thisAddr.split("^")[0] })
18340
+ ]);
18341
+ if (!parsedDelegateTjp) {
18342
+ errors.push(`${lc2} Delegate delegateTjpAddr '${info.delegateTjpAddr}' is not a valid keystone address. (E: 10d8a8bc38c290a184dfbd42be48f526)`);
18343
+ }
18344
+ if (!parsedDelegate) {
18345
+ errors.push(`${lc2} Delegate delegateAddr '${info.delegateAddr}' is not a valid keystone address. (E: 20d8a8bc38c290a184dfbd42be48f526)`);
18346
+ }
18347
+ if (!parsedThis) {
18348
+ errors.push(`${lc2} Delegate thisAddr '${info.thisAddr}' is not a valid keystone address. (E: 30d8a8bc38c290a184dfbd42be48f526)`);
18349
+ }
18350
+ const isHistoricalParent = info.thisAddr === prevAddr || (prevIbGib.rel8ns?.past || []).includes(info.thisAddr);
18351
+ if (!isHistoricalParent) {
18352
+ errors.push(`${lc2} Delegate thisAddr '${info.thisAddr}' is not a valid historical address in the parent keystone timeline. (E: 40d8a8bc38c290a184dfbd42be48f526)`);
18353
+ }
18354
+ }
18355
+ }
18293
18356
  async function validateKeystoneTransition({ currentIbGib, prevIbGib }) {
18294
18357
  const lc2 = `[${validateKeystoneTransition.name}]`;
18295
18358
  const errors = [];
@@ -18306,21 +18369,37 @@ async function validateKeystoneTransition({ currentIbGib, prevIbGib }) {
18306
18369
  }
18307
18370
  const currData = currentIbGib.data;
18308
18371
  const prevData = prevIbGib.data;
18372
+ const isValidIb = await validateKeystoneIb({ ib: currentIbGib.ib });
18373
+ if (!isValidIb) {
18374
+ errors.push(`${lc2} invalid keystone ib (${currentIbGib.ib}). (E: a98dc8bc38c290a184dfbd42be48f526)`);
18375
+ }
18376
+ const metadataErrors = validateKeystoneMetadata({ data: currData });
18377
+ metadataErrors.forEach((x) => errors.push(x));
18378
+ const currDelegates = currData.delegates || {};
18379
+ const prevDelegates = prevData.delegates || {};
18380
+ const delegatesChanged = isDelegatesChanged({ currDelegates, prevDelegates });
18381
+ if (delegatesChanged) {
18382
+ const hasManageProof = currData.proofs?.some((proof) => proof.claim?.verb === KEYSTONE_VERB_MANAGE);
18383
+ if (!hasManageProof) {
18384
+ errors.push(`${lc2} Policy Violation: Delegates map was mutated but no proof for manage verb ('${KEYSTONE_VERB_MANAGE}') is present. (E: 819fa8cb28c9b60e68994511a5825)`);
18385
+ }
18386
+ }
18387
+ await validateDelegates({ currDelegates, prevIbGib, lc: lc2, errors });
18309
18388
  const currDataN = currData.n ?? -2;
18310
18389
  const prevDataN = prevData.n ?? -2;
18311
18390
  if (currDataN < 0) {
18312
- errors.push("invalid keystone.currData.n is undefined or null.");
18391
+ errors.push("invalid keystone.currData.n is undefined or null. (E: 9fbe38704b68829d18ea1d0940d0aa26)");
18313
18392
  }
18314
18393
  if (prevDataN < 0) {
18315
- errors.push("invalid keystone.prevData.n is undefined or null.");
18394
+ errors.push("invalid keystone.prevData.n is undefined or null. (E: 6dc5aa9c87017db6080ed4268b07af26)");
18316
18395
  }
18317
18396
  ;
18318
18397
  if (currDataN !== prevDataN + 1) {
18319
- errors.push(`keystone data.n values are not sequential. prevData.n: ${prevDataN}, currData.n: ${currDataN}. `);
18398
+ errors.push(`keystone data.n values are not sequential. prevData.n: ${prevDataN}, currData.n: ${currDataN}. (E: 5ae12809f2c67216d38d8ba8d6c42826)`);
18320
18399
  }
18321
18400
  for (const proof of currData.proofs) {
18322
18401
  if (proof.solutions.length === 0) {
18323
- errors.push(`Proof ${proof.id || "unknown"} has no solutions.`);
18402
+ errors.push(`Proof ${proof.id || "unknown"} has no solutions. (E: e2b4683ce0ce30f798ec77841e88c826)`);
18324
18403
  continue;
18325
18404
  }
18326
18405
  const poolId = proof.solutions[0].poolId;
@@ -18359,7 +18438,7 @@ async function verifyProofAgainstPool({ proof, pool, errors }) {
18359
18438
  errors.push(`Policy Violation: Pool ${pool.id} used for unauthorized verb ${proof.claim.verb}`);
18360
18439
  }
18361
18440
  }
18362
- const { mandatoryIds, availableIds } = getDeterministicRequirements({
18441
+ const { mandatoryIds, availableIds } = await getDeterministicRequirements({
18363
18442
  pool,
18364
18443
  requiredChallengeIds: proof.requiredChallengeIds,
18365
18444
  targetAddr: proof.claim.target
@@ -18428,6 +18507,9 @@ async function evolvePersistAndRegisterKeystone({ prevIbGib, newData, metaspace,
18428
18507
  if (prevData.checkpointDetails) {
18429
18508
  dataToRemove.checkpointDetails = {};
18430
18509
  }
18510
+ if (prevData.delegates) {
18511
+ dataToRemove.delegates = "remove";
18512
+ }
18431
18513
  if (Object.keys(dataToRemove).length === 0) {
18432
18514
  dataToRemove = void 0;
18433
18515
  }
@@ -18641,7 +18723,6 @@ var KeystoneService_V1 = class _KeystoneService_V1 {
18641
18723
  const strategy = KeystoneStrategyFactory.create({ config });
18642
18724
  const poolSecret = await strategy.derivePoolSecret({ masterSecret });
18643
18725
  const challenges = {};
18644
- const bindingMap = {};
18645
18726
  const targetSize = config.behavior.size;
18646
18727
  const timestamp = Date.now().toString();
18647
18728
  for (let i = 0; i < targetSize; i++) {
@@ -18657,13 +18738,11 @@ var KeystoneService_V1 = class _KeystoneService_V1 {
18657
18738
  });
18658
18739
  const challenge = await strategy.generateChallenge({ solution });
18659
18740
  challenges[challengeId] = challenge;
18660
- addToBindingMap(bindingMap, challengeId);
18661
18741
  }
18662
18742
  challengePools.push({
18663
18743
  id: config.id,
18664
18744
  config,
18665
- challenges,
18666
- bindingMap
18745
+ challenges
18667
18746
  });
18668
18747
  }
18669
18748
  if (challengePools.length === 0) {
@@ -18693,7 +18772,7 @@ var KeystoneService_V1 = class _KeystoneService_V1 {
18693
18772
  *
18694
18773
  * todo: wrap this and other entire keystone sign/method implementations in locks on the keystone's tjpGib.
18695
18774
  */
18696
- async sign({ latestKeystone, masterSecret, claim, poolId, poolFilter, requiredChallengeIds = [], frameDetails, metaspace, space }) {
18775
+ async sign({ latestKeystone, masterSecret, claim, poolId, poolFilter, requiredChallengeIds = [], frameDetails, metaspace, space, dataToPatch }) {
18697
18776
  const lc2 = `${this.lc}[${this.sign.name}]`;
18698
18777
  try {
18699
18778
  if (logalot41) {
@@ -18712,7 +18791,7 @@ var KeystoneService_V1 = class _KeystoneService_V1 {
18712
18791
  if (logalot41) {
18713
18792
  console.log(`${lc2} Selected pool: ${pool.id} (size: ${Object.keys(pool.challenges).length}) (I: 6b26d6f4aad18380f2b3a0989b592826)`);
18714
18793
  }
18715
- const idsToSolve = selectChallengeIds({
18794
+ const idsToSolve = await selectChallengeIds({
18716
18795
  pool,
18717
18796
  targetAddr: claim.target,
18718
18797
  requiredChallengeIds
@@ -18742,7 +18821,9 @@ var KeystoneService_V1 = class _KeystoneService_V1 {
18742
18821
  challengePools: nextPools,
18743
18822
  proofs: [proof],
18744
18823
  frameDetails,
18745
- checkpointDetails
18824
+ checkpointDetails,
18825
+ ...prevData.delegates ? { delegates: prevData.delegates } : {},
18826
+ ...dataToPatch
18746
18827
  };
18747
18828
  const resKeystone = await evolvePersistAndRegisterKeystone({
18748
18829
  prevIbGib: latestKeystone,
@@ -18775,6 +18856,155 @@ var KeystoneService_V1 = class _KeystoneService_V1 {
18775
18856
  const errors = await validateKeystoneTransition({ currentIbGib, prevIbGib });
18776
18857
  return errors;
18777
18858
  }
18859
+ /**
18860
+ * Registers a delegate keystone on the parent keystone by appending its information
18861
+ * to the parent's `delegates` map. This operation is signed using the parent's `manage` pool.
18862
+ * Validates the delegate keystone's entire graph/timeline before registering.
18863
+ */
18864
+ async registerDelegate({ parentKeystone, delegateKeystone, masterSecret, frameDetails, metaspace, space }) {
18865
+ const lc2 = `${this.lc}[${this.registerDelegate.name}]`;
18866
+ try {
18867
+ if (logalot41) {
18868
+ console.log(`${lc2} starting...`);
18869
+ }
18870
+ const validationErrors = await validateKeystoneGraph({
18871
+ keystoneIbGib: delegateKeystone,
18872
+ getLatest: true,
18873
+ invalidIfMoreRecentKeystoneFoundInSpace: false,
18874
+ space
18875
+ });
18876
+ if (validationErrors.length > 0) {
18877
+ throw new Error(`Delegate keystone validation failed: ${validationErrors.join(", ")} (E: 88fa8ab28c9b60e68994511a5825)`);
18878
+ }
18879
+ const prevData = parentKeystone.data;
18880
+ const hasManagePool = prevData.challengePools.some((p) => p.id === POOL_ID_MANAGE);
18881
+ if (!hasManagePool) {
18882
+ throw new Error(`Parent keystone does not support delegation. Manage pool is required. (E: 818fa8ab28c9b60e68994511a5825)`);
18883
+ }
18884
+ const delegateTjpAddr = getTjpAddr({ ibGib: delegateKeystone });
18885
+ if (!delegateTjpAddr) {
18886
+ throw new Error(`(UNEXPECTED) delegateTjpAddr falsy? (E: d4d6e8ad03c8925d4838b9a87b8f2826)`);
18887
+ }
18888
+ const claim = {
18889
+ target: delegateTjpAddr,
18890
+ verb: KEYSTONE_VERB_MANAGE
18891
+ };
18892
+ const currentDelegates = prevData.delegates ? { ...prevData.delegates } : {};
18893
+ currentDelegates[delegateTjpAddr] = {
18894
+ delegateTjpAddr,
18895
+ delegateAddr: getIbGibAddr({ ibGib: delegateKeystone }),
18896
+ thisAddr: getIbGibAddr({ ibGib: parentKeystone })
18897
+ };
18898
+ const evolved = await this.sign({
18899
+ latestKeystone: parentKeystone,
18900
+ masterSecret,
18901
+ claim,
18902
+ poolId: POOL_ID_MANAGE,
18903
+ frameDetails,
18904
+ metaspace,
18905
+ space,
18906
+ dataToPatch: {
18907
+ delegates: currentDelegates
18908
+ }
18909
+ });
18910
+ return evolved;
18911
+ } catch (error) {
18912
+ console.error(`${lc2} ${extractErrorMsg(error)}`);
18913
+ throw error;
18914
+ } finally {
18915
+ if (logalot41) {
18916
+ console.log(`${lc2} complete.`);
18917
+ }
18918
+ }
18919
+ }
18920
+ /**
18921
+ * Unregisters a delegate keystone from the parent keystone by removing its entry
18922
+ * from the parent's `delegates` map. This operation is signed using the parent's `manage` pool.
18923
+ */
18924
+ async unregisterDelegate({ parentKeystone, delegateTjpAddr, masterSecret, frameDetails, metaspace, space }) {
18925
+ const lc2 = `${this.lc}[${this.unregisterDelegate.name}]`;
18926
+ try {
18927
+ if (logalot41) {
18928
+ console.log(`${lc2} starting...`);
18929
+ }
18930
+ const prevData = parentKeystone.data;
18931
+ const hasManagePool = prevData.challengePools.some((p) => p.id === POOL_ID_MANAGE);
18932
+ if (!hasManagePool) {
18933
+ throw new Error(`Parent keystone does not support delegation. Manage pool is required. (E: 3889f8ab28c9b60e68994511a5825)`);
18934
+ }
18935
+ const claim = {
18936
+ target: delegateTjpAddr,
18937
+ verb: KEYSTONE_VERB_MANAGE
18938
+ };
18939
+ const currentDelegates = prevData.delegates ? { ...prevData.delegates } : {};
18940
+ if (!currentDelegates[delegateTjpAddr]) {
18941
+ throw new Error(`Delegate ${delegateTjpAddr} is not registered on this parent. (E: 81d9f8ab28c9b60e68994511a5825)`);
18942
+ }
18943
+ delete currentDelegates[delegateTjpAddr];
18944
+ const evolved = await this.sign({
18945
+ latestKeystone: parentKeystone,
18946
+ masterSecret,
18947
+ claim,
18948
+ poolId: POOL_ID_MANAGE,
18949
+ frameDetails,
18950
+ metaspace,
18951
+ space,
18952
+ dataToPatch: {
18953
+ delegates: currentDelegates
18954
+ }
18955
+ });
18956
+ return evolved;
18957
+ } catch (error) {
18958
+ console.error(`${lc2} ${extractErrorMsg(error)}`);
18959
+ throw error;
18960
+ } finally {
18961
+ if (logalot41) {
18962
+ console.log(`${lc2} complete.`);
18963
+ }
18964
+ }
18965
+ }
18966
+ /**
18967
+ * Gets the registration status of a delegate keystone from the parent's latest tip frame.
18968
+ */
18969
+ async getDelegateStatus({ parentTjpAddr, delegateTjpAddr, metaspace, space }) {
18970
+ const lc2 = `${this.lc}[${this.getDelegateStatus.name}]`;
18971
+ try {
18972
+ if (logalot41) {
18973
+ console.log(`${lc2} starting...`);
18974
+ }
18975
+ const { ib, gib } = getIbAndGib({ ibGibAddr: parentTjpAddr });
18976
+ const parentIbGib = { ib, gib };
18977
+ const resGetLatest = await getLatestAddrs({
18978
+ ibGibs: [parentIbGib],
18979
+ space
18980
+ });
18981
+ if (!resGetLatest.data?.latestAddrsMap) {
18982
+ return { registered: false };
18983
+ }
18984
+ const latestAddr = resGetLatest.data.latestAddrsMap[parentTjpAddr];
18985
+ if (!latestAddr) {
18986
+ return { registered: false };
18987
+ }
18988
+ const resGet = await metaspace.get({ addr: latestAddr, space });
18989
+ const latestParent = resGet.ibGibs?.at(0);
18990
+ if (!latestParent || !latestParent.data) {
18991
+ return { registered: false };
18992
+ }
18993
+ const delegates = latestParent.data.delegates || {};
18994
+ const delegateInfo = delegates[delegateTjpAddr];
18995
+ return {
18996
+ registered: !!delegateInfo,
18997
+ delegateInfo
18998
+ };
18999
+ } catch (error) {
19000
+ console.error(`${lc2} ${extractErrorMsg(error)}`);
19001
+ throw error;
19002
+ } finally {
19003
+ if (logalot41) {
19004
+ console.log(`${lc2} complete.`);
19005
+ }
19006
+ }
19007
+ }
18778
19008
  /**
18779
19009
  * Validates a genesis keystone.
18780
19010
  *
@@ -18870,7 +19100,7 @@ var KeystoneService_V1 = class _KeystoneService_V1 {
18870
19100
  verb: KEYSTONE_VERB_REVOKE,
18871
19101
  target: getIbGibAddr({ ibGib: latestKeystone })
18872
19102
  };
18873
- const idsToSolve = selectChallengeIds({
19103
+ const idsToSolve = await selectChallengeIds({
18874
19104
  pool,
18875
19105
  targetAddr: claim.target,
18876
19106
  requiredChallengeIds: []
@@ -18908,7 +19138,8 @@ var KeystoneService_V1 = class _KeystoneService_V1 {
18908
19138
  proofs: [proof],
18909
19139
  revocationInfo,
18910
19140
  frameDetails,
18911
- checkpointDetails
19141
+ checkpointDetails,
19142
+ ...prevData.delegates ? { delegates: prevData.delegates } : {}
18912
19143
  };
18913
19144
  const newKeystone = await evolvePersistAndRegisterKeystone({
18914
19145
  prevIbGib: latestKeystone,
@@ -18966,7 +19197,7 @@ var KeystoneService_V1 = class _KeystoneService_V1 {
18966
19197
  // Scope creates a cryptographic commitment to WHICH pools are being added
18967
19198
  scope: JSON.stringify({ add: newPools.map((p) => p.id) })
18968
19199
  };
18969
- const idsToSolve = selectChallengeIds({
19200
+ const idsToSolve = await selectChallengeIds({
18970
19201
  pool: adminPool,
18971
19202
  targetAddr: target
18972
19203
  });
@@ -19001,7 +19232,8 @@ var KeystoneService_V1 = class _KeystoneService_V1 {
19001
19232
  challengePools: finalPools,
19002
19233
  proofs: [proof],
19003
19234
  // The proof authorizes the structure change
19004
- checkpointDetails
19235
+ checkpointDetails,
19236
+ ...prevData.delegates ? { delegates: prevData.delegates } : {}
19005
19237
  };
19006
19238
  const newKeystone = await evolvePersistAndRegisterKeystone({
19007
19239
  prevIbGib: latestKeystone,
@@ -19080,7 +19312,7 @@ var SESSION_KEYSTONE_POLICY = {
19080
19312
  SIZE: 10,
19081
19313
  SELECT_SEQUENTIALLY: 2,
19082
19314
  SELECT_RANDOMLY: 2,
19083
- TARGET_BINDING_CHARS: 0,
19315
+ TARGET_BINDING_COUNT: 0,
19084
19316
  SERVER_DEMAND_COUNT: 3
19085
19317
  }
19086
19318
  };
@@ -19245,8 +19477,10 @@ async function validateAndRegisterEvolveKeystone({ domainAddr, keystoneIbGib, re
19245
19477
  if (intrinsicErrors && intrinsicErrors.length > 0) {
19246
19478
  throw new Error(`Intrinsic keystone validation failed: ${intrinsicErrors.join(", ")} (E: 38ea984585edc6ee88d2a698c7895826)`);
19247
19479
  }
19248
- const { tjpGib } = getGibInfo({ ibGibAddr: addr });
19249
- const expectedTjpGib = getGibInfo({ ibGibAddr: domainAddr }).punctiliarHash;
19480
+ const infoAddr = getGibInfo({ ibGibAddr: addr });
19481
+ const tjpGib = infoAddr.tjpGib ?? infoAddr.punctiliarHash;
19482
+ const infoDomain = getGibInfo({ ibGibAddr: domainAddr });
19483
+ const expectedTjpGib = infoDomain.tjpGib ?? infoDomain.punctiliarHash;
19250
19484
  if (tjpGib !== expectedTjpGib) {
19251
19485
  throw new Error(`Keystone tjpGib (${tjpGib}) does not match URL domainAddr tjpGib (${expectedTjpGib})`);
19252
19486
  }
@@ -19579,9 +19813,6 @@ var KeystoneGenesisHandler = class _KeystoneGenesisHandler extends ServeGibHandl
19579
19813
  await metaspace.put({ ibGibs: [keystoneIbGib], space });
19580
19814
  await metaspace.registerNewIbGib({ ibGib: keystoneIbGib, space });
19581
19815
  console.log(`${lc2} domain created and keystone persisted: ${addr}`);
19582
- if (logalot45) {
19583
- console.log(`${lc2} keystoneIbGib: ${pretty(keystoneIbGib)} (I: d598c6ff7a48997d585b84c19c464826)`);
19584
- }
19585
19816
  return this.ok({ success: true, addr }, 201);
19586
19817
  } catch (error) {
19587
19818
  const emsg = extractErrorMsg(error);
@@ -19820,64 +20051,82 @@ function performConnect(req, socket) {
19820
20051
  }
19821
20052
  var WebSocketFrameDecoder = class {
19822
20053
  buffer = Buffer.alloc(0);
20054
+ fragments = [];
19823
20055
  /** Adds new data chunk from TCP socket. */
19824
20056
  addChunk(chunk) {
19825
20057
  this.buffer = Buffer.concat([this.buffer, chunk]);
19826
20058
  }
19827
20059
  /**
19828
- * Parses the next complete text frame from the accumulated buffer.
20060
+ * Parses the next complete text message, assembling fragments if necessary.
19829
20061
  * Returns:
19830
- * - `string` if a complete text frame was decoded successfully (and consumes those bytes).
20062
+ * - `string` if a complete text message was assembled/decoded (and consumes those bytes).
19831
20063
  * - `null` if the buffer is incomplete or we need more data.
19832
20064
  * - `throws Error` on protocol errors or close frames.
19833
20065
  */
19834
20066
  nextFrame() {
19835
- if (this.buffer.length < 2) {
19836
- return null;
19837
- }
19838
- const opcode = this.buffer[0] & 15;
19839
- if (opcode === 8) {
19840
- throw new Error("Close frame received");
19841
- }
19842
- if (opcode !== 1) {
19843
- throw new Error(`Unsupported WebSocket opcode: 0x${opcode.toString(16)}`);
19844
- }
19845
- const secondByte = this.buffer[1];
19846
- const masked = (secondByte & 128) !== 0;
19847
- let payloadLen = secondByte & 127;
19848
- let headerLen = 2;
19849
- if (payloadLen === 126) {
19850
- if (this.buffer.length < 4) {
20067
+ while (true) {
20068
+ if (this.buffer.length < 2) {
19851
20069
  return null;
19852
20070
  }
19853
- payloadLen = this.buffer.readUInt16BE(2);
19854
- headerLen = 4;
19855
- } else if (payloadLen === 127) {
19856
- if (this.buffer.length < 10) {
20071
+ const firstByte = this.buffer[0];
20072
+ const fin = (firstByte & 128) !== 0;
20073
+ const opcode = firstByte & 15;
20074
+ if (opcode === 8) {
20075
+ throw new Error("Close frame received");
20076
+ }
20077
+ const isContinuation = opcode === 0;
20078
+ const isText = opcode === 1;
20079
+ if (!isContinuation && !isText) {
20080
+ throw new Error(`Unsupported WebSocket opcode: 0x${opcode.toString(16)}`);
20081
+ }
20082
+ if (isContinuation && this.fragments.length === 0) {
20083
+ throw new Error("Continuation frame received without an active message.");
20084
+ }
20085
+ if (isText && this.fragments.length > 0) {
20086
+ throw new Error("New message frame received before previous one completed.");
20087
+ }
20088
+ const secondByte = this.buffer[1];
20089
+ const masked = (secondByte & 128) !== 0;
20090
+ let payloadLen = secondByte & 127;
20091
+ let headerLen = 2;
20092
+ if (payloadLen === 126) {
20093
+ if (this.buffer.length < 4) {
20094
+ return null;
20095
+ }
20096
+ payloadLen = this.buffer.readUInt16BE(2);
20097
+ headerLen = 4;
20098
+ } else if (payloadLen === 127) {
20099
+ if (this.buffer.length < 10) {
20100
+ return null;
20101
+ }
20102
+ payloadLen = Number(this.buffer.readBigUInt64BE(2));
20103
+ headerLen = 10;
20104
+ }
20105
+ const maskLen = masked ? 4 : 0;
20106
+ const totalFrameLen = headerLen + maskLen + payloadLen;
20107
+ if (this.buffer.length < totalFrameLen) {
19857
20108
  return null;
19858
20109
  }
19859
- payloadLen = Number(this.buffer.readBigUInt64BE(2));
19860
- headerLen = 10;
19861
- }
19862
- const maskLen = masked ? 4 : 0;
19863
- const totalFrameLen = headerLen + maskLen + payloadLen;
19864
- if (this.buffer.length < totalFrameLen) {
19865
- return null;
19866
- }
19867
- const frameData = this.buffer.subarray(0, totalFrameLen);
19868
- this.buffer = this.buffer.subarray(totalFrameLen);
19869
- let payloadStart = headerLen;
19870
- if (masked) {
19871
- const mask = frameData.subarray(payloadStart, payloadStart + 4);
19872
- payloadStart += 4;
19873
- const payload = Buffer.from(frameData.subarray(payloadStart));
19874
- for (let i = 0; i < payload.length; i++) {
19875
- payload[i] ^= mask[i % 4];
19876
- }
19877
- return payload.toString("utf-8");
19878
- } else {
19879
- const payload = frameData.subarray(payloadStart);
19880
- return payload.toString("utf-8");
20110
+ const frameData = this.buffer.subarray(0, totalFrameLen);
20111
+ this.buffer = this.buffer.subarray(totalFrameLen);
20112
+ let payloadStart = headerLen;
20113
+ let payload;
20114
+ if (masked) {
20115
+ const mask = frameData.subarray(payloadStart, payloadStart + 4);
20116
+ payloadStart += 4;
20117
+ payload = Buffer.from(frameData.subarray(payloadStart));
20118
+ for (let i = 0; i < payload.length; i++) {
20119
+ payload[i] ^= mask[i % 4];
20120
+ }
20121
+ } else {
20122
+ payload = Buffer.from(frameData.subarray(payloadStart));
20123
+ }
20124
+ this.fragments.push(payload);
20125
+ if (fin) {
20126
+ const completePayload = Buffer.concat(this.fragments);
20127
+ this.fragments = [];
20128
+ return completePayload.toString("utf-8");
20129
+ }
19881
20130
  }
19882
20131
  }
19883
20132
  };
@@ -20063,12 +20312,10 @@ async function applyTransforms({ src, createdIbGibs_Running, dnaAddrsToApplyToSt
20063
20312
  const dnaAddrToApply = dnaAddrsToApplyToStoreVersion_MUTATES_IN_PLACE.splice(0, 1)[0];
20064
20313
  const dnaIbGib_IntermediateArray = allLocalIbGibs.filter((x) => getIbGibAddr({ ibGib: x }) === dnaAddrToApply);
20065
20314
  let dnaIbGib;
20066
- if (dnaIbGib_IntermediateArray.length === 1) {
20315
+ if (dnaIbGib_IntermediateArray.length > 0) {
20067
20316
  dnaIbGib = dnaIbGib_IntermediateArray[0];
20068
- } else if (dnaIbGib_IntermediateArray.length === 0) {
20069
- throw new Error(`dna ibGib not found in supplied allLocalIbGibs. dnaAddr: ${dnaAddrToApply}. (E: 7f56826852cf48a79ab8af16bf27e284)`);
20070
20317
  } else {
20071
- throw new Error(`More than one ibGib in allLocalIbGibs with the dna address of ${dnaAddrToApply}? (E: a726134f4cc14a4fb2ed2d39d22af17c)(UNEXPECTED)`);
20318
+ throw new Error(`dna ibGib not found in supplied allLocalIbGibs. dnaAddr: ${dnaAddrToApply}. (E: 7f56826852cf48a79ab8af16bf27e284)`);
20072
20319
  }
20073
20320
  let argTransform = clone(dnaIbGib.data);
20074
20321
  argTransform.src = src;
@@ -21666,6 +21913,7 @@ var SyncPeerWebSocketReceiver_V1 = class _SyncPeerWebSocketReceiver_V1 extends S
21666
21913
  pendingContext;
21667
21914
  pendingPayloadAddrs;
21668
21915
  pendingResponsePayloadsToSend = [];
21916
+ receivedPayloads = [];
21669
21917
  constructor(initialData, initialRel8ns) {
21670
21918
  super(initialData, initialRel8ns);
21671
21919
  }
@@ -21764,14 +22012,18 @@ var SyncPeerWebSocketReceiver_V1 = class _SyncPeerWebSocketReceiver_V1 extends S
21764
22012
  if (validationErrors.length > 0) {
21765
22013
  throw new Error(`controlIbGibs invalid intrinsically. validationErrors: ${validationErrors.join("|")} (E: 5ee1787d4cc53d3d2c55f3d4f2865226)`);
21766
22014
  }
21767
- if (this.pendingContext && this.pendingPayloadAddrs) {
22015
+ const pendingPayloadAddrs = this.pendingPayloadAddrs;
22016
+ if (this.pendingContext && pendingPayloadAddrs) {
21768
22017
  const addr = getIbGibAddr({ ibGib });
21769
- if (this.pendingPayloadAddrs.has(addr)) {
22018
+ if (pendingPayloadAddrs.has(addr)) {
21770
22019
  const tempSpace = await this.ensureLocalTempSpace();
21771
22020
  await putInSpace({ space: tempSpace, ibGibs: [ibGib] });
21772
- this.pendingPayloadAddrs.delete(addr);
21773
- if (this.pendingPayloadAddrs.size === 0) {
22021
+ this.receivedPayloads.push(ibGib);
22022
+ pendingPayloadAddrs.delete(addr);
22023
+ if (pendingPayloadAddrs.size === 0 && this.pendingPayloadAddrs !== void 0) {
21774
22024
  const context = this.pendingContext;
22025
+ context.payloadIbGibsDomain = this.receivedPayloads;
22026
+ this.receivedPayloads = [];
21775
22027
  this.pendingContext = void 0;
21776
22028
  this.pendingPayloadAddrs = void 0;
21777
22029
  await this.executeIncomingSyncRequestAndRespond({ context });
@@ -21805,6 +22057,7 @@ var SyncPeerWebSocketReceiver_V1 = class _SyncPeerWebSocketReceiver_V1 extends S
21805
22057
  if (expectedPayloadAddrs.length > 0) {
21806
22058
  this.pendingContext = context;
21807
22059
  this.pendingPayloadAddrs = new Set(expectedPayloadAddrs);
22060
+ this.receivedPayloads = [];
21808
22061
  this.socketWrapper.send(JSON.stringify({
21809
22062
  type: SyncWebSocketMsgType.sync_frame_authenticated,
21810
22063
  contextAddr: getIbGibAddr({ ibGib: context })
@@ -21821,6 +22074,8 @@ var SyncPeerWebSocketReceiver_V1 = class _SyncPeerWebSocketReceiver_V1 extends S
21821
22074
  ibGib
21822
22075
  }));
21823
22076
  }
22077
+ } else {
22078
+ throw new Error(`(UNEXPECTED) msg.type is ${msg.type}? not expected at this time (E: bad7c4fa1958b1976904cb884e624826)`);
21824
22079
  }
21825
22080
  } catch (error) {
21826
22081
  console.error(`${lc2} message frame handling failed: ${extractErrorMsg(error)}`);
@@ -22042,7 +22297,7 @@ async function appendToTimeline({ timeline, rel8nInfos, rel8nRemovalInfos, timel
22042
22297
  skipLock
22043
22298
  });
22044
22299
  }
22045
- metaspace.registerNewIbGib({ ibGib: newTimelineIbGib2, space });
22300
+ await metaspace.registerNewIbGib({ ibGib: newTimelineIbGib2, space });
22046
22301
  return newTimelineIbGib2;
22047
22302
  };
22048
22303
  const newTimelineIbGib = skipLock ? await fn() : await execInSpaceWithLocking({
@@ -22509,7 +22764,7 @@ var KeystoneConfigBuilderBase = class {
22509
22764
  replenish: this._replenish,
22510
22765
  selectSequentially: this._seq,
22511
22766
  selectRandomly: this._rand,
22512
- targetBindingChars: this._targetBinding
22767
+ targetBindingCount: this._targetBinding
22513
22768
  };
22514
22769
  }
22515
22770
  /**
@@ -22858,112 +23113,116 @@ var SyncSagaCoordinator = class _SyncSagaCoordinator {
22858
23113
  await subscription.unsubscribe();
22859
23114
  }
22860
23115
  }));
22861
- const requestCtx = await this.createSyncSagaContext({
22862
- sagaFrame: currentFrame,
22863
- /**
22864
- * init frame: empty
22865
- * ack frame: possible push offers
22866
- * delta frame: requested ibgibs or commit offer
22867
- * commit frame: empty
22868
- */
22869
- payloadIbGibsDomain: nextDomainIbGibs,
22870
- localSpace,
22871
- metaspace,
22872
- sessionIdentityAddr: peer.currentSessionIdentityAddr,
22873
- peer
22874
- });
22875
- if (logalotControlDomain) {
22876
- const domainAddrs = nextDomainIbGibs.map((p) => getIbGibAddr({ ibGib: p }));
22877
- console.log(`${lc2}${lcControlDomain} SENDER TRANSMIT -> peer.witness (I: 5b0081803698770f0bf64992220b312)`);
22878
- console.log(`${lc2}${lcControlDomain} Context: ${getIbGibAddr({ ibGib: requestCtx })}`);
22879
- console.log(`${lc2}${lcControlDomain} Frame: ${getIbGibAddr({ ibGib: currentFrame })}`);
22880
- console.log(`${lc2}${lcControlDomain} DOMAIN Payloads (${domainAddrs.length}): ${domainAddrs.join(", ") || "(none)"}`);
22881
- }
22882
- updates$.next(requestCtx);
22883
- peer.setOptionalOpts({ localSpace, localTempSpace: tempSpace });
22884
- const responseCtx = await peer.witness(requestCtx);
22885
- if (!responseCtx) {
22886
- if (currentFrame) {
22887
- const msg = await getSyncSagaMessageFromFrame({ frameIbGib: currentFrame, space: localSpace });
22888
- if (msg?.data?.stage === SyncStage.commit) {
22889
- if (logalot58) {
22890
- console.log(`${lc2} Sender sent Commit. Peer returned no response. Saga Complete. (I: 26f9ee073858ca78b8284753368b5226)`);
23116
+ try {
23117
+ const requestCtx = await this.createSyncSagaContext({
23118
+ sagaFrame: currentFrame,
23119
+ /**
23120
+ * init frame: empty
23121
+ * ack frame: possible push offers
23122
+ * delta frame: requested ibgibs or commit offer
23123
+ * commit frame: empty
23124
+ */
23125
+ payloadIbGibsDomain: nextDomainIbGibs,
23126
+ localSpace,
23127
+ metaspace,
23128
+ sessionIdentityAddr: peer.currentSessionIdentityAddr,
23129
+ peer
23130
+ });
23131
+ if (logalotControlDomain) {
23132
+ const domainAddrs = nextDomainIbGibs.map((p) => getIbGibAddr({ ibGib: p }));
23133
+ console.log(`${lc2}${lcControlDomain} SENDER TRANSMIT -> peer.witness (I: 5b0081803698770f0bf64992220b312)`);
23134
+ console.log(`${lc2}${lcControlDomain} Context: ${getIbGibAddr({ ibGib: requestCtx })}`);
23135
+ console.log(`${lc2}${lcControlDomain} Frame: ${getIbGibAddr({ ibGib: currentFrame })}`);
23136
+ console.log(`${lc2}${lcControlDomain} DOMAIN Payloads (${domainAddrs.length}): ${domainAddrs.join(", ") || "(none)"}`);
23137
+ }
23138
+ updates$.next(requestCtx);
23139
+ peer.setOptionalOpts({ localSpace, localTempSpace: tempSpace });
23140
+ const responseCtx = await peer.witness(requestCtx);
23141
+ if (!responseCtx) {
23142
+ if (currentFrame) {
23143
+ const msg = await getSyncSagaMessageFromFrame({ frameIbGib: currentFrame, space: localSpace });
23144
+ if (msg?.data?.stage === SyncStage.commit) {
23145
+ if (logalot58) {
23146
+ console.log(`${lc2} Sender sent Commit. Peer returned no response. Saga Complete. (I: 26f9ee073858ca78b8284753368b5226)`);
23147
+ }
23148
+ currentFrame = null;
23149
+ break;
23150
+ } else {
23151
+ throw new Error(`(UNEXPECTED) responseCtx falsy and currentFrame truthy, but we're not in the commit stage? This may be expected ultimately, but atow I am not seeing this as being expected. (E: cc34498962bd370deeff351fac939f26)`);
22891
23152
  }
22892
- currentFrame = null;
22893
- break;
22894
23153
  } else {
22895
- throw new Error(`(UNEXPECTED) responseCtx falsy and currentFrame truthy, but we're not in the commit stage? This may be expected ultimately, but atow I am not seeing this as being expected. (E: cc34498962bd370deeff351fac939f26)`);
23154
+ throw new Error(`(UNEXPECTED) no response and currentFrame falsy? (E: 8d1085ea2f28cfc3f9c922649864a826)`);
22896
23155
  }
22897
- } else {
22898
- throw new Error(`(UNEXPECTED) no response and currentFrame falsy? (E: 8d1085ea2f28cfc3f9c922649864a826)`);
22899
23156
  }
22900
- }
22901
- if (!responseCtx.data) {
22902
- throw new Error(`(UNEXPECTED) responseCtx.data falsy? (E: a969992bae53ab18a827ec58aec15826)`);
22903
- }
22904
- updates$.next(responseCtx);
22905
- const contextAndSagaFrameValidationErrors = await validateContextAndSagaFrame({ context: responseCtx });
22906
- if (contextAndSagaFrameValidationErrors.length > 0) {
22907
- throw new Error(`contextAndSagaFrameValidationErrors: ${contextAndSagaFrameValidationErrors} (E: 6eebe8e7fa437c00a8cde3ada3c66826)`);
22908
- }
22909
- const returnContextErrors = await this.validateReturnContext({
22910
- requestCtx,
22911
- responseCtx,
22912
- localSpace
22913
- });
22914
- if (returnContextErrors.length > 0) {
22915
- throw new Error(`validateReturnContext errors: ${returnContextErrors.join(", ")} (E: cb8a023b9d0728cceb09fa3da0bb8226)`);
22916
- }
22917
- const responsePayloadAddrsDomain = responseCtx.data[SYNC_SAGA_PAYLOAD_ADDRS_DOMAIN] || [];
22918
- if (responsePayloadAddrsDomain.length > 0) {
22919
- responseCtx.payloadIbGibsDomain = await this.pollForDomainPayloads({
22920
- expectedAddrs: responsePayloadAddrsDomain,
22921
- pollIntervalMs: 20,
22922
- // relatively arbitrary right now
22923
- domainPayloadsMap,
22924
- tempSpace
23157
+ if (!responseCtx.data) {
23158
+ throw new Error(`(UNEXPECTED) responseCtx.data falsy? (E: a969992bae53ab18a827ec58aec15826)`);
23159
+ }
23160
+ updates$.next(responseCtx);
23161
+ const contextAndSagaFrameValidationErrors = await validateContextAndSagaFrame({ context: responseCtx });
23162
+ if (contextAndSagaFrameValidationErrors.length > 0) {
23163
+ throw new Error(`contextAndSagaFrameValidationErrors: ${contextAndSagaFrameValidationErrors} (E: 6eebe8e7fa437c00a8cde3ada3c66826)`);
23164
+ }
23165
+ const returnContextErrors = await this.validateReturnContext({
23166
+ requestCtx,
23167
+ responseCtx,
23168
+ localSpace
22925
23169
  });
22926
- }
22927
- if (!responseCtx.sagaFrame) {
22928
- throw new Error(`(UNEXPECTED) responseCtx.sagaFrame falsy? the Peer should have set this when it got the response back from the remote. (E: e650adadf9a2063ec6764a1e31d3d826)`);
22929
- }
22930
- if (logalotControlDomain) {
22931
- const responseControlAddrs = responseCtx.data?.["@payloadAddrsControl"] || [];
22932
- console.log(`${lc2}${lcControlDomain} SENDER RECEIVED <- peer.witness (I: 3dc76a9744d89a4fc3e2f076c2be4826)`);
22933
- console.log(`${lc2}${lcControlDomain} Response Context: ${getIbGibAddr({ ibGib: responseCtx })}`);
22934
- console.log(`${lc2}${lcControlDomain} Response Saga Frame: ${getIbGibAddr({ ibGib: responseCtx.sagaFrame })}`);
22935
- console.log(`${lc2}${lcControlDomain} CONTROL Payloads (${responseControlAddrs.length}): ${responseControlAddrs.join(", ") || "(none)"}`);
22936
- console.log(`${lc2}${lcControlDomain} DOMAIN Payloads (${responsePayloadAddrsDomain.length}): ${responsePayloadAddrsDomain.join(", ") || "(none)"}`);
22937
- }
22938
- const contextResult = await this.handleResponseSagaContext({
22939
- sagaContext: responseCtx,
22940
- initDomainGraph,
22941
- mySpace: localSpace,
22942
- myTempSpace: tempSpace,
22943
- metaspace
22944
- });
22945
- if (!contextResult) {
22946
- console.error(`${lc2} NAG ERROR (DOES NOT THROW): does this ever hit now? (E: e04d02efc2a8e72a88b79f1f0f95ca26)`);
22947
- break;
22948
- } else if (contextResult.nextFrameInfo?.sagaComplete) {
22949
- if (logalot58) {
22950
- console.log(`${lc2} Handler returned null (Saga End). (I: 123bf9e7dca8886de72553a8d4f29e26)`);
23170
+ if (returnContextErrors.length > 0) {
23171
+ throw new Error(`validateReturnContext errors: ${returnContextErrors.join(", ")} (E: cb8a023b9d0728cceb09fa3da0bb8226)`);
23172
+ }
23173
+ const responsePayloadAddrsDomain = responseCtx.data[SYNC_SAGA_PAYLOAD_ADDRS_DOMAIN] || [];
23174
+ if (responsePayloadAddrsDomain.length > 0) {
23175
+ responseCtx.payloadIbGibsDomain = await this.pollForDomainPayloads({
23176
+ expectedAddrs: responsePayloadAddrsDomain,
23177
+ pollIntervalMs: 20,
23178
+ // relatively arbitrary right now
23179
+ domainPayloadsMap,
23180
+ tempSpace
23181
+ });
22951
23182
  }
22952
- break;
22953
- }
22954
- if (contextResult.errorMsg) {
22955
- throw new Error(`Couldn't handle response saga context. errorMsg: ${contextResult.errorMsg} (E: c948e81d513b2a0eb8b8afa878edc626)`);
22956
- } else if (!contextResult.nextFrameInfo) {
22957
- throw new Error(`(UNEXPECTED) contextResult.nextFrameInfo falsy? (E: c287a82e823e662a77923278e2418826)`);
22958
- }
22959
- const { frame, payloadIbGibsDomain } = contextResult.nextFrameInfo;
22960
- currentFrame = frame;
22961
- nextDomainIbGibs = [...payloadIbGibsDomain || []];
22962
- if (logalotControlDomain) {
22963
- const handlerDomainAddrs = nextDomainIbGibs.map((p) => getIbGibAddr({ ibGib: p }));
22964
- console.log(`${lc2}${lcControlDomain} HANDLER RESULT -> next iteration (I: 6b0d88c4c28857ccd812381515bd7826)`);
22965
- console.log(`${lc2}${lcControlDomain} Next Frame: ${getIbGibAddr({ ibGib: currentFrame })}`);
22966
- console.log(`${lc2}${lcControlDomain} DOMAIN for next (${handlerDomainAddrs.length}): ${handlerDomainAddrs.join(", ") || "(none)"}`);
23183
+ if (!responseCtx.sagaFrame) {
23184
+ throw new Error(`(UNEXPECTED) responseCtx.sagaFrame falsy? the Peer should have set this when it got the response back from the remote. (E: e650adadf9a2063ec6764a1e31d3d826)`);
23185
+ }
23186
+ if (logalotControlDomain) {
23187
+ const responseControlAddrs = responseCtx.data?.["@payloadAddrsControl"] || [];
23188
+ console.log(`${lc2}${lcControlDomain} SENDER RECEIVED <- peer.witness (I: 3dc76a9744d89a4fc3e2f076c2be4826)`);
23189
+ console.log(`${lc2}${lcControlDomain} Response Context: ${getIbGibAddr({ ibGib: responseCtx })}`);
23190
+ console.log(`${lc2}${lcControlDomain} Response Saga Frame: ${getIbGibAddr({ ibGib: responseCtx.sagaFrame })}`);
23191
+ console.log(`${lc2}${lcControlDomain} CONTROL Payloads (${responseControlAddrs.length}): ${responseControlAddrs.join(", ") || "(none)"}`);
23192
+ console.log(`${lc2}${lcControlDomain} DOMAIN Payloads (${responsePayloadAddrsDomain.length}): ${responsePayloadAddrsDomain.join(", ") || "(none)"}`);
23193
+ }
23194
+ const contextResult = await this.handleResponseSagaContext({
23195
+ sagaContext: responseCtx,
23196
+ initDomainGraph,
23197
+ mySpace: localSpace,
23198
+ myTempSpace: tempSpace,
23199
+ metaspace
23200
+ });
23201
+ if (!contextResult) {
23202
+ console.error(`${lc2} NAG ERROR (DOES NOT THROW): does this ever hit now? (E: e04d02efc2a8e72a88b79f1f0f95ca26)`);
23203
+ break;
23204
+ } else if (contextResult.nextFrameInfo?.sagaComplete) {
23205
+ if (logalot58) {
23206
+ console.log(`${lc2} Handler returned null (Saga End). (I: 123bf9e7dca8886de72553a8d4f29e26)`);
23207
+ }
23208
+ break;
23209
+ }
23210
+ if (contextResult.errorMsg) {
23211
+ throw new Error(`Couldn't handle response saga context. errorMsg: ${contextResult.errorMsg} (E: c948e81d513b2a0eb8b8afa878edc626)`);
23212
+ } else if (!contextResult.nextFrameInfo) {
23213
+ throw new Error(`(UNEXPECTED) contextResult.nextFrameInfo falsy? (E: c287a82e823e662a77923278e2418826)`);
23214
+ }
23215
+ const { frame, payloadIbGibsDomain } = contextResult.nextFrameInfo;
23216
+ currentFrame = frame;
23217
+ nextDomainIbGibs = [...payloadIbGibsDomain || []];
23218
+ if (logalotControlDomain) {
23219
+ const handlerDomainAddrs = nextDomainIbGibs.map((p) => getIbGibAddr({ ibGib: p }));
23220
+ console.log(`${lc2}${lcControlDomain} HANDLER RESULT -> next iteration (I: 6b0d88c4c28857ccd812381515bd7826)`);
23221
+ console.log(`${lc2}${lcControlDomain} Next Frame: ${getIbGibAddr({ ibGib: currentFrame })}`);
23222
+ console.log(`${lc2}${lcControlDomain} DOMAIN for next (${handlerDomainAddrs.length}): ${handlerDomainAddrs.join(", ") || "(none)"}`);
23223
+ }
23224
+ } finally {
23225
+ await subscription.unsubscribe();
22967
23226
  }
22968
23227
  }
22969
23228
  }
@@ -23233,7 +23492,8 @@ var SyncSagaCoordinator = class _SyncSagaCoordinator {
23233
23492
  console.log(`${lc2} starting... (I: 26dce86bfca572939885798802d6e926)`);
23234
23493
  }
23235
23494
  let resultDomainPayloads = [];
23236
- let pending = [...expectedAddrs];
23495
+ const uniqueExpectedAddrs = [...new Set(expectedAddrs)];
23496
+ let pending = [...uniqueExpectedAddrs];
23237
23497
  const start = Date.now();
23238
23498
  const timeoutMs = 5 * 60 * 1e3;
23239
23499
  while (pending.length > 0) {
@@ -23259,8 +23519,8 @@ var SyncSagaCoordinator = class _SyncSagaCoordinator {
23259
23519
  await delay(pollIntervalMs);
23260
23520
  }
23261
23521
  }
23262
- if (expectedAddrs.length !== resultDomainPayloads.length) {
23263
- throw new Error(`(UNEXPECTED) expectedAddrs.length !== resultDomainPayloads.length? at this point, we expect all of the payload ibgibs to have been received. (E: 03749a7478c4b8b28bfc86951887a826)`);
23522
+ if (uniqueExpectedAddrs.length !== resultDomainPayloads.length) {
23523
+ throw new Error(`(UNEXPECTED) uniqueExpectedAddrs.length !== resultDomainPayloads.length? at this point, we expect all of the payload ibgibs to have been received. (E: 03749a7478c4b8b28bfc86951887a826)`);
23264
23524
  }
23265
23525
  return resultDomainPayloads;
23266
23526
  } catch (error) {
@@ -23710,11 +23970,11 @@ var SyncSagaCoordinator = class _SyncSagaCoordinator {
23710
23970
  throw new Error(`${lc2} Peer reported terminal conflicts. (E: 23a0096ee05a2ccfa89334e8f156b426)`);
23711
23971
  }
23712
23972
  const outgoingPayloadIbGibsDomain_conflicts = [];
23973
+ const activeConflicts = [];
23713
23974
  if (conflicts.length > 0) {
23714
23975
  if (logalot58) {
23715
23976
  console.log(`${lc2} [CONFLICT DEBUG] Processing ${conflicts.length} non-terminal conflicts`);
23716
23977
  }
23717
- proposeCommit = false;
23718
23978
  for (const conflict of conflicts) {
23719
23979
  const {
23720
23980
  receiverTipAddr,
@@ -23773,13 +24033,26 @@ var SyncSagaCoordinator = class _SyncSagaCoordinator {
23773
24033
  const deltaPayloadIbGib = deltaGraph[deltaPayloadAddr];
23774
24034
  outgoingPayloadIbGibsDomain_conflicts.push(deltaPayloadIbGib);
23775
24035
  });
23776
- if (conflict.accretivePayloadAddrs) {
23777
- throw new Error(`(UNEXPECTED) conflict.accretivePayloadAddrs already truthy? This is expected to be falsy at this point in the sync. (E: 6b6b1846779867849cbde208f4374326)`);
24036
+ const isFastForward = latestCommonFrameAddr === receiverTipAddr;
24037
+ if (isFastForward) {
24038
+ if (logalot58) {
24039
+ console.log(`${lc2} [CONFLICT DEBUG] TJP ${tjpAddr}: Fast-forward detected (receiver behind sender). Excluding from active conflicts and sending delta payload.`);
24040
+ }
24041
+ } else {
24042
+ if (conflict.accretivePayloadAddrs) {
24043
+ throw new Error(`(UNEXPECTED) conflict.accretivePayloadAddrs already truthy? This is expected to be falsy at this point in the sync. (E: 6b6b1846779867849cbde208f4374326)`);
24044
+ }
24045
+ conflict.accretivePayloadAddrs = accretivePayloadAddrs;
24046
+ activeConflicts.push(conflict);
23778
24047
  }
23779
- conflict.accretivePayloadAddrs = accretivePayloadAddrs;
24048
+ }
24049
+ if (activeConflicts.length > 0) {
24050
+ proposeCommit = false;
24051
+ } else {
24052
+ proposeCommit = true;
23780
24053
  }
23781
24054
  if (logalot58) {
23782
- console.log(`${lc2} [CONFLICT DEBUG] Finished processing ${conflicts.length} conflicts. outgoingPayloadIbGibsDomain_conflicts.length (total outgoing payload ibgibs for ALL conflicts): ${outgoingPayloadIbGibsDomain_conflicts.length}`);
24055
+ console.log(`${lc2} [CONFLICT DEBUG] Finished processing ${conflicts.length} conflicts. Active (divergent) conflicts: ${activeConflicts.length}. outgoingPayloadIbGibsDomain_conflicts.length (total outgoing payload ibgibs for ALL conflicts): ${outgoingPayloadIbGibsDomain_conflicts.length}`);
23783
24056
  }
23784
24057
  } else {
23785
24058
  if (logalot58) {
@@ -23808,7 +24081,7 @@ var SyncSagaCoordinator = class _SyncSagaCoordinator {
23808
24081
  /**
23809
24082
  * these are the modified conflicts with additional information.
23810
24083
  */
23811
- conflicts,
24084
+ conflicts: activeConflicts,
23812
24085
  /**
23813
24086
  * we're sending these domain ibgibs as payload (they were
23814
24087
  * requested by receiver and/or they are being sent as delta
@@ -24738,9 +25011,26 @@ var SyncUpgradeHandlerBase = class _SyncUpgradeHandlerBase extends ServeGibHandl
24738
25011
  sagaId: void 0
24739
25012
  // Resolved dynamically inside runtime turns
24740
25013
  });
25014
+ socket.on("error", (err) => {
25015
+ if (logalot59) {
25016
+ console.warn(`[SyncUpgradeHandlerBase] Socket error: ${extractErrorMsg(err)}`);
25017
+ }
25018
+ try {
25019
+ socket.destroy();
25020
+ } catch (e) {
25021
+ }
25022
+ });
24741
25023
  const socketWrapper = {
24742
25024
  send(data) {
24743
- socket.write(encodeTextFrame(data));
25025
+ if (socket.writable) {
25026
+ try {
25027
+ socket.write(encodeTextFrame(data));
25028
+ } catch (e) {
25029
+ if (logalot59) {
25030
+ console.warn(`[SyncUpgradeHandlerBase] failed to write message frame: ${extractErrorMsg(e)}`);
25031
+ }
25032
+ }
25033
+ }
24744
25034
  },
24745
25035
  onMessage(callback) {
24746
25036
  const decoder = new WebSocketFrameDecoder();
@@ -24756,7 +25046,12 @@ var SyncUpgradeHandlerBase = class _SyncUpgradeHandlerBase extends ServeGibHandl
24756
25046
  if (logalot59) {
24757
25047
  console.warn(`[SyncUpgradeHandlerBase] closing connection due to decoder error: ${extractErrorMsg(error)}`);
24758
25048
  }
24759
- socket.write(encodeCloseFrame());
25049
+ if (socket.writable) {
25050
+ try {
25051
+ socket.write(encodeCloseFrame());
25052
+ } catch (e) {
25053
+ }
25054
+ }
24760
25055
  socket.end();
24761
25056
  }
24762
25057
  });
@@ -24765,7 +25060,12 @@ var SyncUpgradeHandlerBase = class _SyncUpgradeHandlerBase extends ServeGibHandl
24765
25060
  socket.on("close", callback);
24766
25061
  },
24767
25062
  close() {
24768
- socket.write(encodeCloseFrame());
25063
+ if (socket.writable) {
25064
+ try {
25065
+ socket.write(encodeCloseFrame());
25066
+ } catch (e) {
25067
+ }
25068
+ }
24769
25069
  socket.end();
24770
25070
  }
24771
25071
  };