@pkcprotocol/pkc-js 0.0.98 → 0.0.100

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 (82) hide show
  1. package/README.md +1 -1
  2. package/dist/browser/clients/base-client-manager.js +26 -4
  3. package/dist/browser/clients/base-client-manager.js.map +1 -1
  4. package/dist/browser/community/community-client-manager.d.ts +5 -0
  5. package/dist/browser/community/community-client-manager.js +134 -23
  6. package/dist/browser/community/community-client-manager.js.map +1 -1
  7. package/dist/browser/errors.d.ts +2 -0
  8. package/dist/browser/errors.js +2 -0
  9. package/dist/browser/errors.js.map +1 -1
  10. package/dist/browser/generated-version.d.ts +1 -1
  11. package/dist/browser/generated-version.js +1 -1
  12. package/dist/browser/generated-version.js.map +1 -1
  13. package/dist/browser/helia/helia-for-pkc.js +47 -1
  14. package/dist/browser/helia/helia-for-pkc.js.map +1 -1
  15. package/dist/browser/helia/util.js +4 -3
  16. package/dist/browser/helia/util.js.map +1 -1
  17. package/dist/browser/pkc-error.d.ts +1 -1
  18. package/dist/browser/publications/publication.d.ts +1 -0
  19. package/dist/browser/publications/publication.js +76 -19
  20. package/dist/browser/publications/publication.js.map +1 -1
  21. package/dist/browser/runtime/node/community/local-community/challenges.js +131 -17
  22. package/dist/browser/runtime/node/community/local-community/challenges.js.map +1 -1
  23. package/dist/browser/runtime/node/community/local-community/defaults.d.ts +1 -0
  24. package/dist/browser/runtime/node/community/local-community/defaults.js +5 -0
  25. package/dist/browser/runtime/node/community/local-community/defaults.js.map +1 -1
  26. package/dist/browser/runtime/node/community/local-community/ipns-publishing.js +7 -1
  27. package/dist/browser/runtime/node/community/local-community/ipns-publishing.js.map +1 -1
  28. package/dist/browser/runtime/node/community/local-community/lifecycle.js +5 -4
  29. package/dist/browser/runtime/node/community/local-community/lifecycle.js.map +1 -1
  30. package/dist/browser/runtime/node/community/local-community.d.ts +2 -0
  31. package/dist/browser/runtime/node/community/local-community.js +8 -1
  32. package/dist/browser/runtime/node/community/local-community.js.map +1 -1
  33. package/dist/browser/test/mock-ipfs-client.js +6 -0
  34. package/dist/browser/test/mock-ipfs-client.js.map +1 -1
  35. package/dist/browser/test/test-util.js +1 -1
  36. package/dist/browser/test/test-util.js.map +1 -1
  37. package/dist/bundled/bundle-manifest.json +25 -25
  38. package/dist/bundled/chunks/{helia-for-pkc-CHv28C49.js → helia-for-pkc-Cq6VWePV.js} +28 -2
  39. package/dist/bundled/chunks/{local-community-Dslc-qve.js → local-community-CcHbWkoT.js} +1 -1
  40. package/dist/bundled/chunks/{local-community-QnF2Xg4Q.js → local-community-ph-pl_pg.js} +92 -20
  41. package/dist/bundled/chunks/{node-B1frjYEE.js → node-2PkhAerB.js} +56 -27
  42. package/dist/bundled/chunks/{rpc-local-community-BUicljzl.js → rpc-local-community-BK1FjUk2.js} +68 -20
  43. package/dist/bundled/chunks/{schema-KFikvQok.js → schema-CYR820MD.js} +3 -1
  44. package/dist/bundled/chunks/{util-b2CRgd4P.js → util-gAVw5PrE.js} +2 -2
  45. package/dist/bundled/index.js +1 -1
  46. package/dist/bundled/rpc/src/index.js +4 -4
  47. package/dist/node/clients/base-client-manager.js +26 -4
  48. package/dist/node/clients/base-client-manager.js.map +1 -1
  49. package/dist/node/community/community-client-manager.d.ts +5 -0
  50. package/dist/node/community/community-client-manager.js +134 -23
  51. package/dist/node/community/community-client-manager.js.map +1 -1
  52. package/dist/node/errors.d.ts +2 -0
  53. package/dist/node/errors.js +2 -0
  54. package/dist/node/errors.js.map +1 -1
  55. package/dist/node/generated-version.d.ts +1 -1
  56. package/dist/node/generated-version.js +1 -1
  57. package/dist/node/generated-version.js.map +1 -1
  58. package/dist/node/helia/helia-for-pkc.js +47 -1
  59. package/dist/node/helia/helia-for-pkc.js.map +1 -1
  60. package/dist/node/helia/util.js +4 -3
  61. package/dist/node/helia/util.js.map +1 -1
  62. package/dist/node/pkc-error.d.ts +1 -1
  63. package/dist/node/publications/publication.d.ts +1 -0
  64. package/dist/node/publications/publication.js +76 -19
  65. package/dist/node/publications/publication.js.map +1 -1
  66. package/dist/node/runtime/node/community/local-community/challenges.js +131 -17
  67. package/dist/node/runtime/node/community/local-community/challenges.js.map +1 -1
  68. package/dist/node/runtime/node/community/local-community/defaults.d.ts +1 -0
  69. package/dist/node/runtime/node/community/local-community/defaults.js +5 -0
  70. package/dist/node/runtime/node/community/local-community/defaults.js.map +1 -1
  71. package/dist/node/runtime/node/community/local-community/ipns-publishing.js +7 -1
  72. package/dist/node/runtime/node/community/local-community/ipns-publishing.js.map +1 -1
  73. package/dist/node/runtime/node/community/local-community/lifecycle.js +5 -4
  74. package/dist/node/runtime/node/community/local-community/lifecycle.js.map +1 -1
  75. package/dist/node/runtime/node/community/local-community.d.ts +2 -0
  76. package/dist/node/runtime/node/community/local-community.js +8 -1
  77. package/dist/node/runtime/node/community/local-community.js.map +1 -1
  78. package/dist/node/test/mock-ipfs-client.js +6 -0
  79. package/dist/node/test/mock-ipfs-client.js.map +1 -1
  80. package/dist/node/test/test-util.js +1 -1
  81. package/dist/node/test/test-util.js.map +1 -1
  82. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  import { i as __toESM, r as __require, t as __commonJSMin } from "./chunk-2rV9d50f.js";
