@atproto/api 0.18.3 → 0.18.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/age-assurance.d.ts +29 -0
  3. package/dist/age-assurance.d.ts.map +1 -0
  4. package/dist/age-assurance.js +95 -0
  5. package/dist/age-assurance.js.map +1 -0
  6. package/dist/age-assurance.test.d.ts +2 -0
  7. package/dist/age-assurance.test.d.ts.map +1 -0
  8. package/dist/age-assurance.test.js +186 -0
  9. package/dist/age-assurance.test.js.map +1 -0
  10. package/dist/agent.d.ts +1 -1
  11. package/dist/agent.d.ts.map +1 -1
  12. package/dist/client/index.d.ts +27 -0
  13. package/dist/client/index.d.ts.map +1 -1
  14. package/dist/client/index.js +91 -8
  15. package/dist/client/index.js.map +1 -1
  16. package/dist/client/lexicons.d.ts +668 -0
  17. package/dist/client/lexicons.d.ts.map +1 -1
  18. package/dist/client/lexicons.js +356 -0
  19. package/dist/client/lexicons.js.map +1 -1
  20. package/dist/client/types/app/bsky/contact/defs.d.ts +24 -0
  21. package/dist/client/types/app/bsky/contact/defs.d.ts.map +1 -0
  22. package/dist/client/types/app/bsky/contact/defs.js +25 -0
  23. package/dist/client/types/app/bsky/contact/defs.js.map +1 -0
  24. package/dist/client/types/app/bsky/contact/dismissMatch.d.ts +27 -0
  25. package/dist/client/types/app/bsky/contact/dismissMatch.d.ts.map +1 -0
  26. package/dist/client/types/app/bsky/contact/dismissMatch.js +26 -0
  27. package/dist/client/types/app/bsky/contact/dismissMatch.js.map +1 -0
  28. package/dist/client/types/app/bsky/contact/getMatches.d.ts +28 -0
  29. package/dist/client/types/app/bsky/contact/getMatches.d.ts.map +1 -0
  30. package/dist/client/types/app/bsky/contact/getMatches.js +26 -0
  31. package/dist/client/types/app/bsky/contact/getMatches.js.map +1 -0
  32. package/dist/client/types/app/bsky/contact/getSyncStatus.d.ts +24 -0
  33. package/dist/client/types/app/bsky/contact/getSyncStatus.d.ts.map +1 -0
  34. package/dist/client/types/app/bsky/contact/getSyncStatus.js +26 -0
  35. package/dist/client/types/app/bsky/contact/getSyncStatus.js.map +1 -0
  36. package/dist/client/types/app/bsky/contact/importContacts.d.ts +32 -0
  37. package/dist/client/types/app/bsky/contact/importContacts.d.ts.map +1 -0
  38. package/dist/client/types/app/bsky/contact/importContacts.js +26 -0
  39. package/dist/client/types/app/bsky/contact/importContacts.js.map +1 -0
  40. package/dist/client/types/app/bsky/contact/removeData.d.ts +25 -0
  41. package/dist/client/types/app/bsky/contact/removeData.d.ts.map +1 -0
  42. package/dist/client/types/app/bsky/contact/removeData.js +26 -0
  43. package/dist/client/types/app/bsky/contact/removeData.js.map +1 -0
  44. package/dist/client/types/app/bsky/contact/startPhoneVerification.d.ts +27 -0
  45. package/dist/client/types/app/bsky/contact/startPhoneVerification.d.ts.map +1 -0
  46. package/dist/client/types/app/bsky/contact/startPhoneVerification.js +26 -0
  47. package/dist/client/types/app/bsky/contact/startPhoneVerification.js.map +1 -0
  48. package/dist/client/types/app/bsky/contact/verifyPhone.d.ts +31 -0
  49. package/dist/client/types/app/bsky/contact/verifyPhone.d.ts.map +1 -0
  50. package/dist/client/types/app/bsky/contact/verifyPhone.js +26 -0
  51. package/dist/client/types/app/bsky/contact/verifyPhone.js.map +1 -0
  52. package/dist/client/types/app/bsky/graph/defs.d.ts +8 -0
  53. package/dist/client/types/app/bsky/graph/defs.d.ts.map +1 -1
  54. package/dist/client/types/app/bsky/graph/defs.js.map +1 -1
  55. package/dist/index.d.ts +1 -0
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +1 -0
  58. package/dist/index.js.map +1 -1
  59. package/package.json +7 -6
  60. package/src/age-assurance.test.ts +213 -0
  61. package/src/age-assurance.ts +137 -0
  62. package/src/client/index.ts +103 -0
  63. package/src/client/lexicons.ts +377 -0
  64. package/src/client/types/app/bsky/contact/defs.ts +52 -0
  65. package/src/client/types/app/bsky/contact/dismissMatch.ts +52 -0
  66. package/src/client/types/app/bsky/contact/getMatches.ts +53 -0
  67. package/src/client/types/app/bsky/contact/getSyncStatus.ts +49 -0
  68. package/src/client/types/app/bsky/contact/importContacts.ts +58 -0
  69. package/src/client/types/app/bsky/contact/removeData.ts +49 -0
  70. package/src/client/types/app/bsky/contact/startPhoneVerification.ts +52 -0
  71. package/src/client/types/app/bsky/contact/verifyPhone.ts +57 -0
  72. package/src/client/types/app/bsky/graph/defs.ts +8 -0
  73. package/src/index.ts +1 -0
  74. package/tests/atp-agent.test.ts +1 -1
  75. package/tsconfig.build.tsbuildinfo +1 -1
