@atproto/bsky 0.0.249 → 0.0.250

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 (62) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/api/age-assurance/const.d.ts.map +1 -1
  3. package/dist/api/age-assurance/const.js +19 -0
  4. package/dist/api/age-assurance/const.js.map +1 -1
  5. package/dist/api/age-assurance/kws/age-verified.d.ts +20 -0
  6. package/dist/api/age-assurance/kws/age-verified.d.ts.map +1 -1
  7. package/dist/api/age-assurance/kws/age-verified.js +6 -0
  8. package/dist/api/age-assurance/kws/age-verified.js.map +1 -1
  9. package/dist/api/app/bsky/graph/muteActor.d.ts.map +1 -1
  10. package/dist/api/app/bsky/graph/muteActor.js +3 -0
  11. package/dist/api/app/bsky/graph/muteActor.js.map +1 -1
  12. package/dist/api/kws/types.d.ts +33 -0
  13. package/dist/api/kws/types.d.ts.map +1 -1
  14. package/dist/api/kws/types.js +3 -0
  15. package/dist/api/kws/types.js.map +1 -1
  16. package/dist/data-plane/index.d.ts +0 -1
  17. package/dist/data-plane/index.d.ts.map +1 -1
  18. package/dist/data-plane/index.js +0 -1
  19. package/dist/data-plane/index.js.map +1 -1
  20. package/dist/data-plane/server/bsync-subscription.d.ts +47 -0
  21. package/dist/data-plane/server/bsync-subscription.d.ts.map +1 -0
  22. package/dist/data-plane/server/bsync-subscription.js +461 -0
  23. package/dist/data-plane/server/bsync-subscription.js.map +1 -0
  24. package/dist/data-plane/server/index.d.ts +1 -0
  25. package/dist/data-plane/server/index.d.ts.map +1 -1
  26. package/dist/data-plane/server/index.js +1 -0
  27. package/dist/data-plane/server/index.js.map +1 -1
  28. package/dist/lexicons/app/bsky/ageassurance/defs.defs.d.ts +4 -0
  29. package/dist/lexicons/app/bsky/ageassurance/defs.defs.d.ts.map +1 -1
  30. package/dist/lexicons/app/bsky/ageassurance/defs.defs.js +3 -0
  31. package/dist/lexicons/app/bsky/ageassurance/defs.defs.js.map +1 -1
  32. package/dist/lexicons/chat/bsky/convo/defs.defs.js +1 -1
  33. package/dist/lexicons/chat/bsky/convo/defs.defs.js.map +1 -1
  34. package/dist/lexicons/chat/bsky/group/editGroup.defs.d.ts +4 -4
  35. package/dist/lexicons/chat/bsky/group/editGroup.defs.js +2 -2
  36. package/dist/lexicons/chat/bsky/group/editGroup.defs.js.map +1 -1
  37. package/dist/lexicons/chat/bsky/moderation/defs.defs.js +1 -1
  38. package/dist/lexicons/chat/bsky/moderation/defs.defs.js.map +1 -1
  39. package/package.json +9 -9
  40. package/src/api/age-assurance/const.ts +19 -0
  41. package/src/api/age-assurance/kws/age-verified.test.ts +98 -0
  42. package/src/api/age-assurance/kws/age-verified.ts +6 -0
  43. package/src/api/app/bsky/graph/muteActor.ts +3 -0
  44. package/src/api/kws/types.ts +11 -0
  45. package/src/api/kws/util.test.ts +50 -0
  46. package/src/data-plane/index.ts +0 -1
  47. package/src/data-plane/{bsync/index.ts → server/bsync-subscription.ts} +245 -121
  48. package/src/data-plane/server/index.ts +1 -0
  49. package/tests/data-plane/thread-mutes.test.ts +2 -0
  50. package/tests/stash.test.ts +6 -1
  51. package/tests/views/age-assurance-v2.test.ts +3 -0
  52. package/tests/views/age-assurance.test.ts +8 -0
  53. package/tests/views/bookmarks.test.ts +19 -0
  54. package/tests/views/drafts.test.ts +25 -2
  55. package/tests/views/mute-lists.test.ts +2 -0
  56. package/tests/views/mutes.test.ts +1 -0
  57. package/tests/views/notifications.test.ts +16 -0
  58. package/tsconfig.build.tsbuildinfo +1 -1
  59. package/dist/data-plane/bsync/index.d.ts +0 -11
  60. package/dist/data-plane/bsync/index.d.ts.map +0 -1
  61. package/dist/data-plane/bsync/index.js +0 -376
  62. package/dist/data-plane/bsync/index.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"defs.defs.js","sourceRoot":"","sources":["../../../../../src/lexicons/chat/bsky/moderation/defs.defs.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,cAAc,CAAA;AAChC,OAAO,KAAK,SAAS,MAAM,uBAAuB,CAAA;AAClD,OAAO,KAAK,SAAS,MAAM,uBAAuB,CAAA;AAElD,MAAM,KAAK,GAAG,2BAA2B,CAAA;AAEzC,OAAO,EAAE,KAAK,EAAE,CAAA;AAgBhB,6WAA6W;AAC7W,MAAM,SAAS,GAAG,aAAa,CAAC,CAAC,CAAC,WAAW,CAC3C,KAAK,EACL,WAAW;AACX,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC;IACrB,EAAE,EAAE,aAAa,CAAC,CAAC,CAAC,MAAM,EAAE;IAC5B,GAAG,EAAE,aAAa,CAAC,CAAC,CAAC,MAAM,EAAE;IAC7B,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,QAAQ;IAC5B,aAAa,CAAC,CAAC,CAAC,UAAU,CACxB;QACE,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAc,CAAC,GAAG,EAAE,CAAC,WAAW,CAAQ,CAAC;QACjE,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAa,CAAC,GAAG,EAAE,CAAC,UAAU,CAAQ,CAAC;KAChE,EACD,KAAK,CACN,CACF;CACF,CAAC,CACH,CAAA;AAED,OAAO,EAAE,SAAS,EAAE,CAAA;AAOpB,uEAAuE;AACvE,MAAM,WAAW,GAAG,aAAa,CAAC,CAAC,CAAC,WAAW,CAC7C,KAAK,EACL,aAAa;AACb,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAC3B,CAAA;AAED,OAAO,EAAE,WAAW,EAAE,CAAA;AAoCtB,kQAAkQ;AAClQ,MAAM,UAAU,GAAG,aAAa,CAAC,CAAC,CAAC,WAAW,CAC5C,KAAK,EACL,YAAY;AACZ,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC;IACrB,SAAS,EAAE,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IACzD,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,QAAQ;IAChC,aAAa,CAAC,CAAC,CAAC,GAAG,CACjB,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,YAAY,CAAQ,CACtC,CACF;IACD,gBAAgB,EAAE,aAAa,CAAC,CAAC,CAAC,OAAO,EAAE;IAC3C,UAAU,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,CAC7B,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,eAAe,CAAQ,CACzC;IACD,WAAW,EAAE,aAAa,CAAC,CAAC,CAAC,OAAO,EAAE;IACtC,WAAW,EAAE,aAAa,CAAC,CAAC,CAAC,OAAO,EAAE;IACtC,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,YAAY,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;CACrE,CAAC,CACH,CAAA;AAED,OAAO,EAAE,UAAU,EAAE,CAAA","sourcesContent":["/*\n * THIS FILE WAS GENERATED BY \"@atproto/lex\". DO NOT EDIT.\n */\n\nimport { l } from '@atproto/lex'\nimport * as GroupDefs from '../group/defs.defs.js'\nimport * as ConvoDefs from '../convo/defs.defs.js'\n\nconst $nsid = 'chat.bsky.moderation.defs'\n\nexport { $nsid }\n\n/** A view of a conversation for moderation purposes. Unlike chat.bsky.convo.defs#convoView, it does not include viewer-specific data (such as muted, unreadCount, status, lastMessage, lastReaction), since the requester is a moderator and not a member of the conversation. The member list is not included; use chat.bsky.moderation.getConvoMembers to list members. */\ntype ConvoView = {\n $type?: 'chat.bsky.moderation.defs#convoView'\n id: string\n rev: string\n\n /**\n * Union field that has data specific to different kinds of convos.\n */\n kind?: l.$Typed<DirectConvo> | l.$Typed<GroupConvo> | l.Unknown$TypedObject\n}\n\nexport type { ConvoView }\n\n/** A view of a conversation for moderation purposes. Unlike chat.bsky.convo.defs#convoView, it does not include viewer-specific data (such as muted, unreadCount, status, lastMessage, lastReaction), since the requester is a moderator and not a member of the conversation. The member list is not included; use chat.bsky.moderation.getConvoMembers to list members. */\nconst convoView = /*#__PURE__*/ l.typedObject<ConvoView>(\n $nsid,\n 'convoView',\n /*#__PURE__*/ l.object({\n id: /*#__PURE__*/ l.string(),\n rev: /*#__PURE__*/ l.string(),\n kind: /*#__PURE__*/ l.optional(\n /*#__PURE__*/ l.typedUnion(\n [\n /*#__PURE__*/ l.typedRef<DirectConvo>((() => directConvo) as any),\n /*#__PURE__*/ l.typedRef<GroupConvo>((() => groupConvo) as any),\n ],\n false,\n ),\n ),\n }),\n)\n\nexport { convoView }\n\n/** Data specific to a direct conversation, for moderation purposes. */\ntype DirectConvo = { $type?: 'chat.bsky.moderation.defs#directConvo' }\n\nexport type { DirectConvo }\n\n/** Data specific to a direct conversation, for moderation purposes. */\nconst directConvo = /*#__PURE__*/ l.typedObject<DirectConvo>(\n $nsid,\n 'directConvo',\n /*#__PURE__*/ l.object({}),\n)\n\nexport { directConvo }\n\n/** Data specific to a group conversation, for moderation purposes. Unlike chat.bsky.convo.defs#groupConvo, it does not include viewer-specific data (such as unreadJoinRequestCount), since the requester is a moderator and not a member of the conversation. */\ntype GroupConvo = {\n $type?: 'chat.bsky.moderation.defs#groupConvo'\n createdAt: l.DatetimeString\n joinLink?: GroupDefs.JoinLinkView\n\n /**\n * The total number of pending join requests for the group conversation. This information is only visible to the owner and to moderators. Capped at 21.\n */\n joinRequestCount: number\n\n /**\n * The lock status of the conversation.\n */\n lockStatus: ConvoDefs.ConvoLockStatus\n\n /**\n * The total number of members in the group conversation.\n */\n memberCount: number\n\n /**\n * The maximum number of members allowed in the group conversation.\n */\n memberLimit: number\n\n /**\n * The display name of the group conversation.\n */\n name: string\n}\n\nexport type { GroupConvo }\n\n/** Data specific to a group conversation, for moderation purposes. Unlike chat.bsky.convo.defs#groupConvo, it does not include viewer-specific data (such as unreadJoinRequestCount), since the requester is a moderator and not a member of the conversation. */\nconst groupConvo = /*#__PURE__*/ l.typedObject<GroupConvo>(\n $nsid,\n 'groupConvo',\n /*#__PURE__*/ l.object({\n createdAt: /*#__PURE__*/ l.string({ format: 'datetime' }),\n joinLink: /*#__PURE__*/ l.optional(\n /*#__PURE__*/ l.ref<GroupDefs.JoinLinkView>(\n (() => GroupDefs.joinLinkView) as any,\n ),\n ),\n joinRequestCount: /*#__PURE__*/ l.integer(),\n lockStatus: /*#__PURE__*/ l.ref<ConvoDefs.ConvoLockStatus>(\n (() => ConvoDefs.convoLockStatus) as any,\n ),\n memberCount: /*#__PURE__*/ l.integer(),\n memberLimit: /*#__PURE__*/ l.integer(),\n name: /*#__PURE__*/ l.string({ maxGraphemes: 128, maxLength: 1280 }),\n }),\n)\n\nexport { groupConvo }\n"]}