2
- import { A as doesDomainAddressHaveCapitalLetter, B as ipnsNameToIpnsOverPubsubTopic, Bn as CommentEditPubsubMessagePublicationSchema, Bt as require_sha256, C as calculateStringSizeSameAsIpfsAddCidV0, Cn as CommentIpfsSchema, Cr as messages, Ct as writeKuboFilesWithTimeout, Dt as findUpdatingCommunity, E as contentContainsMarkdownVideos, F as getErrorCodeFromMessage, Hn as CommentEditReservedFields, I as getIpnsRecordInLocalKuboNode, It as trackUpdatingCommunity, J as isLinkOfImage, Jn as AuthorReservedFields, K as isLinkOfAnimatedImage, Lr as ZodError, Lt as untrackStartedCommunity, N as genToArray, Nn as CommentUpdateTableRowSchema, Nt as syncCommunityRegistryEntry, P as getEquivalentCommunityAddresses, Pn as CommentsTableRowSchema, Pt as trackStartedCommunity, Qt as CommentModerationsTableRowSchema, R as hideClassPrivateProps, S as calculateIpfsCidV0, Sr as PKCError, T as contentContainsMarkdownImages, Tt as findStartedCommunity, Vn as CommentEditPubsubMessagePublicationWithFlexibleAuthorSchema, Wn as CommentEditsTableRowSchema, Wt as CommunityEditPublicationPubsubReservedFields, X as isLinkOfVideo, Xt as CommentModerationReservedFields, Y as isLinkOfMedia, Yt as CommentModerationPubsubMessagePublicationSchema, Z as isLinkValid, _n as VotePubsubMessagePublicationSchema, a as ChallengeRequestMessageSchema, ar as t$6, b as areEquivalentCommunityAddresses, bn as VoteTablesRowSchema, cn as CommunitySignedPropertyNames, d as DecryptedChallengeRequestSchema, et as isStringDomain, gr as require_retry, ht as retryKuboIpfsAddAndProvide, ir as t$7, jn as CommentUpdateSchema, k as derivePublicationFromChallengeRequest, lr as r$1, lt as removeNullUndefinedValues, mr as t$3, nr as t$8, or as e, ot as removeBlocksFromKuboNode, pr as t$5, pt as retryKuboBlockPutPinAndProvidePubsubTopic, q as isLinkOfAudio, r as ChallengeMessageSchema, rr as n$3, s as ChallengeVerificationMessageSchema, sn as CommunityIpfsSchema, sr as e$1, st as removeMfsFilesSafely, t as ChallengeAnswerMessageSchema, tr as n$4, tt as pubsubTopicToDhtKey, u as DecryptedChallengeRequestPublicationSchema, ur as t$4, vn as VotePubsubReservedFields, vt as sleepUntilTimeoutOrAbort, w as contentContainsMarkdownAudio, wn as CommentPubsubMessageReservedFields, wr as logger_default, wt as findCommunityInRegistry, xt as timestamp, y as version_default, yt as statMfsPathSafely, zt as untrackUpdatingCommunity } from "./schema-KFikvQok.js";
3
- import { $t as getPublicKeyFromPrivateKey, A as deriveDbPosts, At as verifyCommentIpfs, Bt as getAuthorNameFromWire, C as BackupAbortError, Cn as parseDecryptedChallengeAnswerWithPKCErrorIfItFails, Dt as verifyChallengeRequest, E as calculateInlineRepliesBudget, En as parseJsonWithPKCErrorIfFails, F as importSignerIntoKuboNode, Ft as verifyCommunityEdit, G as REPLY_REPLIES_SORT_TYPES, Gt as getCommunityPublicKeyFromWire, It as verifyVote, Jt as encode, K as TIMEFRAMES_TO_SECONDS, Lt as buildRuntimeAuthor, M as getDefaultCommunityDbConfig, Mt as verifyCommentPubsubMessage, Nt as verifyCommentUpdate, O as deleteOldCommunityInWindows, P as getThumbnailPropsOfLink, Pt as verifyCommunity, Qt as getPKCAddressFromPublicKeySync, R as moveCommunityDbToDeletedDirectory, Rt as cleanWireAuthor, S as encryptEd25519AesGcmPublicKeyBuffer, St as signCommunity, T as calculateExpectedSignatureSize, Tt as verifyChallengeAnswer, U as POSTS_SORT_TYPES, W as POST_REPLIES_SORT_TYPES, Wt as getCommunityNameFromWire, Xt as getIpfsKeyFromPrivateKey, Z as iterateOverPageCidsToFindAllCids, Zt as getPKCAddressFromPublicKey, _t as signComment, a as loadChallengeFileFromCommunityChallengeSettings, ar as t$11, b as decryptEd25519AesGcmPublicKeyBuffer, bt as signCommentUpdate, c as LRUCache, d as KeyvBetterSqlite3, dn as parseCommunityEditOptionsSchemaWithPKCErrorIfItFails, dr as STORAGE_KEYS, en as createSchemaRowParser, er as crosspostChainDepthUpTo, fr as require_lodash_merge, ft as cleanUpBeforePublishing, g as SignerWithPublicKeyAddress, gt as signChallengeVerification, i as getCommunityChallengeFromCommunityChallengeSettings, ir as r$2, j as deriveDbReplies, jt as verifyCommentModeration, k as deriveCommentIpfsFromCommentTableRow, kt as verifyCommentEdit, lr as t$10, lt as pLimit, mt as signChallengeMessage, nr as t$9, o as pkcJsChallenges, or as n$6, pn as parseCommunityIpfsSchemaPassthroughWithPKCErrorIfItFails, qt as decode, r as getChallengeVerification, rr as t$12, s as TrackedInstanceRegistry, sr as n$5, t as RpcLocalCommunity, tr as effectiveMaxCrosspostDepth, u as MAX_FILE_SIZE_BYTES_FOR_COMMUNITY_IPFS, ur as stringify, vt as signCommentEdit, w as backupCommunityDb, xt as signCommentUpdateForChallengeVerification, z as resolveDbPostsCidRefs } from "./rpc-local-community-BUicljzl.js";
2
+ import { A as doesDomainAddressHaveCapitalLetter, B as ipnsNameToIpnsOverPubsubTopic, Bn as CommentEditPubsubMessagePublicationSchema, Bt as require_sha256, C as calculateStringSizeSameAsIpfsAddCidV0, Cn as CommentIpfsSchema, Cr as messages, Ct as writeKuboFilesWithTimeout, Dt as findUpdatingCommunity, E as contentContainsMarkdownVideos, F as getErrorCodeFromMessage, Hn as CommentEditReservedFields, Ht as pTimeout, I as getIpnsRecordInLocalKuboNode, It as trackUpdatingCommunity, J as isLinkOfImage, Jn as AuthorReservedFields, K as isLinkOfAnimatedImage, Lr as ZodError, Lt as untrackStartedCommunity, N as genToArray, Nn as CommentUpdateTableRowSchema, Nt as syncCommunityRegistryEntry, P as getEquivalentCommunityAddresses, Pn as CommentsTableRowSchema, Pt as trackStartedCommunity, Qt as CommentModerationsTableRowSchema, R as hideClassPrivateProps, S as calculateIpfsCidV0, Sr as PKCError, T as contentContainsMarkdownImages, Tt as findStartedCommunity, Vn as CommentEditPubsubMessagePublicationWithFlexibleAuthorSchema, Wn as CommentEditsTableRowSchema, Wt as CommunityEditPublicationPubsubReservedFields, X as isLinkOfVideo, Xt as CommentModerationReservedFields, Y as isLinkOfMedia, Yt as CommentModerationPubsubMessagePublicationSchema, Z as isLinkValid, _n as VotePubsubMessagePublicationSchema, a as ChallengeRequestMessageSchema, ar as t$6, b as areEquivalentCommunityAddresses, bn as VoteTablesRowSchema, cn as CommunitySignedPropertyNames, d as DecryptedChallengeRequestSchema, et as isStringDomain, gr as require_retry, ht as retryKuboIpfsAddAndProvide, ir as t$7, jn as CommentUpdateSchema, k as derivePublicationFromChallengeRequest, lr as r$1, lt as removeNullUndefinedValues, mr as t$3, nr as t$8, or as e, ot as removeBlocksFromKuboNode, pr as t$5, pt as retryKuboBlockPutPinAndProvidePubsubTopic, q as isLinkOfAudio, r as ChallengeMessageSchema, rr as n$3, s as ChallengeVerificationMessageSchema, sn as CommunityIpfsSchema, sr as e$1, st as removeMfsFilesSafely, t as ChallengeAnswerMessageSchema, tr as n$4, tt as pubsubTopicToDhtKey, u as DecryptedChallengeRequestPublicationSchema, ur as t$4, vn as VotePubsubReservedFields, vt as sleepUntilTimeoutOrAbort, w as contentContainsMarkdownAudio, wn as CommentPubsubMessageReservedFields, wr as logger_default, wt as findCommunityInRegistry, xt as timestamp, y as version_default, yt as statMfsPathSafely, zt as untrackUpdatingCommunity } from "./schema-CYR820MD.js";
3
+ import { $t as getPublicKeyFromPrivateKey, A as deriveDbPosts, At as verifyCommentIpfs, Bt as getAuthorNameFromWire, C as BackupAbortError, Cn as parseDecryptedChallengeAnswerWithPKCErrorIfItFails, Dt as verifyChallengeRequest, E as calculateInlineRepliesBudget, En as parseJsonWithPKCErrorIfFails, F as importSignerIntoKuboNode, Ft as verifyCommunityEdit, G as REPLY_REPLIES_SORT_TYPES, Gt as getCommunityPublicKeyFromWire, It as verifyVote, Jt as encode, K as TIMEFRAMES_TO_SECONDS, Lt as buildRuntimeAuthor, M as getDefaultCommunityDbConfig, Mt as verifyCommentPubsubMessage, Nt as verifyCommentUpdate, O as deleteOldCommunityInWindows, P as getThumbnailPropsOfLink, Pt as verifyCommunity, Qt as getPKCAddressFromPublicKeySync, R as moveCommunityDbToDeletedDirectory, Rt as cleanWireAuthor, S as encryptEd25519AesGcmPublicKeyBuffer, St as signCommunity, T as calculateExpectedSignatureSize, Tt as verifyChallengeAnswer, U as POSTS_SORT_TYPES, W as POST_REPLIES_SORT_TYPES, Wt as getCommunityNameFromWire, Xt as getIpfsKeyFromPrivateKey, Z as iterateOverPageCidsToFindAllCids, Zt as getPKCAddressFromPublicKey, _t as signComment, a as loadChallengeFileFromCommunityChallengeSettings, ar as t$11, b as decryptEd25519AesGcmPublicKeyBuffer, bt as signCommentUpdate, c as LRUCache, d as KeyvBetterSqlite3, dn as parseCommunityEditOptionsSchemaWithPKCErrorIfItFails, dr as STORAGE_KEYS, en as createSchemaRowParser, er as crosspostChainDepthUpTo, fr as require_lodash_merge, ft as cleanUpBeforePublishing, g as SignerWithPublicKeyAddress, gt as signChallengeVerification, i as getCommunityChallengeFromCommunityChallengeSettings, ir as r$2, j as deriveDbReplies, jt as verifyCommentModeration, k as deriveCommentIpfsFromCommentTableRow, kt as verifyCommentEdit, lr as t$10, lt as pLimit, mt as signChallengeMessage, nr as t$9, o as pkcJsChallenges, or as n$6, pn as parseCommunityIpfsSchemaPassthroughWithPKCErrorIfItFails, qt as decode, r as getChallengeVerification, rr as t$12, s as TrackedInstanceRegistry, sr as n$5, t as RpcLocalCommunity, tr as effectiveMaxCrosspostDepth, u as MAX_FILE_SIZE_BYTES_FOR_COMMUNITY_IPFS, ur as stringify, vt as signCommentEdit, w as backupCommunityDb, xt as signCommentUpdateForChallengeVerification, z as resolveDbPostsCidRefs } from "./rpc-local-community-BK1FjUk2.js";
4
4
  import { randomFillSync, randomUUID } from "node:crypto";
5
5
  import { fileURLToPath, pathToFileURL } from "node:url";
6
6
  import Path from "node:path";
@@ -678,6 +678,7 @@ const DUPLICATE_PUBLICATION_ERRORS = new Set([
678
678
  messages.ERR_DUPLICATE_COMMENT_EDIT,
679
679
  messages.ERR_DUPLICATE_COMMENT_MODERATION
680
680
  ]);
681
+ const CHALLENGE_EXCHANGE_TTL_MS = 600 * 1e3;
681
682
  const defaultChallengeQuestionText = "What is the answer to this community's challenge? (check community.settings.challenges to see the answer, or set your own challenge)";