package/dist/index.d.ts CHANGED
@@ -16,6 +16,7 @@ export * from './rich-text/util';
16
16
  export * from './moderation';
17
17
  export * from './moderation/types';
18
18
  export * from './mocker';
19
+ export * from './age-assurance';
19
20
  export { DEFAULT_LABEL_SETTINGS, LABELS } from './moderation/const/labels';
20
21
  export { Agent } from './agent';
21
22
  export { AtpAgent, type AtpAgentOptions } from './atp-agent';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAG3C,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAA;AACvC,OAAO,EACL,OAAO,EACP,eAAe,EACf,SAAS,EACT,SAAS,EACT,YAAY,GACb,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,cAAc,SAAS,CAAA;AACvB,cAAc,SAAS,CAAA;AACvB,cAAc,QAAQ,CAAA;AACtB,cAAc,UAAU,CAAA;AACxB,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAC3C,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,cAAc,uBAAuB,CAAA;AACrC,cAAc,0BAA0B,CAAA;AACxC,cAAc,qBAAqB,CAAA;AACnC,cAAc,kBAAkB,CAAA;AAChC,cAAc,cAAc,CAAA;AAC5B,cAAc,oBAAoB,CAAA;AAClC,cAAc,UAAU,CAAA;AACxB,OAAO,EAAE,sBAAsB,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAA;AAC1E,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAE/B,OAAO,EAAE,QAAQ,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAA;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAExC,OAAO;AACL,kBAAkB;AAClB,QAAQ,IAAI,OAAO,GACpB,MAAM,aAAa,CAAA;AAIpB,eAAO,MAAM,QAAQ,UAAiC,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAG3C,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAA;AACvC,OAAO,EACL,OAAO,EACP,eAAe,EACf,SAAS,EACT,SAAS,EACT,YAAY,GACb,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,cAAc,SAAS,CAAA;AACvB,cAAc,SAAS,CAAA;AACvB,cAAc,QAAQ,CAAA;AACtB,cAAc,UAAU,CAAA;AACxB,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAC3C,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,cAAc,uBAAuB,CAAA;AACrC,cAAc,0BAA0B,CAAA;AACxC,cAAc,qBAAqB,CAAA;AACnC,cAAc,kBAAkB,CAAA;AAChC,cAAc,cAAc,CAAA;AAC5B,cAAc,oBAAoB,CAAA;AAClC,cAAc,UAAU,CAAA;AACxB,cAAc,iBAAiB,CAAA;AAC/B,OAAO,EAAE,sBAAsB,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAA;AAC1E,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAE/B,OAAO,EAAE,QAAQ,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAA;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAExC,OAAO;AACL,kBAAkB;AAClB,QAAQ,IAAI,OAAO,GACpB,MAAM,aAAa,CAAA;AAIpB,eAAO,MAAM,QAAQ,UAAiC,CAAA"}
package/dist/index.js CHANGED
@@ -42,6 +42,7 @@ __exportStar(require("./rich-text/util"), exports);
42
42
  __exportStar(require("./moderation"), exports);
43
43
  __exportStar(require("./moderation/types"), exports);
44
44
  __exportStar(require("./mocker"), exports);
45
+ __exportStar(require("./age-assurance"), exports);
45
46
  var labels_1 = require("./moderation/const/labels");
46
47
  Object.defineProperty(exports, "DEFAULT_LABEL_SETTINGS", { enumerable: true, get: function () { return labels_1.DEFAULT_LABEL_SETTINGS; } });