1
+ {"version":3,"file":"defs.defs.js","sourceRoot":"","sources":["../../../../../src/lexicons/chat/bsky/moderation/defs.defs.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,cAAc,CAAA;AAChC,OAAO,KAAK,SAAS,MAAM,uBAAuB,CAAA;AAClD,OAAO,KAAK,SAAS,MAAM,uBAAuB,CAAA;AAElD,MAAM,KAAK,GAAG,2BAA2B,CAAA;AAEzC,OAAO,EAAE,KAAK,EAAE,CAAA;AAgBhB,6WAA6W;AAC7W,MAAM,SAAS,GAAG,aAAa,CAAC,CAAC,CAAC,WAAW,CAC3C,KAAK,EACL,WAAW;AACX,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC;IACrB,EAAE,EAAE,aAAa,CAAC,CAAC,CAAC,MAAM,EAAE;IAC5B,GAAG,EAAE,aAAa,CAAC,CAAC,CAAC,MAAM,EAAE;IAC7B,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,QAAQ;IAC5B,aAAa,CAAC,CAAC,CAAC,UAAU,CACxB;QACE,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAc,CAAC,GAAG,EAAE,CAAC,WAAW,CAAQ,CAAC;QACjE,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAa,CAAC,GAAG,EAAE,CAAC,UAAU,CAAQ,CAAC;KAChE,EACD,KAAK,CACN,CACF;CACF,CAAC,CACH,CAAA;AAED,OAAO,EAAE,SAAS,EAAE,CAAA;AAOpB,uEAAuE;AACvE,MAAM,WAAW,GAAG,aAAa,CAAC,CAAC,CAAC,WAAW,CAC7C,KAAK,EACL,aAAa;AACb,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAC3B,CAAA;AAED,OAAO,EAAE,WAAW,EAAE,CAAA;AAoCtB,kQAAkQ;AAClQ,MAAM,UAAU,GAAG,aAAa,CAAC,CAAC,CAAC,WAAW,CAC5C,KAAK,EACL,YAAY;AACZ,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC;IACrB,SAAS,EAAE,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IACzD,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,QAAQ;IAChC,aAAa,CAAC,CAAC,CAAC,GAAG,CACjB,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,YAAY,CAAQ,CACtC,CACF;IACD,gBAAgB,EAAE,aAAa,CAAC,CAAC,CAAC,OAAO,EAAE;IAC3C,UAAU,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,CAC7B,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,eAAe,CAAQ,CACzC;IACD,WAAW,EAAE,aAAa,CAAC,CAAC,CAAC,OAAO,EAAE;IACtC,WAAW,EAAE,aAAa,CAAC,CAAC,CAAC,OAAO,EAAE;IACtC,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,YAAY,EAAE,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC;CACnE,CAAC,CACH,CAAA;AAED,OAAO,EAAE,UAAU,EAAE,CAAA","sourcesContent":["/*\n * THIS FILE WAS GENERATED BY \"@atproto/lex\". DO NOT EDIT.\n */\n\nimport { l } from '@atproto/lex'\nimport * as GroupDefs from '../group/defs.defs.js'\nimport * as ConvoDefs from '../convo/defs.defs.js'\n\nconst $nsid = 'chat.bsky.moderation.defs'\n\nexport { $nsid }\n\n/** A view of a conversation for moderation purposes. Unlike chat.bsky.convo.defs#convoView, it does not include viewer-specific data (such as muted, unreadCount, status, lastMessage, lastReaction), since the requester is a moderator and not a member of the conversation. The member list is not included; use chat.bsky.moderation.getConvoMembers to list members. */\ntype ConvoView = {\n $type?: 'chat.bsky.moderation.defs#convoView'\n id: string\n rev: string\n\n /**\n * Union field that has data specific to different kinds of convos.\n */\n kind?: l.$Typed<DirectConvo> | l.$Typed<GroupConvo> | l.Unknown$TypedObject\n}\n\nexport type { ConvoView }\n\n/** A view of a conversation for moderation purposes. Unlike chat.bsky.convo.defs#convoView, it does not include viewer-specific data (such as muted, unreadCount, status, lastMessage, lastReaction), since the requester is a moderator and not a member of the conversation. The member list is not included; use chat.bsky.moderation.getConvoMembers to list members. */\nconst convoView = /*#__PURE__*/ l.typedObject<ConvoView>(\n $nsid,\n 'convoView',\n /*#__PURE__*/ l.object({\n id: /*#__PURE__*/ l.string(),\n rev: /*#__PURE__*/ l.string(),\n kind: /*#__PURE__*/ l.optional(\n /*#__PURE__*/ l.typedUnion(\n [\n /*#__PURE__*/ l.typedRef<DirectConvo>((() => directConvo) as any),\n /*#__PURE__*/ l.typedRef<GroupConvo>((() => groupConvo) as any),\n ],\n false,\n ),\n ),\n }),\n)\n\nexport { convoView }\n\n/** Data specific to a direct conversation, for moderation purposes. */\ntype DirectConvo = { $type?: 'chat.bsky.moderation.defs#directConvo' }\n\nexport type { DirectConvo }\n\n/** Data specific to a direct conversation, for moderation purposes. */\nconst directConvo = /*#__PURE__*/ l.typedObject<DirectConvo>(\n $nsid,\n 'directConvo',\n /*#__PURE__*/ l.object({}),\n)\n\nexport { directConvo }\n\n/** Data specific to a group conversation, for moderation purposes. Unlike chat.bsky.convo.defs#groupConvo, it does not include viewer-specific data (such as unreadJoinRequestCount), since the requester is a moderator and not a member of the conversation. */\ntype GroupConvo = {\n $type?: 'chat.bsky.moderation.defs#groupConvo'\n createdAt: l.DatetimeString\n joinLink?: GroupDefs.JoinLinkView\n\n /**\n * The total number of pending join requests for the group conversation. This information is only visible to the owner and to moderators. Capped at 21.\n */\n joinRequestCount: number\n\n /**\n * The lock status of the conversation.\n */\n lockStatus: ConvoDefs.ConvoLockStatus\n\n /**\n * The total number of members in the group conversation.\n */\n memberCount: number\n\n /**\n * The maximum number of members allowed in the group conversation.\n */\n memberLimit: number\n\n /**\n * The display name of the group conversation.\n */\n name: string\n}\n\nexport type { GroupConvo }\n\n/** Data specific to a group conversation, for moderation purposes. Unlike chat.bsky.convo.defs#groupConvo, it does not include viewer-specific data (such as unreadJoinRequestCount), since the requester is a moderator and not a member of the conversation. */\nconst groupConvo = /*#__PURE__*/ l.typedObject<GroupConvo>(\n $nsid,\n 'groupConvo',\n /*#__PURE__*/ l.object({\n createdAt: /*#__PURE__*/ l.string({ format: 'datetime' }),\n joinLink: /*#__PURE__*/ l.optional(\n /*#__PURE__*/ l.ref<GroupDefs.JoinLinkView>(\n (() => GroupDefs.joinLinkView) as any,\n ),\n ),\n joinRequestCount: /*#__PURE__*/ l.integer(),\n lockStatus: /*#__PURE__*/ l.ref<ConvoDefs.ConvoLockStatus>(\n (() => ConvoDefs.convoLockStatus) as any,\n ),\n memberCount: /*#__PURE__*/ l.integer(),\n memberLimit: /*#__PURE__*/ l.integer(),\n name: /*#__PURE__*/ l.string({ maxGraphemes: 50, maxLength: 500 }),\n }),\n)\n\nexport { groupConvo }\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/bsky",
3
- "version": "0.0.249",
3
+ "version": "0.0.250",
4
4
  "license": "MIT",
5
5
  "description": "Reference implementation of app.bsky App View (Bluesky API)",
6
6
  "keywords": [
@@ -49,17 +49,17 @@
49
49
  "undici": "^8.5.0",
50
50
  "zod": "3.23.8",
51
51
  "@atproto-labs/fetch-node": "^0.3.3",
52
- "@atproto-labs/xrpc-utils": "^0.1.2",
53
- "@atproto/api": "^0.20.22",
52
+ "@atproto/api": "^0.20.23",
54
53
  "@atproto/common": "^0.6.4",
55
54
  "@atproto/crypto": "^0.5.2",
56
- "@atproto/sync": "^0.3.4",
57
- "@atproto/syntax": "^0.6.3",
58
- "@atproto/xrpc-server": "^0.11.3",
59
- "@atproto/lex": "^0.1.5",
60
55
  "@atproto/did": "^0.5.2",
56
+ "@atproto/identity": "^0.5.2",
57
+ "@atproto-labs/xrpc-utils": "^0.1.3",
58
+ "@atproto/lex": "^0.1.5",
61
59
  "@atproto/repo": "^0.10.2",
62
- "@atproto/identity": "^0.5.2"
60
+ "@atproto/sync": "^0.3.5",
61
+ "@atproto/xrpc-server": "^0.11.4",
62
+ "@atproto/syntax": "^0.6.3"
63
63
  },
64
64
  "devDependencies": {
65
65
  "@bufbuild/buf": "^1.28.1",
@@ -73,7 +73,7 @@
73
73
  "@types/qs": "^6.9.7",
74
74
  "ts-node": "^10.8.2",
75
75
  "vitest": "^4.0.16",
76
- "@atproto/pds": "^0.5.9"
76
+ "@atproto/pds": "^0.5.11"
77
77
  },
78
78
  "type": "module",
79
79
  "exports": {
@@ -158,6 +158,25 @@ export const AGE_ASSURANCE_CONFIG = app.bsky.ageassurance.defs.config.$build({
158
158
  }),
159
159
  ],
160
160
  },
161
+ {
162
+ countryCode: 'US',
163
+ regionCode: 'TX',
164
+ minAccessAge: 18,
165
+ // On-device age verification is permitted here in addition to the
166
+ // third-party (KWS) flow, which is always supported and remains as a
167
+ // fallback for platforms without the native age API (e.g. web) or
168
+ // insufficient device results.
169
+ additionalVerificationMethods: ['device'],
170
+ rules: [
171
+ app.bsky.ageassurance.defs.configRegionRuleIfAssuredOverAge.$build({
172
+ age: 18,
173
+ access: 'full',
174
+ }),
175
+ app.bsky.ageassurance.defs.configRegionRuleDefault.$build({
176
+ access: 'none',
177
+ }),
178
+ ],
179
+ },
161
180
  {
162
181
  countryCode: 'BR',
163
182
  regionCode: undefined,
@@ -0,0 +1,98 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import {
3
+ parseKWSAgeVerifiedStatus,
4
+ serializeKWSAgeVerifiedStatus,
5
+ } from './age-verified.js'
6
+
7
+ describe('parseKWSAgeVerifiedStatus', () => {
8
+ it('parses a minimal status object', () => {
9
+ const raw = JSON.stringify({ verified: true, verifiedMinimumAge: 18 })
10
+ expect(parseKWSAgeVerifiedStatus(raw)).toEqual({
11
+ verified: true,
12
+ verifiedMinimumAge: 18,
13
+ })
14
+ })
15
+
16
+ it('parses the optional transactionId and timestamp fields', () => {
17
+ const raw = JSON.stringify({
18
+ verified: true,
19
+ verifiedMinimumAge: 18,
20
+ transactionId: 'txn-123',
21
+ timestamp: 1750680000,
22
+ })
23
+ expect(parseKWSAgeVerifiedStatus(raw)).toEqual({
24
+ verified: true,
25
+ verifiedMinimumAge: 18,
26
+ transactionId: 'txn-123',
27
+ timestamp: 1750680000,
28
+ })
29
+ })
30
+
31
+ it('tolerates the new timestamp field without transactionId', () => {
32
+ const raw = JSON.stringify({
33
+ verified: true,
34
+ verifiedMinimumAge: 18,
35
+ timestamp: 1750680000,
36
+ })
37
+ expect(parseKWSAgeVerifiedStatus(raw)).toEqual({
38
+ verified: true,
39
+ verifiedMinimumAge: 18,
40
+ timestamp: 1750680000,
41
+ })
42
+ })
43
+
44
+ it('silently strips unknown fields KWS may add', () => {
45
+ const raw = JSON.stringify({
46
+ verified: true,
47
+ verifiedMinimumAge: 18,
48
+ timestamp: 1750680000,
49
+ errorCode: null,
50
+ somethingNew: 'ignored',
51
+ })
52
+ expect(parseKWSAgeVerifiedStatus(raw)).toEqual({
53
+ verified: true,
54
+ verifiedMinimumAge: 18,
55
+ timestamp: 1750680000,
56
+ errorCode: null,
57
+ })
58
+ })
59
+
60
+ it('strips a truly unknown field', () => {
61
+ const raw = JSON.stringify({
62
+ verified: true,
63
+ verifiedMinimumAge: 18,
64
+ somethingNew: 'ignored',
65
+ })
66
+ expect(parseKWSAgeVerifiedStatus(raw)).toEqual({
67
+ verified: true,
68
+ verifiedMinimumAge: 18,
69
+ })
70
+ })
71
+
72
+ it('throws on malformed JSON', () => {
73
+ expect(() => parseKWSAgeVerifiedStatus('not json')).toThrow(
74
+ /Invalid KWS age-verified status/,
75
+ )
76
+ })
77
+
78
+ it('throws when a required field is missing', () => {
79
+ const raw = JSON.stringify({ verified: true })
80
+ expect(() => parseKWSAgeVerifiedStatus(raw)).toThrow(
81
+ /Invalid KWS age-verified status/,
82
+ )
83
+ })
84
+ })
85
+
86
+ describe('serializeKWSAgeVerifiedStatus', () => {
87
+ it('round-trips a status object including timestamp', () => {
88
+ const status = {
89
+ verified: true,
90
+ verifiedMinimumAge: 18,
91
+ transactionId: 'txn-123',
92
+ timestamp: 1750680000,
93
+ }
94
+ expect(
95
+ parseKWSAgeVerifiedStatus(serializeKWSAgeVerifiedStatus(status)),
96
+ ).toEqual(status)
97
+ })
98
+ })
@@ -7,6 +7,12 @@ export const KWSAgeVerifiedStatusSchema = z.object({
7
7
  verified: z.boolean(),
8
8
  verifiedMinimumAge: z.number(),
9
9
  transactionId: z.string().optional(),
10
+ // A code indicating the reason for failure, or null when verification
11
+ // succeeded.
12
+ errorCode: z.string().nullable().optional(),
13
+ // Epoch seconds indicating when KWS generated the payload. Optional so we
14
+ // remain compatible with payloads sent before this field was introduced.
15
+ timestamp: z.number().optional(),
10
16
  })
11
17
 
12
18
  /**
@@ -11,6 +11,9 @@ export default function (server: Server, ctx: AppContext) {
11
11
  const requester = auth.credentials.iss
12
12
  const [did] = await ctx.hydrator.actor.getDids([actor])
13
13
  if (!did) throw new InvalidRequestError('Actor not found')
14
+ if (requester === did) {
15
+ throw new InvalidRequestError('Actor cannot mute themselves')
16
+ }
14
17
  await ctx.bsyncClient.addMuteOperation({
15
18
  type: MuteOperation_Type.ADD,
16
19
  actorDid: requester,
@@ -25,6 +25,14 @@ export const externalPayloadSchema = z
25
25
 
26
26
  export type KwsStatus = {
27
27
  verified: boolean
28
+ // A unique reference ID for the verification transaction.
29
+ transactionId?: string
30
+ // A code indicating the reason for failure, or null when verification
31
+ // succeeded.
32
+ errorCode?: string | null
33
+ // Epoch seconds indicating when KWS generated the payload. Optional so we
34
+ // remain compatible with payloads sent before this field was introduced.
35
+ timestamp?: number
28
36
  }
29
37
 
30
38
  export type KwsVerificationIntermediateQuery = {
@@ -56,6 +64,9 @@ export type KwsWebhookBody = {
56
64
  // Not `.strict()` to avoid breaking if KWS adds fields.
57
65
  export const statusSchema = z.object({
58
66
  verified: z.boolean(),
67
+ transactionId: z.string().optional(),
68
+ errorCode: z.string().nullable().optional(),
69
+ timestamp: z.number().optional(),
59
70
  })
60
71
 
61
72
  // Not `.strict()` to avoid breaking if KWS adds fields.
@@ -0,0 +1,50 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { parseStatus } from './util.js'
3
+
4
+ describe('parseStatus', () => {
5
+ it('parses a minimal status object', () => {
6
+ const raw = JSON.stringify({ verified: true })
7
+ expect(parseStatus(raw)).toEqual({ verified: true })
8
+ })
9
+
10
+ it('parses the optional transactionId, errorCode, and timestamp fields', () => {
11
+ const raw = JSON.stringify({
12
+ verified: true,
13
+ transactionId: 'txn-123',
14
+ errorCode: null,
15
+ timestamp: 1750680000,
16
+ })
17
+ expect(parseStatus(raw)).toEqual({
18
+ verified: true,
19
+ transactionId: 'txn-123',
20
+ errorCode: null,
21
+ timestamp: 1750680000,
22
+ })
23
+ })
24
+
25
+ it('parses a non-null errorCode', () => {
26
+ const raw = JSON.stringify({ verified: false, errorCode: 'some-failure' })
27
+ expect(parseStatus(raw)).toEqual({
28
+ verified: false,
29
+ errorCode: 'some-failure',
30
+ })
31
+ })
32
+
33
+ it('silently strips unknown fields KWS may add', () => {
34
+ const raw = JSON.stringify({
35
+ verified: true,
36
+ timestamp: 1750680000,
37
+ somethingNew: 'ignored',
38
+ })
39
+ expect(parseStatus(raw)).toEqual({ verified: true, timestamp: 1750680000 })
40
+ })
41
+
42
+ it('throws on malformed JSON', () => {
43
+ expect(() => parseStatus('not json')).toThrow(/Invalid status/)
44
+ })
45
+
46
+ it('throws when verified is missing', () => {
47
+ const raw = JSON.stringify({ timestamp: 1750680000 })
48
+ expect(() => parseStatus(raw)).toThrow(/Invalid status/)
49
+ })
50
+ })
@@ -1,3 +1,2 @@
1
1
  export * from './server/index.js'
2
2
  export * from './client/index.js'
3
- export * from './bsync/index.js'