682
683
  function generateDefaultChallenges(answer) {
683
684
  return [{
@@ -784,6 +785,7 @@ async function publishIdempotentDuplicateVerification(community, request, challe
784
785
  author: { community: authorCommunity },
785
786
  cid: existingComment.cid,
786
787
  protocolVersion: version_default.PROTOCOL_VERSION,
788
+ pendingApproval: existingComment.pendingApproval ? true : void 0,
787
789
  ...commentNumberPostNumber
788
790
  });
789
791
  const commentUpdate = {
@@ -794,7 +796,7 @@ async function publishIdempotentDuplicateVerification(community, request, challe
794
796
  })
795
797
  };
796
798
  toEncryptDecrypted = {
797
- comment: CommentIpfsSchema.strip().parse(existingComment),
799
+ comment: deriveCommentIpfsFromCommentTableRow(existingComment),
798
800
  commentUpdate
799
801
  };
800
802
  encrypted = await encryptEd25519AesGcmPublicKeyBuffer(stringify(toEncryptDecrypted), community.signer.privateKey, request.signature.publicKey);
@@ -871,7 +873,12 @@ async function publishChallengeVerification(community, challengeResult, request,
871
873
  try {
872
874
  toEncrypt = await storePublicationAndEncryptForChallengeVerification(community, request, pendingApproval, challengeAggregate);
873
875
  } catch (e) {
874
- failureReason = e.message;
876
+ const error = e;
877
+ if (DUPLICATE_PUBLICATION_ERRORS.has(error.message)) {
878
+ log("Publication was stored by an overlapping exchange, answering idempotently", request.challengeRequestId.toString());
879
+ return publishIdempotentDuplicateVerification(community, request, request.challengeRequestId, error.message);
880
+ }
881
+ failureReason = error.message;
875
882
  log.error("Failed to store store Publication And Encrypt For ChallengeVerification", e);
876
883
  }
877
884
  const toSignMsg = cleanUpBeforePublishing({
@@ -968,7 +975,7 @@ function buildRuntimeChallengeRequest({ request, authorCommunity }) {
968
975
  });
969
976
  return runtimeRequest;
970
977
  }
971
- async function parseAndValidateChallengeRequest(community, request, log) {
978
+ async function parseChallengeRequest(community, request, log) {
972
979
  const decryptedRequest = await parseChallengeRequestPublicationOrRespondWithFailure(community, request, await decryptOrRespondWithFailure(community, request));
973
980
  const publicationFieldNames = t$6(DecryptedChallengeRequestPublicationSchema.shape);
974
981
  let publication;
@@ -1014,18 +1021,34 @@ async function parseAndValidateChallengeRequest(community, request, log) {
1014
1021
  }
1015
1022
  log.trace("Received a valid challenge request", decryptedRequestWithCommunityAuthor);
1016
1023
  community.emit("challengerequest", decryptedRequestWithCommunityAuthor);
1024
+ return {
1025
+ decryptedRequestMsg,
1026
+ publication
1027
+ };
1028
+ }
1029
+ async function validatePublicationOrRespondWithFailure({ community, decryptedRequestMsg, publication, countDuplicateAttempt }) {
1030
+ const authorSignerAddress = await getPKCAddressFromPublicKey(publication.signature.publicKey);
1031
+ const authorDomain = getAuthorNameFromWire(publication.author);
1032
+ const communityAuthor = community._dbHandler.queryCommunityAuthor(authorSignerAddress, authorDomain);
1033
+ const decryptedRequestWithCommunityAuthor = buildRuntimeChallengeRequest({
1034
+ request: decryptedRequestMsg,
1035
+ authorCommunity: communityAuthor
1036
+ });
1017
1037
  const publicationInvalidityReason = await checkPublicationValidity(community, decryptedRequestMsg, publication, communityAuthor);
1018
1038
  if (publicationInvalidityReason) {
1019
1039
  if (DUPLICATE_PUBLICATION_ERRORS.has(publicationInvalidityReason)) {
1020
1040
  const sig = publication.signature.signature;
1021
- const attempts = (community._duplicatePublicationAttempts.get(sig) || 0) + 1;
1022
- community._duplicatePublicationAttempts.set(sig, attempts);
1041
+ let attempts = 0;
1042
+ if (countDuplicateAttempt) {
1043
+ attempts = (community._duplicatePublicationAttempts.get(sig) || 0) + 1;
1044
+ community._duplicatePublicationAttempts.set(sig, attempts);
1045
+ }
1023
1046
  if (attempts <= 1) {
1024
- await publishIdempotentDuplicateVerification(community, decryptedRequestMsg, request.challengeRequestId, publicationInvalidityReason);
1047
+ await publishIdempotentDuplicateVerification(community, decryptedRequestMsg, decryptedRequestMsg.challengeRequestId, publicationInvalidityReason);
1025
1048
  return;
1026
1049
  }
1027
1050
  }
1028
- await publishFailedChallengeVerification(community, { reason: publicationInvalidityReason }, request.challengeRequestId);
1051
+ await publishFailedChallengeVerification(community, { reason: publicationInvalidityReason }, decryptedRequestMsg.challengeRequestId);
1029
1052
  return;
1030
1053
  }
1031
1054
  return {
@@ -1040,12 +1063,21 @@ async function runChallengeExchangeIfNeeded(community, parsed, log) {
1040
1063
  const answerPromiseKey = decryptedRequestWithCommunityAuthor.challengeRequestId.toString();
1041
1064
  const getChallengeAnswers = async (challenges) => {
1042
1065
  await publishChallenges(community, challenges, decryptedRequestWithCommunityAuthor);
1066
+ community._ongoingChallengeExchanges.set(answerPromiseKey, true);
1043
1067
  const challengeAnswerPromise = new Promise((resolve, reject) => community._challengeAnswerResolveReject.set(answerPromiseKey, {
1044
1068
  resolve,
1045
1069
  reject
1046
1070
  }));
1047
1071
  community._challengeAnswerPromises.set(answerPromiseKey, challengeAnswerPromise);
1048
- const challengeAnswers = await community._challengeAnswerPromises.get(answerPromiseKey);
1072
+ const challengeAnswers = await pTimeout(challengeAnswerPromise, {
1073
+ milliseconds: community._challengeExchangeTtlMs,
1074
+ fallback: () => {
1075
+ throw new PKCError("ERR_COMMUNITY_TIMED_OUT_WAITING_FOR_CHALLENGE_ANSWER", {
1076
+ challengeRequestId: answerPromiseKey,
1077
+ challengeExchangeTtlMs: community._challengeExchangeTtlMs
1078
+ });
1079
+ }
1080
+ });
1049
1081
  if (!challengeAnswers) throw Error("Failed to retrieve challenge answers from promise. This is a critical error");
1050
1082
  cleanUpChallengeAnswerPromise(community, answerPromiseKey);
1051
1083
  return challengeAnswers;
@@ -1058,6 +1090,15 @@ async function runChallengeExchangeIfNeeded(community, parsed, log) {
1058
1090
  getChallengeAnswers
1059
1091
  });
1060
1092
  } catch (e) {
1093
+ if (e instanceof PKCError && e.code === "ERR_COMMUNITY_TIMED_OUT_WAITING_FOR_CHALLENGE_ANSWER") {
1094
+ log("Timed out waiting for the challenge answer, failing the exchange", answerPromiseKey);
1095
+ cleanUpChallengeAnswerPromise(community, answerPromiseKey);
1096
+ challengeVerification = {
1097
+ challengeSuccess: false,
1098
+ reason: messages.ERR_COMMUNITY_TIMED_OUT_WAITING_FOR_CHALLENGE_ANSWER
1099
+ };
1100
+ return challengeVerification;
1101
+ }
1061
1102
  log.error("getChallenge failed, the community owner needs to check the challenge code. The error is: ", e);
1062
1103
  community.emit("error", e);
1063
1104
  challengeVerification = {
@@ -1095,11 +1136,39 @@ async function handleChallengeRequest(community, request, isLocalPublisher) {
1095
1136
  validateTimestampRange: true
1096
1137
  });
1097
1138
  if (!requestSignatureValidation.valid) throw new PKCError(getErrorCodeFromMessage(requestSignatureValidation.reason), { challengeRequest: n$3(request, ["encrypted"]) });
1098
- const parsed = await parseAndValidateChallengeRequest(community, request, log);
1099
- if (!parsed) return;
1100
- const challengeVerification = await runChallengeExchangeIfNeeded(community, parsed, log);
1101
- if (!challengeVerification) return;
1102
- await runVerificationAndStorePublication(community, parsed, challengeVerification);
1139
+ const decrypted = await parseChallengeRequest(community, request, log);
1140
+ if (!decrypted) return;
1141
+ const publicationSignature = decrypted.publication.signature.signature;
1142
+ let waitedForInFlightExchange = false;
1143
+ while (community._inFlightPublicationExchanges.has(publicationSignature)) {
1144
+ waitedForInFlightExchange = true;
1145
+ log("Received a challenge request for a signed publication whose challenge exchange is in flight, waiting for it to settle", request.challengeRequestId.toString());
1146
+ if (!await pTimeout(community._inFlightPublicationExchanges.get(publicationSignature), {
1147
+ milliseconds: community._challengeExchangeTtlMs,
1148
+ fallback: () => false
1149
+ }).then((result) => result !== false)) {
1150
+ log("The in-flight challenge exchange did not settle within the challenge exchange ttl, proceeding with this request", request.challengeRequestId.toString());
1151
+ break;
1152
+ }
1153
+ }
1154
+ let settleInFlightExchange;
1155
+ const inFlightExchange = new Promise((resolve) => settleInFlightExchange = resolve);
1156
+ community._inFlightPublicationExchanges.set(publicationSignature, inFlightExchange);
1157
+ try {
1158
+ const parsed = await validatePublicationOrRespondWithFailure({
1159
+ community,
1160
+ decryptedRequestMsg: decrypted.decryptedRequestMsg,
1161
+ publication: decrypted.publication,
1162
+ countDuplicateAttempt: !waitedForInFlightExchange
1163
+ });
1164
+ if (!parsed) return;
1165
+ const challengeVerification = await runChallengeExchangeIfNeeded(community, parsed, log);
1166
+ if (!challengeVerification) return;
1167
+ await runVerificationAndStorePublication(community, parsed, challengeVerification);
1168
+ } finally {
1169
+ if (community._inFlightPublicationExchanges.get(publicationSignature) === inFlightExchange) community._inFlightPublicationExchanges.delete(publicationSignature);
1170
+ settleInFlightExchange();
1171
+ }
1103
1172
  }
1104
1173
  async function parseChallengeAnswerOrRespondWithFailure(community, challengeAnswer, decryptedRawString) {
1105
1174
  let parsedJson;
@@ -6570,7 +6639,7 @@ async function publishCommunityRecordToIpns(community, build, newCommunityRecord
6570
6639
  });
6571
6640
  if (!community.signer.ipnsKeyName) throw Error("IPNS key name is not defined");
6572
6641
  if (community._firstUpdateAfterStart) await community._resolveIpnsAndLogIfPotentialProblematicSequence();
6573
- const ttl = `${community._pkc.publishInterval * 3}ms`;
6642
+ const ttl = `${community._pkc.publishInterval * 1}ms`;
6574
6643
  const lastPublishedIpnsRecordData = await community._dbHandler.keyvGet(STORAGE_KEYS[STORAGE_KEYS.LAST_IPNS_RECORD]);
6575
6644
  const decodedIpnsRecord = lastPublishedIpnsRecordData ? decode(new Uint8Array(Object.values(lastPublishedIpnsRecordData))) : void 0;
6576
6645
  decodedIpnsRecord && BigInt(decodedIpnsRecord.sequence) + 1n;
@@ -6781,19 +6850,19 @@ async function initBeforeStarting(community) {
6781
6850
  if (!community.signer?.address) throw new PKCError("ERR_COMMUNITY_SIGNER_NOT_DEFINED");
6782
6851
  if (!community._challengeAnswerPromises) community._challengeAnswerPromises = new LRUCache({
6783
6852
  max: 1e3,
6784
- ttl: 6e5
6853
+ ttl: community._challengeExchangeTtlMs
6785
6854
  });
6786
6855
  if (!community._challengeAnswerResolveReject) community._challengeAnswerResolveReject = new LRUCache({
6787
6856
  max: 1e3,
6788
- ttl: 6e5
6857
+ ttl: community._challengeExchangeTtlMs
6789
6858
  });
6790
6859
  if (!community._ongoingChallengeExchanges) community._ongoingChallengeExchanges = new LRUCache({
6791
6860
  max: 1e3,
6792
- ttl: 6e5
6861
+ ttl: community._challengeExchangeTtlMs
6793
6862
  });
6794
6863
  if (!community._duplicatePublicationAttempts) community._duplicatePublicationAttempts = new LRUCache({
6795
6864
  max: 1e3,
6796
- ttl: 6e5
6865
+ ttl: community._challengeExchangeTtlMs
6797
6866
  });
6798
6867
  await community._dbHandler.initDbIfNeeded();
6799
6868
  }
@@ -7045,6 +7114,7 @@ async function stop(community) {
7045
7114
  untrackStartedCommunity(community._pkc, community);
7046
7115
  processStartedCommunities.untrack(community);
7047
7116
  community._duplicatePublicationAttempts?.clear();
7117
+ community._inFlightPublicationExchanges.clear();
7048
7118
  await community._dbHandler.rollbackAllTransactions();
7049
7119
  await community._dbHandler.unlockCommunityState();
7050
7120
  await community._updateStartedValue();
@@ -7498,6 +7568,8 @@ var LocalCommunity = class extends RpcLocalCommunity {
7498
7568
  ];
7499
7569
  this._defaultCommunityChallenges = generateDefaultChallenges();
7500
7570
  this._challengeExchangesFromLocalPublishers = {};
7571
+ this._inFlightPublicationExchanges = /* @__PURE__ */ new Map();
7572
+ this._challengeExchangeTtlMs = CHALLENGE_EXCHANGE_TTL_MS;
7501
7573
  this._cidsToUnPin = /* @__PURE__ */ new Set();
7502
7574
  this._mfsPathsToRemove = /* @__PURE__ */ new Set();
7503
7575
  this._communityUpdateTrigger = false;
@@ -1,7 +1,7 @@
1
1
  import { i as __toESM, r as __require, t as __commonJSMin } from "./chunk-2rV9d50f.js";
2
- import { A as doesDomainAddressHaveCapitalLetter, At as listUpdatingCommunities, B as ipnsNameToIpnsOverPubsubTopic, Br as stringifyPrimitive, Bt as require_sha256, Cn as CommentIpfsSchema, Cr as messages, D as createAbortError, Dr as setErrorMap, Dt as findUpdatingCommunity, Et as findUpdatingComment, Ft as trackUpdatingComment, H as isIpfsCid, Ht as pTimeout, O as deepMergeRuntimeFields, On as CommentUpdateForChallengeVerificationSchema, Ot as listStartedCommunities, R as hideClassPrivateProps, Rr as joinValues, Rt as untrackUpdatingComment, Sr as PKCError, St as waitForUpdateInCommunityInstanceWithErrorAndTimeout, Tt as findStartedCommunity, V as isAbortError, Vt as TimeoutError, W as isIpns, _r as FailedToFetchCommentIpfsFromGatewaysError, _t as shortifyCid, a as ChallengeRequestMessageSchema, ar as t$1, at as rejectWhenCommunityStopsOrAborts, b as areEquivalentCommunityAddresses, bt as throwIfAbortSignalAborted, dt as replaceXWithY, et as isStringDomain, ft as resolveWhenPredicateIsTrue, gr as require_retry, gt as shortifyAddress, it as rejectWhenAbortSignalFires, jn as CommentUpdateSchema, jt as refreshTrackedCommentAliases, kt as listUpdatingComments, lr as r, mt as retryKuboIpfsAdd, nn as CommunityAnchorSchema, or as e, pn as PubsubTopicSchema, r as ChallengeMessageSchema, rr as n$1, s as ChallengeVerificationMessageSchema, sn as CommunityIpfsSchema, sr as e$1, t as ChallengeAnswerMessageSchema, tr as n$2, ut as removeUndefinedValuesRecursively, vt as sleepUntilTimeoutOrAbort, wr as logger_default, xt as timestamp, y as version_default, zr as parsedType } from "./schema-KFikvQok.js";
3
- import { $ as RepliesPages, $n as RpcUnsubscribeParamSchema, An as parseSetNewSettingsPKCWsServerSchemaWithPKCErrorIfItFails, At as verifyCommentIpfs, B as setNativeFunctions$1, Bn as RpcEditCommunityParamSchema, Cn as parseDecryptedChallengeAnswerWithPKCErrorIfItFails, Ct as signCommunityEdit, D as createKuboRpcClient, Dn as parsePKCUserOptionsSchemaWithPKCErrorIfItFails, En as parseJsonWithPKCErrorIfFails, Et as verifyChallengeMessage, Fn as RpcCancelExportParamSchema, Ft as verifyCommunityEdit, Gn as RpcFetchCidParamSchema, Gt as getCommunityPublicKeyFromWire, H as CommentClientsManager, Hn as RpcExportCommunityModLogsResultSchema, Ht as buildRuntimeCommunityFields, I as listCommunitiesSync, In as RpcCidParamSchema, It as verifyVote, Jn as RpcPublishChallengeAnswersParamSchema, Kn as RpcFetchCidResultSchema, Kt as normalizeCommunityInputFromCommunity, L as monitorCommunitiesDirectory, Ln as RpcCommentRepliesPageParamSchema, Lt as buildRuntimeAuthor, Mn as parseVotePubsubMessagePublicationSchemaWithPKCErrorIfItFails, Mt as verifyCommentPubsubMessage, N as getDefaultDataPath, Nn as RpcAnchorPublishPreparationResultSchema, Nt as verifyCommentUpdate, On as parseRpcCommentEventWithPKCErrorIfItFails, Ot as verifyChallengeVerification, Pn as RpcAuthorNameParamSchema, Q as parseRawPages, Qn as RpcSuccessResultSchema, Qt as getPKCAddressFromPublicKeySync, Rn as RpcCommunityIdentifierParamSchema, Rt as cleanWireAuthor, Sn as parseCreateVoteOptionsSchemaWithPKCErrorIfItFails, Tn as parseDecryptedChallengeWithPKCErrorIfItFails, Un as RpcExportCommunityParamSchema, Ut as getCommunityAddressFromRecord, V as tryToDeleteCommunitiesThatFailedToBeDeletedBefore, Vn as RpcExportCommunityModLogsParamSchema, Vt as normalizeCreatePublicationAuthor, Wn as RpcExportCommunityResultSchema, Wt as getCommunityNameFromWire, X as findCommentInPageInstanceRecursively, Xn as RpcResolveAuthorNameResultSchema, Y as extractCommunityRuntimeFieldsFromParsedPages, Yn as RpcPublishedAnchorRecordResultSchema, Yt as getBufferedPKCAddressFromPublicKey, Zn as RpcSubscriptionIdResultSchema, _ as createSigner, _n as parseCreateCommunityEditPublicationOptionsSchemaWithPKCErrorIfItFails, _t as signComment, an as parseCommentIpfsSchemaWithPKCErrorIfItFails, ar as t$3, at as PublicationClientsManager, bn as parseCreateRemoteCommunityFunctionArgumentSchemaWithPKCErrorIfItFails, c as LRUCache, cn as parseCommentPubsubMessagePublicationWithPKCErrorIfItFails, cr as t$2, d as KeyvBetterSqlite3, dt as require_lib$1, et as applyNameResolvedCacheToCrosspost, f as decodeRpcChallengeAnswerPubsubMsg, fn as parseCommunityEditPubsubMessagePublicationSchemaWithPKCErrorIfItFails, ft as cleanUpBeforePublishing, gn as parseCreateCommentOptionsSchemaWithPKCErrorIfItFails, h as decodeRpcChallengeVerificationPubsubMsg, hn as parseCreateCommentModerationOptionsSchemaWithPKCErrorIfItFails, ht as signChallengeRequest, in as parseCommentEditPubsubMessagePublicationSchemaWithPKCErrorIfItFails, it as stripNameResolvedFromCrosspost, jt as verifyCommentModeration, kn as parseRpcCommentUpdateEventWithPKCErrorIfItFails, kt as verifyCommentEdit, l as RemoteCommunity, ln as parseCommunityAddressWithPKCErrorIfItFails, lr as t$5, m as decodeRpcChallengeRequestPubsubMsg, mn as parseCreateCommentEditOptionsSchemaWithPKCErrorIfItFails, mr as require_undici, n as RpcRemoteCommunity, nr as t$4, nt as collectCrosspostAuthorsToResolve, o as pkcJsChallenges, or as n$3, ot as PKCClientsManager, p as decodeRpcChallengePubsubMsg, pr as nativeFunctions$2, pt as signChallengeAnswer, qn as RpcPublishAnchorRecordParamSchema, qt as decode, s as TrackedInstanceRegistry, sn as parseCommentModerationPubsubMessagePublicationSchemaWithPKCErrorIfItFails, st as InflightFetchManager, t as RpcLocalCommunity, tn as parseCidStringSchemaWithPKCErrorIfItFails, tt as cloneCrosspostForRuntime, ut as PKCTypedEmitter, v as createAnchorIpnsRecord$1, vt as signCommentEdit, wn as parseDecryptedChallengeVerification, wt as signVote, x as encryptEd25519AesGcm, xn as parseCreateRpcCommunityFunctionArgumentSchemaWithPKCErrorIfItFails, y as decryptEd25519AesGcm, yt as signCommentModeration, zn as RpcCommunityPageParamSchema, zt as getAuthorNameFromRuntime } from "./rpc-local-community-BUicljzl.js";
4
- import { o as getHeliaDebugContext } from "./util-b2CRgd4P.js";
2
+ import { A as doesDomainAddressHaveCapitalLetter, At as listUpdatingCommunities, B as ipnsNameToIpnsOverPubsubTopic, Br as stringifyPrimitive, Bt as require_sha256, Cn as CommentIpfsSchema, Cr as messages, D as createAbortError, Dr as setErrorMap, Dt as findUpdatingCommunity, Et as findUpdatingComment, Ft as trackUpdatingComment, H as isIpfsCid, Ht as pTimeout, O as deepMergeRuntimeFields, On as CommentUpdateForChallengeVerificationSchema, Ot as listStartedCommunities, R as hideClassPrivateProps, Rr as joinValues, Rt as untrackUpdatingComment, Sr as PKCError, St as waitForUpdateInCommunityInstanceWithErrorAndTimeout, Tt as findStartedCommunity, V as isAbortError, Vt as TimeoutError, W as isIpns, _r as FailedToFetchCommentIpfsFromGatewaysError, _t as shortifyCid, a as ChallengeRequestMessageSchema, ar as t$1, at as rejectWhenCommunityStopsOrAborts, b as areEquivalentCommunityAddresses, bt as throwIfAbortSignalAborted, dt as replaceXWithY, et as isStringDomain, ft as resolveWhenPredicateIsTrue, gr as require_retry, gt as shortifyAddress, it as rejectWhenAbortSignalFires, jn as CommentUpdateSchema, jt as refreshTrackedCommentAliases, kt as listUpdatingComments, lr as r, mt as retryKuboIpfsAdd, nn as CommunityAnchorSchema, or as e, pn as PubsubTopicSchema, r as ChallengeMessageSchema, rr as n$1, s as ChallengeVerificationMessageSchema, sn as CommunityIpfsSchema, sr as e$1, t as ChallengeAnswerMessageSchema, tr as n$2, ut as removeUndefinedValuesRecursively, vt as sleepUntilTimeoutOrAbort, wr as logger_default, xt as timestamp, y as version_default, zr as parsedType } from "./schema-CYR820MD.js";
3
+ import { $ as RepliesPages, $n as RpcUnsubscribeParamSchema, An as parseSetNewSettingsPKCWsServerSchemaWithPKCErrorIfItFails, At as verifyCommentIpfs, B as setNativeFunctions$1, Bn as RpcEditCommunityParamSchema, Cn as parseDecryptedChallengeAnswerWithPKCErrorIfItFails, Ct as signCommunityEdit, D as createKuboRpcClient, Dn as parsePKCUserOptionsSchemaWithPKCErrorIfItFails, En as parseJsonWithPKCErrorIfFails, Et as verifyChallengeMessage, Fn as RpcCancelExportParamSchema, Ft as verifyCommunityEdit, Gn as RpcFetchCidParamSchema, Gt as getCommunityPublicKeyFromWire, H as CommentClientsManager, Hn as RpcExportCommunityModLogsResultSchema, Ht as buildRuntimeCommunityFields, I as listCommunitiesSync, In as RpcCidParamSchema, It as verifyVote, Jn as RpcPublishChallengeAnswersParamSchema, Kn as RpcFetchCidResultSchema, Kt as normalizeCommunityInputFromCommunity, L as monitorCommunitiesDirectory, Ln as RpcCommentRepliesPageParamSchema, Lt as buildRuntimeAuthor, Mn as parseVotePubsubMessagePublicationSchemaWithPKCErrorIfItFails, Mt as verifyCommentPubsubMessage, N as getDefaultDataPath, Nn as RpcAnchorPublishPreparationResultSchema, Nt as verifyCommentUpdate, On as parseRpcCommentEventWithPKCErrorIfItFails, Ot as verifyChallengeVerification, Pn as RpcAuthorNameParamSchema, Q as parseRawPages, Qn as RpcSuccessResultSchema, Qt as getPKCAddressFromPublicKeySync, Rn as RpcCommunityIdentifierParamSchema, Rt as cleanWireAuthor, Sn as parseCreateVoteOptionsSchemaWithPKCErrorIfItFails, Tn as parseDecryptedChallengeWithPKCErrorIfItFails, Un as RpcExportCommunityParamSchema, Ut as getCommunityAddressFromRecord, V as tryToDeleteCommunitiesThatFailedToBeDeletedBefore, Vn as RpcExportCommunityModLogsParamSchema, Vt as normalizeCreatePublicationAuthor, Wn as RpcExportCommunityResultSchema, Wt as getCommunityNameFromWire, X as findCommentInPageInstanceRecursively, Xn as RpcResolveAuthorNameResultSchema, Y as extractCommunityRuntimeFieldsFromParsedPages, Yn as RpcPublishedAnchorRecordResultSchema, Yt as getBufferedPKCAddressFromPublicKey, Zn as RpcSubscriptionIdResultSchema, _ as createSigner, _n as parseCreateCommunityEditPublicationOptionsSchemaWithPKCErrorIfItFails, _t as signComment, an as parseCommentIpfsSchemaWithPKCErrorIfItFails, ar as t$3, at as PublicationClientsManager, bn as parseCreateRemoteCommunityFunctionArgumentSchemaWithPKCErrorIfItFails, c as LRUCache, cn as parseCommentPubsubMessagePublicationWithPKCErrorIfItFails, cr as t$2, d as KeyvBetterSqlite3, dt as require_lib$1, et as applyNameResolvedCacheToCrosspost, f as decodeRpcChallengeAnswerPubsubMsg, fn as parseCommunityEditPubsubMessagePublicationSchemaWithPKCErrorIfItFails, ft as cleanUpBeforePublishing, gn as parseCreateCommentOptionsSchemaWithPKCErrorIfItFails, h as decodeRpcChallengeVerificationPubsubMsg, hn as parseCreateCommentModerationOptionsSchemaWithPKCErrorIfItFails, ht as signChallengeRequest, in as parseCommentEditPubsubMessagePublicationSchemaWithPKCErrorIfItFails, it as stripNameResolvedFromCrosspost, jt as verifyCommentModeration, kn as parseRpcCommentUpdateEventWithPKCErrorIfItFails, kt as verifyCommentEdit, l as RemoteCommunity, ln as parseCommunityAddressWithPKCErrorIfItFails, lr as t$5, m as decodeRpcChallengeRequestPubsubMsg, mn as parseCreateCommentEditOptionsSchemaWithPKCErrorIfItFails, mr as require_undici, n as RpcRemoteCommunity, nr as t$4, nt as collectCrosspostAuthorsToResolve, o as pkcJsChallenges, or as n$3, ot as PKCClientsManager, p as decodeRpcChallengePubsubMsg, pr as nativeFunctions$2, pt as signChallengeAnswer, qn as RpcPublishAnchorRecordParamSchema, qt as decode, s as TrackedInstanceRegistry, sn as parseCommentModerationPubsubMessagePublicationSchemaWithPKCErrorIfItFails, st as InflightFetchManager, t as RpcLocalCommunity, tn as parseCidStringSchemaWithPKCErrorIfItFails, tt as cloneCrosspostForRuntime, ut as PKCTypedEmitter, v as createAnchorIpnsRecord$1, vt as signCommentEdit, wn as parseDecryptedChallengeVerification, wt as signVote, x as encryptEd25519AesGcm, xn as parseCreateRpcCommunityFunctionArgumentSchemaWithPKCErrorIfItFails, y as decryptEd25519AesGcm, yt as signCommentModeration, zn as RpcCommunityPageParamSchema, zt as getAuthorNameFromRuntime } from "./rpc-local-community-BK1FjUk2.js";
4
+ import { o as getHeliaDebugContext } from "./util-gAVw5PrE.js";
5
5
  import path from "path";
6
6
  import assert from "assert";
7
7
  import fs from "fs";
@@ -358,6 +358,10 @@ var Publication = class extends import_lib.TypedEmitter {
358
358
  ...msg,
359
359
  ...decryptedChallenge
360
360
  };
361
+ if (Object.values(this._challengeExchanges).some((exchange) => exchange.challenge)) {
362
+ log.trace("Received a copy of a challenge that was already handled, ignoring it");
363
+ return;
364
+ }
361
365
  this._challengeExchanges[msg.challengeRequestId.toString()].challenge = decryptedChallengeMsg;
362
366
  this._updatePublishingStateWithEmission("waiting-challenge-answers");
363
367
  const challengeTopic = this._community?.pubsubTopic;
@@ -422,10 +426,6 @@ var Publication = class extends import_lib.TypedEmitter {
422
426
  this.emit("error", e);
423
427
  return;
424
428
  }
425
- if (decryptedChallengeVerification.comment) {
426
- await this._verifyDecryptedChallengeVerificationAndUpdateCommentProps(decryptedChallengeVerification);
427
- log("Updated the props of this instance with challengeverification.encrypted");
428
- }
429
429
  }
430
430
  } else {
431
431
  newPublishingState = "failed";
@@ -435,7 +435,15 @@ var Publication = class extends import_lib.TypedEmitter {
435
435
  ...msg,
436
436
  ...decryptedChallengeVerification
437
437
  };
438
+ if (this._challengeExchanges[msg.challengeRequestId.toString()].challengeVerification) {
439
+ log.trace("Received a copy of a challenge verification that was already handled, ignoring it");
440
+ return;
441
+ }
438
442
  this._challengeExchanges[msg.challengeRequestId.toString()].challengeVerification = challengeVerificationMsg;
443
+ if (decryptedChallengeVerification?.comment) {
444
+ await this._verifyDecryptedChallengeVerificationAndUpdateCommentProps(decryptedChallengeVerification);
445
+ log("Updated the props of this instance with challengeverification.encrypted");
446
+ }
439
447
  Object.values(this._challengeExchanges).forEach((exchange) => this._updatePubsubState("stopped", exchange.providerUrl));
440
448
  this._changePublicationStateEmitEventEmitStateChangeEvent({
441
449
  newPublishingState,
@@ -498,30 +506,43 @@ var Publication = class extends import_lib.TypedEmitter {
498
506
  if (challengeExchangesWithChallenge.length === 0) throw Error("No challenge exchanges with challenge");
499
507
  if (challengeExchangesWithChallenge.length > 1) throw Error("We should only have one challenge exchange with challenge");
500
508
  const challengeExchange = challengeExchangesWithChallenge[0];
509
+ const challengeRequestIdString = challengeExchange.challengeRequest.challengeRequestId.toString();
510
+ if (challengeExchange.challengeAnswer || challengeExchange.challengeAnswerPublishInFlight) throw new PKCError("ERR_CHALLENGE_ANSWER_ALREADY_PUBLISHED", {
511
+ challengeRequestId: challengeRequestIdString,
512
+ publishingState: this.publishingState
513
+ });
514
+ challengeExchange.challengeAnswerPublishInFlight = true;
501
515
  assert(this._community, "Local pkc-js needs publication._community to be defined to publish challenge answer");
502
516
  if (!challengeExchange.signer) throw Error("Signer is undefined for this challenge exchange");
503
- const encryptedChallengeAnswers = await encryptEd25519AesGcm(JSON.stringify(toEncryptAnswers), challengeExchange.signer.privateKey, this._community.encryption.publicKey);
504
- const toSignAnswer = cleanUpBeforePublishing({
505
- type: "CHALLENGEANSWER",
506
- challengeRequestId: challengeExchange.challengeRequest.challengeRequestId,
507
- encrypted: encryptedChallengeAnswers,
508
- userAgent: this._pkc.userAgent,
509
- protocolVersion: version_default.PROTOCOL_VERSION,
510
- timestamp: timestamp()
511
- });
512
- const answerMsgToPublish = {
513
- ...toSignAnswer,
514
- signature: await signChallengeAnswer({
515
- challengeAnswer: toSignAnswer,
516
- signer: challengeExchange.signer
517
- })
518
- };
517
+ let answerMsgToPublish;
518
+ try {
519
+ const encryptedChallengeAnswers = await encryptEd25519AesGcm(JSON.stringify(toEncryptAnswers), challengeExchange.signer.privateKey, this._community.encryption.publicKey);
520
+ const toSignAnswer = cleanUpBeforePublishing({
521
+ type: "CHALLENGEANSWER",
522
+ challengeRequestId: challengeExchange.challengeRequest.challengeRequestId,
523
+ encrypted: encryptedChallengeAnswers,
524
+ userAgent: this._pkc.userAgent,
525
+ protocolVersion: version_default.PROTOCOL_VERSION,
526
+ timestamp: timestamp()
527
+ });
528
+ answerMsgToPublish = {
529
+ ...toSignAnswer,
530
+ signature: await signChallengeAnswer({
531
+ challengeAnswer: toSignAnswer,
532
+ signer: challengeExchange.signer
533
+ })
534
+ };
535
+ } catch (e) {
536
+ challengeExchange.challengeAnswerPublishInFlight = false;
537
+ throw e;
538
+ }
519
539
  this._updatePublishingStateWithEmission("publishing-challenge-answer");
520
540
  this._updatePubsubState("publishing-challenge-answer", challengeExchange.providerUrl);
521
541
  if (this._publishingToLocalCommunity) try {
522
542
  await this._publishingToLocalCommunity.handleChallengeAnswer(answerMsgToPublish);
523
543
  } catch (e) {
524
544
  this._challengeExchanges[challengeExchange.challengeRequest.challengeRequestId.toString()].challengeAnswerPublishError = e;
545
+ challengeExchange.challengeAnswerPublishInFlight = false;
525
546
  this._updatePublishingStateWithEmission("failed");
526
547
  this._updatePubsubState("stopped", challengeExchange.providerUrl);
527
548
  throw e;
@@ -530,6 +551,7 @@ var Publication = class extends import_lib.TypedEmitter {
530
551
  await this._clientsManager.pubsubPublishOnProvider(this._communityChallengePubsubExchangeTopic(), answerMsgToPublish, challengeExchange.providerUrl);
531
552
  } catch (e) {
532
553
  this._challengeExchanges[challengeExchange.challengeRequest.challengeRequestId.toString()].challengeAnswerPublishError = e;
554
+ challengeExchange.challengeAnswerPublishInFlight = false;
533
555
  this._updatePublishingStateWithEmission("failed");
534
556
  this._updatePubsubState("stopped", challengeExchange.providerUrl);
535
557
  throw e;
@@ -682,6 +704,7 @@ var Publication = class extends import_lib.TypedEmitter {
682
704
  _isAllAttemptsExhausted(maxNumOfChallengeExchanges) {
683
705
  if (Object.keys(this._challengeExchanges).length !== maxNumOfChallengeExchanges) return false;
684
706
  return Object.values(this._challengeExchanges).every((exchange) => {
707
+ if (exchange.challenge || exchange.challengeVerification) return false;
685
708
  if (exchange.challengeRequestPublishError || exchange.challengeAnswerPublishError) return true;
686
709
  return typeof exchange.challengeRequestPublishTimestamp === "number" && exchange.challengeRequestPublishTimestamp + this._setProviderFailureThresholdSeconds <= timestamp();
687
710
  });
@@ -947,8 +970,10 @@ var Publication = class extends import_lib.TypedEmitter {
947
970
  currentPubsubProviderIndex += 1;
948
971
  }
949
972
  const decryptedRequest = this._challengeExchanges[challengeRequest.challengeRequestId.toString()].challengeRequest;
950
- this._updatePubsubState("waiting-challenge", providerUrl);
951
- this._updatePublishingStateWithEmission("waiting-challenge");
973
+ if (!this._didWeReceiveChallengeOrChallengeVerification()) {
974
+ this._updatePubsubState("waiting-challenge", providerUrl);
975
+ this._updatePublishingStateWithEmission("waiting-challenge");
976
+ }
952
977
  log(`Published a challenge request of publication`, this.getType(), "with provider", providerUrl);
953
978
  this.emit("challengerequest", decryptedRequest);
954
979
  if (currentPubsubProviderIndex !== providers.length) await sleepUntilTimeoutOrAbort(this._publishToDifferentProviderThresholdSeconds * 1e3, this._pkc._getDestroyAbortSignal());
@@ -957,6 +982,10 @@ var Publication = class extends import_lib.TypedEmitter {
957
982
  await sleepUntilTimeoutOrAbort(this._setProviderFailureThresholdSeconds * 1e3, this._pkc._getDestroyAbortSignal());
958
983
  if (this._pkc.destroyed) return;
959
984
  if (this.state === "stopped") return;
985
+ if (this._didWeReceiveChallengeOrChallengeVerification()) {
986
+ log(`Received a challenge or challenge verification while waiting for retried providers, will keep the exchange alive`);
987
+ return;
988
+ }
960
989
  if (this._isAllAttemptsExhausted(providers.length)) {
961
990
  await this._postSucessOrFailurePublishing();
962
991
  const allAttemptsFailedError = new PKCError("ERR_ALL_PUBSUB_PROVIDERS_THROW_ERRORS", {
@@ -7025,7 +7054,7 @@ var PKC$1 = class extends PKCTypedEmitter {
7025
7054
  this.clients.libp2pJsClients = {};
7026
7055
  if (!this.libp2pJsClientsOptions) return;
7027
7056
  if (!this.httpRoutersOptions) throw Error("httpRoutersOptions is required for libp2pJsClient");
7028
- const { createLibp2pJsClientOrUseExistingOne } = await import("./helia-for-pkc-CHv28C49.js");
7057
+ const { createLibp2pJsClientOrUseExistingOne } = await import("./helia-for-pkc-Cq6VWePV.js");
7029
7058
  for (const clientOptions of this.libp2pJsClientsOptions) {
7030
7059
  const heliaNode = await createLibp2pJsClientOrUseExistingOne({
7031
7060
  ...clientOptions,
@@ -7304,7 +7333,7 @@ var PKC$1 = class extends PKCTypedEmitter {
7304
7333
  const log = logger_default("pkc-js:pkc:createLocalCommunity");
7305
7334
  log.trace("Received community options to create a local community instance:", options);
7306
7335
  if (!this._canCreateNewLocalCommunity()) throw new PKCError("ERR_CAN_NOT_CREATE_A_LOCAL_COMMUNITY", { pkcOptions: this._userPKCOptions });
7307
- const { LocalCommunity, createNewLocalCommunityDb, updateInstancePropsWithStartedCommunityOrDb } = await import("./local-community-Dslc-qve.js");
7336
+ const { LocalCommunity, createNewLocalCommunityDb, updateInstancePropsWithStartedCommunityOrDb } = await import("./local-community-CcHbWkoT.js");
7308
7337
  const isLocalCommunity = (await listCommunitiesSync(this)).includes(options.address);
7309
7338
  const community = new LocalCommunity(this);
7310
7339
  if (isLocalCommunity) {
@@ -1,5 +1,5 @@
1
1
  import { i as __toESM, n as __exportAll, r as __require, t as __commonJSMin } from "./chunk-2rV9d50f.js";
2
- import { $ as isRequestPubsubPublicationOfReply, $n as CommunityAuthorChallengeReservedFieldNames, $t as CreateCommentModerationOptionsWithRefinementSchema, A as doesDomainAddressHaveCapitalLetter, An as CommentUpdateReservedFields, Ar as boolean, At as listUpdatingCommunities, B as ipnsNameToIpnsOverPubsubTopic, Bt as require_sha256$1, Cn as CommentIpfsSchema, Cr as messages, D as createAbortError, Dn as CommentUpdateChallengeReservedFieldNames, Dt as findUpdatingCommunity, En as CommentSignedPropertyNames, Er as require_ms$1, Et as findUpdatingComment, Fn as CreateCommentOptionsWithRefinementSchema, Fr as record, G as isIpnsPath, Gn as CreateCommentEditOptionsWithRefinementSchema, Gt as CommunityEditPublicationSignedPropertyNames, H as isIpfsCid, Ht as pTimeout, In as GetPageParamSchema, Ir as string, It as trackUpdatingCommunity, Jt as CommentModerationChallengeRequestToEncryptSchema, Kn as AuthorAddressSchema, Kt as CommunityEditPubsubMessagePublicationSchema, L as getPostUpdateTimestampRange, Ln as ModQueuePageIpfsSchema, Lt as untrackStartedCommunity, M as fetchAndValidateIpnsRecordFromGateway, Mn as CommentUpdateSignedPropertyNames, Mr as literal, Mt as refreshTrackedCommunityAliases, Nr as number, O as deepMergeRuntimeFields, Or as _enum, Ot as listStartedCommunities, Pr as object, Pt as trackStartedCommunity, Q as isRequestPubsubPublicationOfPost, Qn as CommunityAddressSchema, Qt as CommentModerationsTableRowSchema, R as hideClassPrivateProps, Rn as PageIpfsSchema, Rt as untrackUpdatingComment, S as calculateIpfsCidV0, Sn as CommentIpfsReservedFields, Sr as PKCError, St as waitForUpdateInCommunityInstanceWithErrorAndTimeout, Tn as CommentPubsubMessageWithFlexibleAuthorRefinementSchema, Tr as require_supports_color, Tt as findStartedCommunity, U as isIpfsPath, Un as CommentEditSignedPropertyNames, Ut as CommunityEditPublicationChallengeRequestToEncryptSchema, V as isAbortError, Vn as CommentEditPubsubMessagePublicationWithFlexibleAuthorSchema, W as isIpns, Xn as CidPathSchema, Yn as ChallengeAnswersSchema, Yt as CommentModerationPubsubMessagePublicationSchema, Zn as CidStringSchema, Zt as CommentModerationSignedPropertyNames, _ as PKCUserOptionBaseSchema, _n as VotePubsubMessagePublicationSchema, _r as FailedToFetchCommentIpfsFromGatewaysError, _t as shortifyCid, an as CommunityExportRecordsSchema, ar as t$13, b as areEquivalentCommunityAddresses, br as FailedToFetchGenericIpfsFromGatewaysError, bt as throwIfAbortSignalAborted, c as ChallengeVerificationMessageSignedPropertyNames, cn as CommunitySignedPropertyNames, cr as t$11, ct as removeNullUndefinedEmptyObjectsValuesRecursively, dn as CreateRpcCommunityFunctionArgumentSchema, dr as n$8, en as ChallengeFileFactorySchema, er as CreateSignerSchema, et as isStringDomain, f as DecryptedChallengeSchema, fn as ExportCommunityModLogsOptionsSchema, fr as r$7, ft as resolveWhenPredicateIsTrue, g as PKCParsedOptionsSchema, gn as VoteChallengeRequestToEncryptSchema, gr as require_retry$1, gt as shortifyAddress, h as NameResolverSerializedSchema, hn as CreateVoteUserOptionsWithRefinementSchema, hr as e$4, i as ChallengeMessageSignedPropertyNames, in as CommunityEditOptionsSchema, j as extractNetworkErrorDetails, jn as CommentUpdateSchema, jr as custom, k as derivePublicationFromChallengeRequest, kn as CommentUpdateForChallengeVerificationSignedPropertyNames, kr as array, l as DecryptedChallengeAnswerSchema, ln as CreateNewLocalCommunityUserOptionsSchema, lr as r$8, m as NameResolveCacheOptionsSchema, mn as RpcRemoteCommunityUpdateEventResultSchema, mr as t$9, n as ChallengeAnswerMessageSignedPropertyNames, o as ChallengeRequestMessageSignedPropertyNames, on as CommunityIpfsReservedFields, or as e$6, p as DecryptedChallengeVerificationSchema, pr as t$10, qn as AuthorCommentIpfsReservedFields, qt as CreateCommunityEditPublicationOptionsWithRefinementSchema, rn as CommunityChallengeSettingSchema, rr as n$9, rt as raceAgainstAbort, sn as CommunityIpfsSchema, sr as e$5, tn as ChallengeFileSchema, tr as n$10, tt as pubsubTopicToDhtKey, un as CreateRemoteCommunityFunctionArgumentSchema, ur as t$12, v as PKCUserOptionsSchema, vr as FailedToFetchCommentUpdateFromGatewaysError, vt as sleepUntilTimeoutOrAbort, wr as logger_default, xn as CommentChallengeRequestToEncryptSchema, xr as FailedToFetchPageIpfsFromGatewaysError, xt as timestamp, yn as VoteSignedPropertyNames, yr as FailedToFetchCommunityFromGatewaysError, z as interruptibleSleep, zn as CommentEditChallengeRequestToEncryptSchema, zt as untrackUpdatingCommunity } from "./schema-KFikvQok.js";
2
+ import { $ as isRequestPubsubPublicationOfReply, $n as CommunityAuthorChallengeReservedFieldNames, $t as CreateCommentModerationOptionsWithRefinementSchema, A as doesDomainAddressHaveCapitalLetter, An as CommentUpdateReservedFields, Ar as boolean, At as listUpdatingCommunities, B as ipnsNameToIpnsOverPubsubTopic, Bt as require_sha256$1, Cn as CommentIpfsSchema, Cr as messages, D as createAbortError, Dn as CommentUpdateChallengeReservedFieldNames, Dt as findUpdatingCommunity, En as CommentSignedPropertyNames, Er as require_ms$1, Et as findUpdatingComment, Fn as CreateCommentOptionsWithRefinementSchema, Fr as record, G as isIpnsPath, Gn as CreateCommentEditOptionsWithRefinementSchema, Gt as CommunityEditPublicationSignedPropertyNames, H as isIpfsCid, Ht as pTimeout, In as GetPageParamSchema, Ir as string, It as trackUpdatingCommunity, Jt as CommentModerationChallengeRequestToEncryptSchema, Kn as AuthorAddressSchema, Kt as CommunityEditPubsubMessagePublicationSchema, L as getPostUpdateTimestampRange, Ln as ModQueuePageIpfsSchema, Lt as untrackStartedCommunity, M as fetchAndValidateIpnsRecordFromGateway, Mn as CommentUpdateSignedPropertyNames, Mr as literal, Mt as refreshTrackedCommunityAliases, Nr as number, O as deepMergeRuntimeFields, Or as _enum, Ot as listStartedCommunities, Pr as object, Pt as trackStartedCommunity, Q as isRequestPubsubPublicationOfPost, Qn as CommunityAddressSchema, Qt as CommentModerationsTableRowSchema, R as hideClassPrivateProps, Rn as PageIpfsSchema, Rt as untrackUpdatingComment, S as calculateIpfsCidV0, Sn as CommentIpfsReservedFields, Sr as PKCError, St as waitForUpdateInCommunityInstanceWithErrorAndTimeout, Tn as CommentPubsubMessageWithFlexibleAuthorRefinementSchema, Tr as require_supports_color, Tt as findStartedCommunity, U as isIpfsPath, Un as CommentEditSignedPropertyNames, Ut as CommunityEditPublicationChallengeRequestToEncryptSchema, V as isAbortError, Vn as CommentEditPubsubMessagePublicationWithFlexibleAuthorSchema, W as isIpns, Xn as CidPathSchema, Yn as ChallengeAnswersSchema, Yt as CommentModerationPubsubMessagePublicationSchema, Zn as CidStringSchema, Zt as CommentModerationSignedPropertyNames, _ as PKCUserOptionBaseSchema, _n as VotePubsubMessagePublicationSchema, _r as FailedToFetchCommentIpfsFromGatewaysError, _t as shortifyCid, an as CommunityExportRecordsSchema, ar as t$13, b as areEquivalentCommunityAddresses, br as FailedToFetchGenericIpfsFromGatewaysError, bt as throwIfAbortSignalAborted, c as ChallengeVerificationMessageSignedPropertyNames, cn as CommunitySignedPropertyNames, cr as t$11, ct as removeNullUndefinedEmptyObjectsValuesRecursively, dn as CreateRpcCommunityFunctionArgumentSchema, dr as n$8, en as ChallengeFileFactorySchema, er as CreateSignerSchema, et as isStringDomain, f as DecryptedChallengeSchema, fn as ExportCommunityModLogsOptionsSchema, fr as r$7, ft as resolveWhenPredicateIsTrue, g as PKCParsedOptionsSchema, gn as VoteChallengeRequestToEncryptSchema, gr as require_retry$1, gt as shortifyAddress, h as NameResolverSerializedSchema, hn as CreateVoteUserOptionsWithRefinementSchema, hr as e$4, i as ChallengeMessageSignedPropertyNames, in as CommunityEditOptionsSchema, j as extractNetworkErrorDetails, jn as CommentUpdateSchema, jr as custom, k as derivePublicationFromChallengeRequest, kn as CommentUpdateForChallengeVerificationSignedPropertyNames, kr as array, l as DecryptedChallengeAnswerSchema, ln as CreateNewLocalCommunityUserOptionsSchema, lr as r$8, m as NameResolveCacheOptionsSchema, mn as RpcRemoteCommunityUpdateEventResultSchema, mr as t$9, n as ChallengeAnswerMessageSignedPropertyNames, o as ChallengeRequestMessageSignedPropertyNames, on as CommunityIpfsReservedFields, or as e$6, p as DecryptedChallengeVerificationSchema, pr as t$10, qn as AuthorCommentIpfsReservedFields, qt as CreateCommunityEditPublicationOptionsWithRefinementSchema, rn as CommunityChallengeSettingSchema, rr as n$9, rt as raceAgainstAbort, sn as CommunityIpfsSchema, sr as e$5, tn as ChallengeFileSchema, tr as n$10, tt as pubsubTopicToDhtKey, un as CreateRemoteCommunityFunctionArgumentSchema, ur as t$12, v as PKCUserOptionsSchema, vr as FailedToFetchCommentUpdateFromGatewaysError, vt as sleepUntilTimeoutOrAbort, wr as logger_default, xn as CommentChallengeRequestToEncryptSchema, xr as FailedToFetchPageIpfsFromGatewaysError, xt as timestamp, yn as VoteSignedPropertyNames, yr as FailedToFetchCommunityFromGatewaysError, z as interruptibleSleep, zn as CommentEditChallengeRequestToEncryptSchema, zt as untrackUpdatingCommunity } from "./schema-CYR820MD.js";
3
3
  import { t as all } from "./src-CYIMjhjK.js";
4
4
  import { createHash } from "node:crypto";
5
5
  import { pathToFileURL } from "node:url";
@@ -99362,6 +99362,8 @@ var BaseClientsManager = class BaseClientsManager {
99362
99362
  url,
99363
99363
  status: res?.status,
99364
99364
  statusText: res?.statusText,
99365
+ responseEtag: res?.headers?.get("etag"),
99366
+ responseCacheControl: res?.headers?.get("cache-control"),
99365
99367
  fetchError: String(e),
99366
99368
  fetchErrorCode: nodeError?.code,
99367
99369
  fetchErrorErrno: nodeError?.errno,
@@ -99477,7 +99479,8 @@ var BaseClientsManager = class BaseClientsManager {
99477
99479
  wasRequestAborted: loadOpts.abortController.signal.aborted
99478
99480
  };
99479
99481
  this.postFetchGatewayFailure(gateway, loadOpts, e);
99480
- this._pkc._stats.recordGatewayFailure(gateway, loadOpts.recordIpfsType).catch((err) => log.error("failed to report gateway error", err));
99482
+ if (e.details?.status === 304 || String(e.code).startsWith("ERR_GATEWAY_ABORTING_LOADING")) this._pkc._stats.recordGatewaySuccess(gateway, loadOpts.recordIpfsType, Date.now() - timeBefore).catch((err) => log.error("Failed to report gateway success", err));
99483
+ else this._pkc._stats.recordGatewayFailure(gateway, loadOpts.recordIpfsType).catch((err) => log.error("failed to report gateway error", err));
99481
99484
  return { error: e };
99482
99485
  }
99483
99486
  }
@@ -99650,6 +99653,7 @@ var BaseClientsManager = class BaseClientsManager {
99650
99653
  const data = concat(await all(ipfsClient.cat(cidV0, {
99651
99654
  length: loadOpts.maxFileSizeBytes,
99652
99655
  timeout: `${loadOpts.timeoutMs}ms`,
99656
+ signal: loadOpts.abortSignal,
99653
99657
  ...seedScopeOptions
99654
99658
  })));
99655
99659
  const fileContent = toString(data);
@@ -125416,12 +125420,15 @@ function selectWinningGatewayCommunity(opts) {
125416
125420
  //#region dist/node/community/community-client-manager.js
125417
125421
  const MAX_FILE_SIZE_BYTES_FOR_COMMUNITY_IPFS = 1024 * 1024;
125418
125422
  const FORCED_IPNS_NETWORK_REVALIDATION_MIN_INTERVAL_MS = 3e4;
125423
+ const GATEWAY_FALLBACK_POLL_MS = 1e4;
125424
+ const GATEWAY_POLL_FLOOR_MS = 1e3;
125419
125425
  var CommunityClientsManager = class extends PKCClientsManager {
125420
125426
  constructor(community) {
125421
125427
  super(community._pkc);
125422
125428
  this._suppressUpdatingStateForNameResolution = 0;
125423
125429
  this._ipnsLoadingOperation = void 0;
125424
125430
  this._updateCidsAlreadyLoaded = new LimitedSet(30);
125431
+ this._gatewayEtagsOfLoadedRecords = new LimitedSet(5);
125425
125432
  this._subscribedIpnsArrivalTopics = /* @__PURE__ */ new Set();
125426
125433
  this._pendingIpnsArrivalCids = /* @__PURE__ */ new Set();
125427
125434
  this._pendingIpnsArrivalHopTargets = /* @__PURE__ */ new Set();
@@ -125616,7 +125623,7 @@ var CommunityClientsManager = class extends PKCClientsManager {
125616
125623
  ms: safetyNetMs,
125617
125624
  signal: this._community._getStopAbortSignal()
125618
125625
  });
125619
- } else await sleepUntilTimeoutOrAbort(areWeConnectedToKuboOrHelia ? 1e3 : this._pkc.updateInterval, this._community._getStopAbortSignal());
125626
+ } else await sleepUntilTimeoutOrAbort(areWeConnectedToKuboOrHelia ? 1e3 : this._computeGatewayPollMs(), this._community._getStopAbortSignal());
125620
125627
  }
125621
125628
  this._clearIpnsArrivalSubscriptions();
125622
125629
  this._community._clearStopAbortController();
@@ -125625,8 +125632,27 @@ var CommunityClientsManager = class extends PKCClientsManager {
125625
125632
  async stopUpdatingLoop() {
125626
125633
  this._ipnsLoadingOperation?.stop();
125627
125634
  this._updateCidsAlreadyLoaded.clear();
125635
+ this._gatewayEtagsOfLoadedRecords.clear();
125636
+ this._gatewayMaxAgeSecondsFromLastPoll = void 0;
125628
125637
  this._clearIpnsArrivalSubscriptions();
125629
125638
  }
125639
+ _computeGatewayPollMs() {
125640
+ const maxAgeSeconds = this._gatewayMaxAgeSecondsFromLastPoll;
125641
+ const baseMs = typeof maxAgeSeconds === "number" ? maxAgeSeconds * 1e3 + 250 + Math.random() * 750 : GATEWAY_FALLBACK_POLL_MS * (.75 + Math.random() * .5);
125642
+ const ceilingMs = this._pkc.updateInterval;
125643
+ return Math.max(Math.min(baseMs, ceilingMs), Math.min(GATEWAY_POLL_FLOOR_MS, ceilingMs));
125644
+ }
125645
+ _parseMaxAgeSeconds(cacheControl) {
125646
+ const maxAgeMatch = cacheControl?.match(/max-age=(\d+)/i);
125647
+ if (!maxAgeMatch?.[1]) return void 0;
125648
+ const maxAgeSeconds = parseInt(maxAgeMatch[1]);
125649
+ return Number.isFinite(maxAgeSeconds) ? maxAgeSeconds : void 0;
125650
+ }
125651
+ _normalizedEtagOfResponse(res) {
125652
+ const etag = res?.headers?.get("etag");
125653
+ if (!etag) return void 0;
125654
+ return etag.replace(/^W\//, "");
125655
+ }
125630
125656
  _onIpnsRecordArrival(arrival) {
125631
125657
  const value = arrival.record.value;
125632
125658
  if (isIpfsPath(value)) {
@@ -126025,26 +126051,16 @@ var CommunityClientsManager = class extends PKCClientsManager {
126025
126051
  } else {
126026
126052
  gatewayFetches[gatewayUrl].communityRecord = communityIpfs;
126027
126053
  gatewayFetches[gatewayUrl].cid = calculatedCommunityCidFromBody;
126028
- const cacheControl = gatewayRes?.res?.headers?.get("cache-control");
126029
- if (cacheControl) {
126030
- const maxAgeMatch = cacheControl.match(/max-age=(\d+)/);
126031
- if (maxAgeMatch && maxAgeMatch[1]) {
126032
- const ttl = parseInt(maxAgeMatch[1]);
126033
- gatewayFetches[gatewayUrl].ttl = ttl;
126034
- }
126035
- }
126054
+ gatewayFetches[gatewayUrl].ttl = this._parseMaxAgeSeconds(gatewayRes?.res?.headers?.get("cache-control"));
126055
+ const etag = this._normalizedEtagOfResponse(gatewayRes?.res);
126056
+ if (etag) this._gatewayEtagsOfLoadedRecords.add(etag);
126036
126057
  }
126037
126058
  };
126038
126059
  const checkResponseHeadersIfOldCid = async (gatewayRes) => {
126060
+ gatewayFetches[gatewayUrl].ttl = this._parseMaxAgeSeconds(gatewayRes?.headers?.get("cache-control"));
126039
126061
  const cidOfIpnsFromEtagHeader = gatewayRes?.headers?.get("etag")?.toString();
126040
126062
  if (!cidOfIpnsFromEtagHeader) return;
126041
- let parsedCid;
126042
- try {
126043
- parsedCid = CID.parse(cidOfIpnsFromEtagHeader.replace(/^W\//, "").split("\"").join("")).toV0().toString();
126044
- } catch (e) {
126045
- return;
126046
- }
126047
- if (this._updateCidsAlreadyLoaded.has(parsedCid)) {
126063
+ const abortBecauseRecordAlreadyLoaded = () => {
126048
126064
  abortController.abort("Aborting community IPNS request because we already loaded this record");
126049
126065
  return new PKCError("ERR_GATEWAY_ABORTING_LOADING_COMMUNITY_BECAUSE_WE_ALREADY_LOADED_THIS_RECORD", {
126050
126066
  cidOfIpnsFromEtagHeader,
@@ -126052,9 +126068,24 @@ var CommunityClientsManager = class extends PKCClientsManager {
126052
126068
  gatewayRes,
126053
126069
  gatewayUrl
126054
126070
  });
126071
+ };
126072
+ const normalizedEtag = this._normalizedEtagOfResponse(gatewayRes);
126073
+ if (normalizedEtag && this._gatewayEtagsOfLoadedRecords.has(normalizedEtag)) return abortBecauseRecordAlreadyLoaded();
126074
+ let parsedCid;
126075
+ try {
126076
+ parsedCid = CID.parse(cidOfIpnsFromEtagHeader.replace(/^W\//, "").split("\"").join("")).toV0().toString();
126077
+ } catch (e) {
126078
+ return;
126079
+ }
126080
+ if (this._updateCidsAlreadyLoaded.has(parsedCid) || this._community.updateCid === parsedCid) {
126081
+ if (normalizedEtag) this._gatewayEtagsOfLoadedRecords.add(normalizedEtag);
126082
+ return abortBecauseRecordAlreadyLoaded();
126055
126083
  }
126056
126084
  };
126057
- const requestHeaders = this._updateCidsAlreadyLoaded.size > 0 ? { "If-None-Match": "\"" + Array.from(this._updateCidsAlreadyLoaded.values()).join(",") + "\"" } : void 0;
126085
+ const etagsForConditionalRequest = new Set(this._gatewayEtagsOfLoadedRecords);
126086
+ if (this._community.updateCid) etagsForConditionalRequest.add(`"${this._community.updateCid}"`);
126087
+ for (const cid of Array.from(this._updateCidsAlreadyLoaded.values()).slice(-5)) etagsForConditionalRequest.add(`"${cid}"`);
126088
+ const requestHeaders = etagsForConditionalRequest.size > 0 ? { "If-None-Match": Array.from(etagsForConditionalRequest).join(", ") } : void 0;
126058
126089
  gatewayFetches[gatewayUrl] = {
126059
126090
  abortController,
126060
126091
  promise: queueLimit(() => this._fetchWithGateway(gatewayUrl, {
@@ -126102,6 +126133,15 @@ var CommunityClientsManager = class extends PKCClientsManager {
126102
126133
  };
126103
126134
  };
126104
126135
  const promisesToIterate = Object.values(gatewayFetches).map((gatewayFetch) => gatewayFetch.promise);
126136
+ const recordGatewayMaxAgeForPacing = () => {
126137
+ const maxAges = [];
126138
+ for (const gatewayFetch of Object.values(gatewayFetches)) {
126139
+ if (typeof gatewayFetch.ttl === "number") maxAges.push(gatewayFetch.ttl);
126140
+ const maxAgeFrom304 = this._parseMaxAgeSeconds(gatewayFetch.error?.details?.responseCacheControl);
126141
+ if (typeof maxAgeFrom304 === "number") maxAges.push(maxAgeFrom304);
126142
+ }
126143
+ this._gatewayMaxAgeSecondsFromLastPoll = maxAges.length > 0 ? Math.min(...maxAges) : void 0;
126144
+ };
126105
126145
  let suitableCommunity;
126106
126146
  try {
126107
126147
  suitableCommunity = await new Promise((resolve, reject) => promisesToIterate.map((gatewayPromise, i) => gatewayPromise.then(async (res) => {
@@ -126116,8 +126156,15 @@ var CommunityClientsManager = class extends PKCClientsManager {
126116
126156
  } catch {
126117
126157
  cleanUp();
126118
126158
  throwIfAbortSignalAborted(stopSignal);
126159
+ recordGatewayMaxAgeForPacing();
126119
126160
  const gatewayToError = t$4(gatewayFetches, (gatewayFetch) => gatewayFetch.error);
126120
- if (Object.keys(gatewayFetches).map((gatewayUrl) => gatewayFetches[gatewayUrl].error).some((err) => err.details?.status === 304 || err.code === "ERR_GATEWAY_ABORTING_LOADING_COMMUNITY_BECAUSE_WE_ALREADY_LOADED_THIS_RECORD")) {
126161
+ const hasGatewayConfirmingCurrentRecord = Object.keys(gatewayFetches).map((gatewayUrl) => gatewayFetches[gatewayUrl].error).some((err) => err.details?.status === 304 || err.code === "ERR_GATEWAY_ABORTING_LOADING_COMMUNITY_BECAUSE_WE_ALREADY_LOADED_THIS_RECORD");
126162
+ for (const gatewayFetch of Object.values(gatewayFetches)) {
126163
+ if (gatewayFetch.error?.details?.status !== 304) continue;
126164
+ const etagFrom304 = gatewayFetch.error.details?.responseEtag;
126165
+ if (etagFrom304) this._gatewayEtagsOfLoadedRecords.add(etagFrom304.replace(/^W\//, ""));
126166
+ }
126167
+ if (hasGatewayConfirmingCurrentRecord) {
126121
126168
  if (this._community.anchor && this._community.ipnsHops?.[0] !== ipnsName) {
126122
126169
  const confirmingGatewayUrl = Object.keys(gatewayFetches).find((gatewayUrl) => gatewayFetches[gatewayUrl].error?.details?.status === 304 || gatewayFetches[gatewayUrl].error?.code === "ERR_GATEWAY_ABORTING_LOADING_COMMUNITY_BECAUSE_WE_ALREADY_LOADED_THIS_RECORD");
126123
126170
  if (confirmingGatewayUrl) {
@@ -126148,6 +126195,7 @@ var CommunityClientsManager = class extends PKCClientsManager {
126148
126195
  delete combinedError.stack;
126149
126196
  throw combinedError;
126150
126197
  }
126198
+ recordGatewayMaxAgeForPacing();
126151
126199
  return suitableCommunity;
126152
126200
  }
126153
126201
  async _resolveIpnsChainViaGateway(gatewayUrl, anchorIpnsName, abortSignal) {