47
48
  Object.defineProperty(exports, "LABELS", { enumerable: true, get: function () { return labels_1.LABELS; } });
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,8CAA2C;AAC3C,gDAAgE;AAEhE,0CAAuC;AAA9B,+FAAA,KAAK,OAAA;AACd,4CAMyB;AALvB,kGAAA,OAAO,OAAA;AACP,0GAAA,eAAe,OAAA;AACf,oGAAA,SAAS,OAAA;AACT,oGAAA,SAAS,OAAA;AACT,uGAAA,YAAY,OAAA;AAEd,kDAAmD;AAA1C,2GAAA,aAAa,OAAA;AACtB,0CAAuB;AACvB,0CAAuB;AACvB,yCAAsB;AACtB,2CAAwB;AACxB,8CAA2C;AAAlC,mGAAA,OAAO,OAAA;AAEhB,sCAA2C;AAAlC,mGAAA,WAAW,OAAA;AACpB,wDAAqC;AACrC,2DAAwC;AACxC,sDAAmC;AACnC,mDAAgC;AAChC,+CAA4B;AAC5B,qDAAkC;AAClC,2CAAwB;AACxB,oDAA0E;AAAjE,gHAAA,sBAAsB,OAAA;AAAE,gGAAA,MAAM,OAAA;AACvC,iCAA+B;AAAtB,8FAAA,KAAK,OAAA;AAEd,yCAA4D;AAAnD,qGAAA,QAAQ,OAAA;AACjB,yCAA+C;AAAtC,8GAAA,iBAAiB,OAAA;AAC1B,2CAAwC;AAA/B,uGAAA,SAAS,OAAA;AAElB,yCAGoB;AAFlB,kBAAkB;AAClB,oGAAA,QAAQ,OAAW;AAGrB,+EAA+E;AAC/E,2BAA2B;AACd,QAAA,QAAQ,GAAG,IAAI,kBAAQ,CAAC,mBAAgB,CAAC,CAAA","sourcesContent":["import { Lexicons } from '@atproto/lexicon'\nimport { lexicons as internalLexicons } from './client/lexicons'\n\nexport { AtUri } from '@atproto/syntax'\nexport {\n BlobRef,\n jsonStringToLex,\n jsonToLex,\n lexToJson,\n stringifyLex,\n} from '@atproto/lexicon'\nexport { parseLanguage } from '@atproto/common-web'\nexport * from './types'\nexport * from './const'\nexport * from './util'\nexport * from './client'\nexport { schemas } from './client/lexicons'\nexport type { $Typed, Un$Typed } from './client/util'\nexport { asPredicate } from './client/util'\nexport * from './rich-text/rich-text'\nexport * from './rich-text/sanitization'\nexport * from './rich-text/unicode'\nexport * from './rich-text/util'\nexport * from './moderation'\nexport * from './moderation/types'\nexport * from './mocker'\nexport { DEFAULT_LABEL_SETTINGS, LABELS } from './moderation/const/labels'\nexport { Agent } from './agent'\n\nexport { AtpAgent, type AtpAgentOptions } from './atp-agent'\nexport { CredentialSession } from './atp-agent'\nexport { BskyAgent } from './bsky-agent'\n\nexport {\n /** @deprecated */\n AtpAgent as default,\n} from './atp-agent'\n\n// Expose a copy to prevent alteration of the internal Lexicon instance used by\n// the AtpBaseClient class.\nexport const lexicons = new Lexicons(internalLexicons)\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,8CAA2C;AAC3C,gDAAgE;AAEhE,0CAAuC;AAA9B,+FAAA,KAAK,OAAA;AACd,4CAMyB;AALvB,kGAAA,OAAO,OAAA;AACP,0GAAA,eAAe,OAAA;AACf,oGAAA,SAAS,OAAA;AACT,oGAAA,SAAS,OAAA;AACT,uGAAA,YAAY,OAAA;AAEd,kDAAmD;AAA1C,2GAAA,aAAa,OAAA;AACtB,0CAAuB;AACvB,0CAAuB;AACvB,yCAAsB;AACtB,2CAAwB;AACxB,8CAA2C;AAAlC,mGAAA,OAAO,OAAA;AAEhB,sCAA2C;AAAlC,mGAAA,WAAW,OAAA;AACpB,wDAAqC;AACrC,2DAAwC;AACxC,sDAAmC;AACnC,mDAAgC;AAChC,+CAA4B;AAC5B,qDAAkC;AAClC,2CAAwB;AACxB,kDAA+B;AAC/B,oDAA0E;AAAjE,gHAAA,sBAAsB,OAAA;AAAE,gGAAA,MAAM,OAAA;AACvC,iCAA+B;AAAtB,8FAAA,KAAK,OAAA;AAEd,yCAA4D;AAAnD,qGAAA,QAAQ,OAAA;AACjB,yCAA+C;AAAtC,8GAAA,iBAAiB,OAAA;AAC1B,2CAAwC;AAA/B,uGAAA,SAAS,OAAA;AAElB,yCAGoB;AAFlB,kBAAkB;AAClB,oGAAA,QAAQ,OAAW;AAGrB,+EAA+E;AAC/E,2BAA2B;AACd,QAAA,QAAQ,GAAG,IAAI,kBAAQ,CAAC,mBAAgB,CAAC,CAAA","sourcesContent":["import { Lexicons } from '@atproto/lexicon'\nimport { lexicons as internalLexicons } from './client/lexicons'\n\nexport { AtUri } from '@atproto/syntax'\nexport {\n BlobRef,\n jsonStringToLex,\n jsonToLex,\n lexToJson,\n stringifyLex,\n} from '@atproto/lexicon'\nexport { parseLanguage } from '@atproto/common-web'\nexport * from './types'\nexport * from './const'\nexport * from './util'\nexport * from './client'\nexport { schemas } from './client/lexicons'\nexport type { $Typed, Un$Typed } from './client/util'\nexport { asPredicate } from './client/util'\nexport * from './rich-text/rich-text'\nexport * from './rich-text/sanitization'\nexport * from './rich-text/unicode'\nexport * from './rich-text/util'\nexport * from './moderation'\nexport * from './moderation/types'\nexport * from './mocker'\nexport * from './age-assurance'\nexport { DEFAULT_LABEL_SETTINGS, LABELS } from './moderation/const/labels'\nexport { Agent } from './agent'\n\nexport { AtpAgent, type AtpAgentOptions } from './atp-agent'\nexport { CredentialSession } from './atp-agent'\nexport { BskyAgent } from './bsky-agent'\n\nexport {\n /** @deprecated */\n AtpAgent as default,\n} from './atp-agent'\n\n// Expose a copy to prevent alteration of the internal Lexicon instance used by\n// the AtpBaseClient class.\nexport const lexicons = new Lexicons(internalLexicons)\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/api",
3
- "version": "0.18.3",
3
+ "version": "0.18.5",
4
4
  "license": "MIT",
5
5
  "description": "Client library for atproto and Bluesky",
6
6
  "keywords": [
@@ -21,16 +21,17 @@
21
21
  "multiformats": "^9.9.0",
22
22
  "tlds": "^1.234.0",
23
23
  "zod": "^3.23.8",
24
- "@atproto/common-web": "^0.4.5",
25
- "@atproto/lexicon": "^0.5.2",
26
- "@atproto/syntax": "^0.4.1",
27
- "@atproto/xrpc": "^0.7.6"
24
+ "@atproto/common-web": "^0.4.7",
25
+ "@atproto/lexicon": "^0.6.0",
26
+ "@atproto/syntax": "^0.4.2",
27
+ "@atproto/xrpc": "^0.7.7"
28
28
  },
29
29
  "devDependencies": {
30
+ "@jest/globals": "^28.1.3",
30
31
  "jest": "^28.1.2",
31
32
  "prettier": "^3.2.5",
32
33
  "typescript": "^5.6.3",
33
- "@atproto/lex-cli": "^0.9.7"
34
+ "@atproto/lex-cli": "^0.9.8"
34
35
  },
35
36
  "scripts": {
36
37
  "codegen": "node ./scripts/generate-code.mjs && lex gen-api --yes ./src/client ../../lexicons/com/atproto/*/* ../../lexicons/app/bsky/*/* ../../lexicons/chat/bsky/*/* ../../lexicons/tools/ozone/*/*",
@@ -0,0 +1,213 @@
1
+ import { describe, expect, it } from '@jest/globals'
2
+ import {
3
+ ageAssuranceRuleIDs,
4
+ computeAgeAssuranceRegionAccess,
5
+ getAgeAssuranceRegionConfig,
6
+ } from './age-assurance'
7
+ import { AppBskyAgeassuranceDefs } from './client'
8
+
9
+ describe('age-assurance', () => {
10
+ describe('getAgeAssuranceRegionConfig', () => {
11
+ const config: AppBskyAgeassuranceDefs.Config = {
12
+ regions: [
13
+ {
14
+ countryCode: 'US',
15
+ regionCode: 'CA',
16
+ rules: [],
17
+ },
18
+ {
19
+ countryCode: 'US',
20
+ rules: [],
21
+ },
22
+ ],
23
+ }
24
+
25
+ it('should find region by country code only', () => {
26
+ const result = getAgeAssuranceRegionConfig(config, {
27
+ countryCode: 'US',
28
+ })
29
+
30
+ expect(result).toEqual({
31
+ countryCode: 'US',
32
+ rules: [],
33
+ })
34
+ })
35
+
36
+ it('should find region by country code and region code', () => {
37
+ const result = getAgeAssuranceRegionConfig(config, {
38
+ countryCode: 'US',
39
+ regionCode: 'CA',
40
+ })
41
+
42
+ expect(result).toEqual({
43
+ countryCode: 'US',
44
+ regionCode: 'CA',
45
+ rules: [],
46
+ })
47
+ })
48
+
49
+ it('should return undefined when no matching region found', () => {
50
+ const result = getAgeAssuranceRegionConfig(config, {
51
+ countryCode: 'GB',
52
+ })
53
+
54
+ expect(result).toBeUndefined()
55
+ })
56
+ })
57
+
58
+ describe('computeAgeAssuranceRegionAccess', () => {
59
+ const region: AppBskyAgeassuranceDefs.ConfigRegion = {
60
+ countryCode: 'US',
61
+ rules: [
62
+ {
63
+ $type: ageAssuranceRuleIDs.IfAccountNewerThan,
64
+ date: '2025-12-10T00:00:00Z',
65
+ access: 'none',
66
+ },
67
+ {
68
+ $type: ageAssuranceRuleIDs.IfAssuredOverAge,
69
+ age: 18,
70
+ access: 'full',
71
+ },
72
+ {
73
+ $type: ageAssuranceRuleIDs.IfAssuredOverAge,
74
+ age: 16,
75
+ access: 'safe',
76
+ },
77
+ {
78
+ $type: ageAssuranceRuleIDs.IfDeclaredOverAge,
79
+ age: 16,
80
+ access: 'safe',
81
+ },
82
+ {
83
+ $type: ageAssuranceRuleIDs.Default,
84
+ access: 'none',
85
+ },
86
+ ],
87
+ }
88
+
89
+ it('should apply default if no data provided', () => {
90
+ const result = computeAgeAssuranceRegionAccess(region, {})
91
+
92
+ expect(result).toEqual({
93
+ access: 'none',
94
+ reason: ageAssuranceRuleIDs.Default,
95
+ })
96
+ })
97
+
98
+ describe('IfAccountNewerThan', () => {
99
+ it('should block accounts created after threshold', () => {
100
+ const result = computeAgeAssuranceRegionAccess(region, {
101
+ accountCreatedAt: new Date(2025, 11, 15).toISOString(),
102
+ declaredAge: 18,
103
+ })
104
+ expect(result).toEqual({
105
+ access: 'none',
106
+ reason: ageAssuranceRuleIDs.IfAccountNewerThan,
107
+ })
108
+ })
109
+
110
+ it('should allow accounts created before threshold', () => {
111
+ const result = computeAgeAssuranceRegionAccess(region, {
112
+ accountCreatedAt: new Date(2025, 10, 1).toISOString(),
113
+ declaredAge: 18,
114
+ })
115
+ expect(result).toEqual({
116
+ access: 'safe',
117
+ reason: ageAssuranceRuleIDs.IfDeclaredOverAge,
118
+ })
119
+ })
120
+
121
+ it('should allow accounts created exactly at threshold', () => {
122
+ const result = computeAgeAssuranceRegionAccess(region, {
123
+ accountCreatedAt: new Date(2025, 11, 1).toISOString(),
124
+ declaredAge: 18,
125
+ })
126
+ expect(result).toEqual({
127
+ access: 'safe',
128
+ reason: ageAssuranceRuleIDs.IfDeclaredOverAge,
129
+ })
130
+ })
131
+
132
+ it('should not apply rule when accountCreatedAt is not provided', () => {
133
+ const result = computeAgeAssuranceRegionAccess(region, {
134
+ declaredAge: 15,
135
+ })
136
+ expect(result).toEqual({
137
+ access: 'none',
138
+ reason: ageAssuranceRuleIDs.Default,
139
+ })
140
+ })
141
+
142
+ it('should not apply rule when assuredAge is present', () => {
143
+ const result = computeAgeAssuranceRegionAccess(region, {
144
+ accountCreatedAt: new Date(2025, 11, 15).toISOString(),
145
+ assuredAge: 20,
146
+ })
147
+ expect(result).toEqual({
148
+ access: 'full',
149
+ reason: ageAssuranceRuleIDs.IfAssuredOverAge,
150
+ })
151
+ })
152
+ })
153
+
154
+ describe('IfDeclaredOverAge rule', () => {
155
+ it('should allow users at or above age threshold', () => {
156
+ const result = computeAgeAssuranceRegionAccess(region, {
157
+ declaredAge: 18,
158
+ })
159
+
160
+ expect(result).toEqual({
161
+ access: 'safe',
162
+ reason: ageAssuranceRuleIDs.IfDeclaredOverAge,
163
+ })
164
+ })
165
+
166
+ it('should allow users above age threshold', () => {
167
+ const result = computeAgeAssuranceRegionAccess(region, {
168
+ declaredAge: 25,
169
+ })
170
+
171
+ expect(result).toEqual({
172
+ access: 'safe',
173
+ reason: ageAssuranceRuleIDs.IfDeclaredOverAge,
174
+ })
175
+ })
176
+
177
+ it('should not allow users below age threshold', () => {
178
+ const result = computeAgeAssuranceRegionAccess(region, {
179
+ declaredAge: 17,
180
+ })
181
+
182
+ expect(result).toEqual({
183
+ access: 'safe',
184
+ reason: ageAssuranceRuleIDs.IfDeclaredOverAge,
185
+ })
186
+ })
187
+ })
188
+
189
+ describe('IfAssuredOverAge rule', () => {
190
+ it('should allow users at or above assured age threshold', () => {
191
+ const result = computeAgeAssuranceRegionAccess(region, {
192
+ assuredAge: 18,
193
+ })
194
+
195
+ expect(result).toEqual({
196
+ access: 'full',
197
+ reason: ageAssuranceRuleIDs.IfAssuredOverAge,
198
+ })
199
+ })
200
+
201
+ it('should not allow users below assured age threshold', () => {
202
+ const result = computeAgeAssuranceRegionAccess(region, {
203
+ assuredAge: 17,
204
+ })
205
+
206
+ expect(result).toEqual({
207
+ access: 'safe',
208
+ reason: ageAssuranceRuleIDs.IfAssuredOverAge,
209
+ })
210
+ })
211
+ })
212
+ })
213
+ })
@@ -0,0 +1,137 @@
1
+ import { AppBskyAgeassuranceDefs } from './client'
2
+ import { ids } from './client/lexicons'
3
+
4
+ export type AgeAssuranceRuleID = Exclude<
5
+ | AppBskyAgeassuranceDefs.ConfigRegionRuleDefault['$type']
6
+ | AppBskyAgeassuranceDefs.ConfigRegionRuleIfDeclaredOverAge['$type']
7
+ | AppBskyAgeassuranceDefs.ConfigRegionRuleIfDeclaredUnderAge['$type']
8
+ | AppBskyAgeassuranceDefs.ConfigRegionRuleIfAssuredOverAge['$type']
9
+ | AppBskyAgeassuranceDefs.ConfigRegionRuleIfAssuredUnderAge['$type']
10
+ | AppBskyAgeassuranceDefs.ConfigRegionRuleIfAccountNewerThan['$type']
11
+ | AppBskyAgeassuranceDefs.ConfigRegionRuleIfAccountOlderThan['$type'],
12
+ undefined
13
+ >
14
+
15
+ export const ageAssuranceRuleIDs: Record<string, AgeAssuranceRuleID> = {
16
+ Default: `${ids.AppBskyAgeassuranceDefs}#configRegionRuleDefault`,
17
+ IfDeclaredOverAge: `${ids.AppBskyAgeassuranceDefs}#configRegionRuleIfDeclaredOverAge`,
18
+ IfDeclaredUnderAge: `${ids.AppBskyAgeassuranceDefs}#configRegionRuleIfDeclaredUnderAge`,
19
+ IfAssuredOverAge: `${ids.AppBskyAgeassuranceDefs}#configRegionRuleIfAssuredOverAge`,
20
+ IfAssuredUnderAge: `${ids.AppBskyAgeassuranceDefs}#configRegionRuleIfAssuredUnderAge`,
21
+ IfAccountNewerThan: `${ids.AppBskyAgeassuranceDefs}#configRegionRuleIfAccountNewerThan`,
22
+ IfAccountOlderThan: `${ids.AppBskyAgeassuranceDefs}#configRegionRuleIfAccountOlderThan`,
23
+ }
24
+
25
+ /**
26
+ * Returns the first matched region configuration based on the provided geolocation.
27
+ */
28
+ export function getAgeAssuranceRegionConfig(
29
+ config: AppBskyAgeassuranceDefs.Config,
30
+ geolocation: {
31
+ countryCode: string
32
+ regionCode?: string
33
+ },
34
+ ): AppBskyAgeassuranceDefs.ConfigRegion | undefined {
35
+ const { regions } = config
36
+ return regions.find(({ countryCode, regionCode }) => {
37
+ if (countryCode === geolocation.countryCode) {
38
+ return !regionCode || regionCode === geolocation.regionCode
39
+ }
40
+ })
41
+ }
42
+
43
+ export function computeAgeAssuranceRegionAccess(
44
+ region: AppBskyAgeassuranceDefs.ConfigRegion,
45
+ data:
46
+ | {
47
+ /**
48
+ * The account creation date in ISO 8601 format. Only checked if we
49
+ * don't have an assured age, such as on the client.
50
+ */
51
+ accountCreatedAt?: string
52
+ /**
53
+ * The user's declared age
54
+ */
55
+ declaredAge?: number
56
+ /**
57
+ * The user's minimum age as assured by a trusted third party.
58
+ */
59
+ assuredAge?: number
60
+ }
61
+ | undefined,
62
+ ):
63
+ | {
64
+ access: AppBskyAgeassuranceDefs.Access
65
+ reason: AgeAssuranceRuleID
66
+ }
67
+ | undefined {
68
+ // first match wins
69
+ for (const rule of region.rules) {
70
+ if (AppBskyAgeassuranceDefs.isConfigRegionRuleIfAccountNewerThan(rule)) {
71
+ if (data?.accountCreatedAt && !data?.assuredAge) {
72
+ const accountCreatedAt = new Date(data.accountCreatedAt)
73
+ const threshold = new Date(rule.date)
74
+ if (accountCreatedAt >= threshold) {
75
+ return {
76
+ access: rule.access,
77
+ reason: rule.$type,
78
+ }
79
+ }
80
+ }
81
+ } else if (
82
+ AppBskyAgeassuranceDefs.isConfigRegionRuleIfAccountOlderThan(rule)
83
+ ) {
84
+ if (data?.accountCreatedAt && !data?.assuredAge) {
85
+ const accountCreatedAt = new Date(data.accountCreatedAt)
86
+ const threshold = new Date(rule.date)
87
+ if (accountCreatedAt < threshold) {
88
+ return {
89
+ access: rule.access,
90
+ reason: rule.$type,
91
+ }
92
+ }
93
+ }
94
+ } else if (
95
+ AppBskyAgeassuranceDefs.isConfigRegionRuleIfDeclaredOverAge(rule)
96
+ ) {
97
+ if (data?.declaredAge !== undefined && data.declaredAge >= rule.age) {
98
+ return {
99
+ access: rule.access,
100
+ reason: rule.$type,
101
+ }
102
+ }
103
+ } else if (
104
+ AppBskyAgeassuranceDefs.isConfigRegionRuleIfDeclaredUnderAge(rule)
105
+ ) {
106
+ if (data?.declaredAge !== undefined && data.declaredAge < rule.age) {
107
+ return {
108
+ access: rule.access,
109
+ reason: rule.$type,
110
+ }
111
+ }
112
+ } else if (
113
+ AppBskyAgeassuranceDefs.isConfigRegionRuleIfAssuredOverAge(rule)
114
+ ) {
115
+ if (data?.assuredAge && data.assuredAge >= rule.age) {
116
+ return {
117
+ access: rule.access,
118
+ reason: rule.$type,
119
+ }
120
+ }
121
+ } else if (
122
+ AppBskyAgeassuranceDefs.isConfigRegionRuleIfAssuredUnderAge(rule)
123
+ ) {
124
+ if (data?.assuredAge && data.assuredAge < rule.age) {
125
+ return {
126
+ access: rule.access,
127
+ reason: rule.$type,
128
+ }
129
+ }
130
+ } else if (AppBskyAgeassuranceDefs.isConfigRegionRuleDefault(rule)) {
131
+ return {
132
+ access: rule.access,
133
+ reason: rule.$type,
134
+ }
135
+ }
136
+ }
137
+ }
@@ -27,6 +27,14 @@ import * as AppBskyBookmarkCreateBookmark from './types/app/bsky/bookmark/create
27
27
  import * as AppBskyBookmarkDefs from './types/app/bsky/bookmark/defs.js'
28
28
  import * as AppBskyBookmarkDeleteBookmark from './types/app/bsky/bookmark/deleteBookmark.js'
29
29
  import * as AppBskyBookmarkGetBookmarks from './types/app/bsky/bookmark/getBookmarks.js'
30
+ import * as AppBskyContactDefs from './types/app/bsky/contact/defs.js'
31
+ import * as AppBskyContactDismissMatch from './types/app/bsky/contact/dismissMatch.js'
32
+ import * as AppBskyContactGetMatches from './types/app/bsky/contact/getMatches.js'
33
+ import * as AppBskyContactGetSyncStatus from './types/app/bsky/contact/getSyncStatus.js'
34
+ import * as AppBskyContactImportContacts from './types/app/bsky/contact/importContacts.js'
35
+ import * as AppBskyContactRemoveData from './types/app/bsky/contact/removeData.js'
36
+ import * as AppBskyContactStartPhoneVerification from './types/app/bsky/contact/startPhoneVerification.js'
37
+ import * as AppBskyContactVerifyPhone from './types/app/bsky/contact/verifyPhone.js'
30
38
  import * as AppBskyEmbedDefs from './types/app/bsky/embed/defs.js'
31
39
  import * as AppBskyEmbedExternal from './types/app/bsky/embed/external.js'
32
40
  import * as AppBskyEmbedImages from './types/app/bsky/embed/images.js'
@@ -325,6 +333,14 @@ export * as AppBskyBookmarkCreateBookmark from './types/app/bsky/bookmark/create
325
333
  export * as AppBskyBookmarkDefs from './types/app/bsky/bookmark/defs.js'
326
334
  export * as AppBskyBookmarkDeleteBookmark from './types/app/bsky/bookmark/deleteBookmark.js'
327
335
  export * as AppBskyBookmarkGetBookmarks from './types/app/bsky/bookmark/getBookmarks.js'
336
+ export * as AppBskyContactDefs from './types/app/bsky/contact/defs.js'
337
+ export * as AppBskyContactDismissMatch from './types/app/bsky/contact/dismissMatch.js'
338
+ export * as AppBskyContactGetMatches from './types/app/bsky/contact/getMatches.js'
339
+ export * as AppBskyContactGetSyncStatus from './types/app/bsky/contact/getSyncStatus.js'
340
+ export * as AppBskyContactImportContacts from './types/app/bsky/contact/importContacts.js'
341
+ export * as AppBskyContactRemoveData from './types/app/bsky/contact/removeData.js'
342
+ export * as AppBskyContactStartPhoneVerification from './types/app/bsky/contact/startPhoneVerification.js'
343
+ export * as AppBskyContactVerifyPhone from './types/app/bsky/contact/verifyPhone.js'
328
344
  export * as AppBskyEmbedDefs from './types/app/bsky/embed/defs.js'
329
345
  export * as AppBskyEmbedExternal from './types/app/bsky/embed/external.js'
330
346
  export * as AppBskyEmbedImages from './types/app/bsky/embed/images.js'
@@ -748,6 +764,7 @@ export class AppBskyNS {
748
764
  actor: AppBskyActorNS
749
765
  ageassurance: AppBskyAgeassuranceNS
750
766
  bookmark: AppBskyBookmarkNS
767
+ contact: AppBskyContactNS
751
768
  embed: AppBskyEmbedNS
752
769
  feed: AppBskyFeedNS
753
770
  graph: AppBskyGraphNS
@@ -762,6 +779,7 @@ export class AppBskyNS {
762
779
  this.actor = new AppBskyActorNS(client)
763
780
  this.ageassurance = new AppBskyAgeassuranceNS(client)
764
781
  this.bookmark = new AppBskyBookmarkNS(client)
782
+ this.contact = new AppBskyContactNS(client)
765
783
  this.embed = new AppBskyEmbedNS(client)
766
784
  this.feed = new AppBskyFeedNS(client)
767
785
  this.graph = new AppBskyGraphNS(client)
@@ -1122,6 +1140,91 @@ export class AppBskyBookmarkNS {
1122
1140
  }
1123
1141
  }
1124
1142
 
1143
+ export class AppBskyContactNS {
1144
+ _client: XrpcClient
1145
+
1146
+ constructor(client: XrpcClient) {
1147
+ this._client = client
1148
+ }
1149
+
1150
+ dismissMatch(
1151
+ data?: AppBskyContactDismissMatch.InputSchema,
1152
+ opts?: AppBskyContactDismissMatch.CallOptions,
1153
+ ): Promise<AppBskyContactDismissMatch.Response> {
1154
+ return this._client
1155
+ .call('app.bsky.contact.dismissMatch', opts?.qp, data, opts)
1156
+ .catch((e) => {
1157
+ throw AppBskyContactDismissMatch.toKnownErr(e)
1158
+ })
1159
+ }
1160
+
1161
+ getMatches(
1162
+ params?: AppBskyContactGetMatches.QueryParams,
1163
+ opts?: AppBskyContactGetMatches.CallOptions,
1164
+ ): Promise<AppBskyContactGetMatches.Response> {
1165
+ return this._client
1166
+ .call('app.bsky.contact.getMatches', params, undefined, opts)
1167
+ .catch((e) => {
1168
+ throw AppBskyContactGetMatches.toKnownErr(e)
1169
+ })
1170
+ }
1171
+
1172
+ getSyncStatus(
1173
+ params?: AppBskyContactGetSyncStatus.QueryParams,
1174
+ opts?: AppBskyContactGetSyncStatus.CallOptions,
1175
+ ): Promise<AppBskyContactGetSyncStatus.Response> {
1176
+ return this._client
1177
+ .call('app.bsky.contact.getSyncStatus', params, undefined, opts)
1178
+ .catch((e) => {
1179
+ throw AppBskyContactGetSyncStatus.toKnownErr(e)
1180
+ })
1181
+ }
1182
+
1183
+ importContacts(
1184
+ data?: AppBskyContactImportContacts.InputSchema,
1185
+ opts?: AppBskyContactImportContacts.CallOptions,
1186
+ ): Promise<AppBskyContactImportContacts.Response> {
1187
+ return this._client
1188
+ .call('app.bsky.contact.importContacts', opts?.qp, data, opts)
1189
+ .catch((e) => {
1190
+ throw AppBskyContactImportContacts.toKnownErr(e)
1191
+ })
1192
+ }
1193
+
1194
+ removeData(
1195
+ data?: AppBskyContactRemoveData.InputSchema,
1196
+ opts?: AppBskyContactRemoveData.CallOptions,
1197
+ ): Promise<AppBskyContactRemoveData.Response> {
1198
+ return this._client
1199
+ .call('app.bsky.contact.removeData', opts?.qp, data, opts)
1200
+ .catch((e) => {
1201
+ throw AppBskyContactRemoveData.toKnownErr(e)
1202
+ })
1203
+ }
1204
+
1205
+ startPhoneVerification(
1206
+ data?: AppBskyContactStartPhoneVerification.InputSchema,
1207
+ opts?: AppBskyContactStartPhoneVerification.CallOptions,
1208
+ ): Promise<AppBskyContactStartPhoneVerification.Response> {
1209
+ return this._client
1210
+ .call('app.bsky.contact.startPhoneVerification', opts?.qp, data, opts)
1211
+ .catch((e) => {
1212
+ throw AppBskyContactStartPhoneVerification.toKnownErr(e)
1213
+ })
1214
+ }
1215
+
1216
+ verifyPhone(
1217
+ data?: AppBskyContactVerifyPhone.InputSchema,
1218
+ opts?: AppBskyContactVerifyPhone.CallOptions,
1219
+ ): Promise<AppBskyContactVerifyPhone.Response> {
1220
+ return this._client
1221
+ .call('app.bsky.contact.verifyPhone', opts?.qp, data, opts)
1222
+ .catch((e) => {
1223
+ throw AppBskyContactVerifyPhone.toKnownErr(e)
1224
+ })
1225
+ }
1226
+ }
1227
+
1125
1228
  export class AppBskyEmbedNS {
1126
1229
  _client: XrpcClient
1127
1230