@atproto/common-web 0.2.4 → 0.3.1

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 (60) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/arrays.d.ts +1 -0
  3. package/dist/arrays.d.ts.map +1 -0
  4. package/dist/arrays.js +22 -0
  5. package/dist/arrays.js.map +1 -0
  6. package/dist/async.d.ts +2 -1
  7. package/dist/async.d.ts.map +1 -0
  8. package/dist/async.js +187 -0
  9. package/dist/async.js.map +1 -0
  10. package/dist/check.d.ts +3 -2
  11. package/dist/check.d.ts.map +1 -0
  12. package/dist/check.js +20 -0
  13. package/dist/check.js.map +1 -0
  14. package/dist/did-doc.d.ts +6 -5
  15. package/dist/did-doc.d.ts.map +1 -0
  16. package/dist/did-doc.js +157 -0
  17. package/dist/did-doc.js.map +1 -0
  18. package/dist/index.d.ts +1 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +39 -14274
  21. package/dist/index.js.map +1 -7
  22. package/dist/ipld.d.ts +1 -0
  23. package/dist/ipld.d.ts.map +1 -0
  24. package/dist/ipld.js +119 -0
  25. package/dist/ipld.js.map +1 -0
  26. package/dist/retry.d.ts +1 -0
  27. package/dist/retry.d.ts.map +1 -0
  28. package/dist/retry.js +46 -0
  29. package/dist/retry.js.map +1 -0
  30. package/dist/strings.d.ts +1 -0
  31. package/dist/strings.d.ts.map +1 -0
  32. package/dist/strings.js +74 -0
  33. package/dist/strings.js.map +1 -0
  34. package/dist/tid.d.ts +1 -0
  35. package/dist/tid.d.ts.map +1 -0
  36. package/dist/tid.js +100 -0
  37. package/dist/tid.js.map +1 -0
  38. package/dist/times.d.ts +1 -0
  39. package/dist/times.d.ts.map +1 -0
  40. package/dist/times.js +19 -0
  41. package/dist/times.js.map +1 -0
  42. package/dist/types.d.ts +1 -0
  43. package/dist/types.d.ts.map +1 -0
  44. package/dist/types.js +42 -0
  45. package/dist/types.js.map +1 -0
  46. package/dist/util.d.ts +2 -0
  47. package/dist/util.d.ts.map +1 -0
  48. package/dist/util.js +120 -0
  49. package/dist/util.js.map +1 -0
  50. package/jest.config.js +3 -3
  51. package/package.json +8 -7
  52. package/src/arrays.ts +7 -4
  53. package/src/check.ts +8 -2
  54. package/src/did-doc.ts +83 -39
  55. package/src/util.ts +15 -0
  56. package/tsconfig.build.json +6 -2
  57. package/tsconfig.json +5 -7
  58. package/tsconfig.tests.json +7 -0
  59. package/babel.config.js +0 -1
  60. package/build.js +0 -15
package/jest.config.js CHANGED
@@ -1,6 +1,6 @@
1
- const base = require('../../jest.config.base.js')
2
-
1
+ /** @type {import('jest').Config} */
3
2
  module.exports = {
4
- ...base,
5
3
  displayName: 'Common Web',
4
+ transform: { '^.+\\.(t|j)s$': '@swc/jest' },
5
+ setupFiles: ['<rootDir>/../../jest.setup.ts'],
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/common-web",
3
- "version": "0.2.4",
3
+ "version": "0.3.1",
4
4
  "license": "MIT",
5
5
  "description": "Shared web-platform-friendly code for atproto libraries",
6
6
  "keywords": [
@@ -13,17 +13,18 @@
13
13
  "directory": "packages/common-web"
14
14
  },
15
15
  "main": "dist/index.js",
16
+ "types": "dist/index.d.ts",
16
17
  "dependencies": {
17
18
  "graphemer": "^1.4.0",
18
19
  "multiformats": "^9.9.0",
19
20
  "uint8arrays": "3.0.0",
20
- "zod": "^3.21.4"
21
+ "zod": "^3.23.8"
22
+ },
23
+ "devDependencies": {
24
+ "jest": "^28.1.2"
21
25
  },
22
26
  "scripts": {
23
27
  "test": "jest",
24
- "build": "node ./build.js",
25
- "postbuild": "tsc --build tsconfig.build.json",
26
- "update-main-to-dist": "node ../../update-main-to-dist.js packages/common-web"
27
- },
28
- "types": "dist/index.d.ts"
28
+ "build": "tsc --build tsconfig.build.json"
29
+ }
29
30
  }
package/src/arrays.ts CHANGED
@@ -1,8 +1,11 @@
1
1
  export const keyBy = <T>(arr: T[], key: string): Record<string, T> => {
2
- return arr.reduce((acc, cur) => {
3
- acc[cur[key]] = cur
4
- return acc
5
- }, {} as Record<string, T>)
2
+ return arr.reduce(
3
+ (acc, cur) => {
4
+ acc[cur[key]] = cur
5
+ return acc
6
+ },
7
+ {} as Record<string, T>,
8
+ )
6
9
  }
7
10
 
8
11
  export const mapDefined = <T, S>(
package/src/check.ts CHANGED
@@ -1,10 +1,11 @@
1
- import { ZodError } from 'zod'
1
+ // Explicitly not using "zod" types here to avoid mismatching types due to
2
+ // version differences.
2
3
 
3
4
  export interface Checkable<T> {
4
5
  parse: (obj: unknown) => T
5
6
  safeParse: (
6
7
  obj: unknown,
7
- ) => { success: true; data: T } | { success: false; error: ZodError }
8
+ ) => { success: true; data: T } | { success: false; error: Error }
8
9
  }
9
10
 
10
11
  export interface Def<T> {
@@ -16,6 +17,11 @@ export const is = <T>(obj: unknown, def: Checkable<T>): obj is T => {
16
17
  return def.safeParse(obj).success
17
18
  }
18
19
 
20
+ export const create =
21
+ <T>(def: Checkable<T>) =>
22
+ (v: unknown): v is T =>
23
+ def.safeParse(v).success
24
+
19
25
  export const assure = <T>(def: Checkable<T>, obj: unknown): T => {
20
26
  return def.parse(obj)
21
27
  }
package/src/did-doc.ts CHANGED
@@ -17,11 +17,16 @@ export const getDid = (doc: DidDocument): string => {
17
17
 
18
18
  export const getHandle = (doc: DidDocument): string | undefined => {
19
19
  const aka = doc.alsoKnownAs
20
- if (!aka) return undefined
21
- const found = aka.find((name) => name.startsWith('at://'))
22
- if (!found) return undefined
23
- // strip off at:// prefix
24
- return found.slice(5)
20
+ if (aka) {
21
+ for (let i = 0; i < aka.length; i++) {
22
+ const alias = aka[i]
23
+ if (alias.startsWith('at://')) {
24
+ // strip off "at://" prefix
25
+ return alias.slice(5)
26
+ }
27
+ }
28
+ }
29
+ return undefined
25
30
  }
26
31
 
27
32
  // @NOTE we parse to type/publicKeyMultibase to avoid the dependency on @atproto/crypto
@@ -35,20 +40,20 @@ export const getVerificationMaterial = (
35
40
  doc: DidDocument,
36
41
  keyId: string,
37
42
  ): { type: string; publicKeyMultibase: string } | undefined => {
38
- const did = getDid(doc)
39
- let keys = doc.verificationMethod
40
- if (!keys) return undefined
41
- if (typeof keys !== 'object') return undefined
42
- if (!Array.isArray(keys)) {
43
- keys = [keys]
43
+ // /!\ Hot path
44
+
45
+ const key = findItemById(doc, 'verificationMethod', `#${keyId}`)
46
+ if (!key) {
47
+ return undefined
48
+ }
49
+
50
+ if (!key.publicKeyMultibase) {
51
+ return undefined
44
52
  }
45
- const found = keys.find(
46
- (key) => key.id === `#${keyId}` || key.id === `${did}#${keyId}`,
47
- )
48
- if (!found?.publicKeyMultibase) return undefined
53
+
49
54
  return {
50
- type: found.type,
51
- publicKeyMultibase: found.publicKeyMultibase,
55
+ type: key.type,
56
+ publicKeyMultibase: key.publicKeyMultibase,
52
57
  }
53
58
  }
54
59
 
@@ -83,43 +88,82 @@ export const getServiceEndpoint = (
83
88
  doc: DidDocument,
84
89
  opts: { id: string; type?: string },
85
90
  ) => {
86
- const did = getDid(doc)
87
- let services = doc.service
88
- if (!services) return undefined
89
- if (typeof services !== 'object') return undefined
90
- if (!Array.isArray(services)) {
91
- services = [services]
91
+ // /!\ Hot path
92
+
93
+ const service = findItemById(doc, 'service', opts.id)
94
+ if (!service) {
95
+ return undefined
92
96
  }
93
- const found = services.find(
94
- (service) => service.id === opts.id || service.id === `${did}${opts.id}`,
95
- )
96
- if (!found) return undefined
97
- if (opts.type && found.type !== opts.type) {
97
+
98
+ if (opts.type && service.type !== opts.type) {
98
99
  return undefined
99
100
  }
100
- if (typeof found.serviceEndpoint !== 'string') {
101
+
102
+ if (typeof service.serviceEndpoint !== 'string') {
101
103
  return undefined
102
104
  }
103
- return validateUrl(found.serviceEndpoint)
105
+
106
+ return validateUrl(service.serviceEndpoint)
107
+ }
108
+
109
+ function findItemById<
110
+ D extends DidDocument,
111
+ T extends 'verificationMethod' | 'service',
112
+ >(doc: D, type: T, id: string): NonNullable<D[T]>[number] | undefined
113
+ function findItemById(
114
+ doc: DidDocument,
115
+ type: 'verificationMethod' | 'service',
116
+ id: string,
117
+ ) {
118
+ // /!\ Hot path
119
+
120
+ const items = doc[type]
121
+ if (items) {
122
+ for (let i = 0; i < items.length; i++) {
123
+ const item = items[i]
124
+ const itemId = item.id
125
+
126
+ if (
127
+ itemId[0] === '#'
128
+ ? itemId === id
129
+ : // Optimized version of: itemId === `${doc.id}${id}`
130
+ itemId.length === doc.id.length + id.length &&
131
+ itemId[doc.id.length] === '#' &&
132
+ itemId.endsWith(id) &&
133
+ itemId.startsWith(doc.id) // <== We could probably skip this check
134
+ ) {
135
+ return item
136
+ }
137
+ }
138
+ }
139
+ return undefined
104
140
  }
105
141
 
106
142
  // Check protocol and hostname to prevent potential SSRF
107
143
  const validateUrl = (urlStr: string): string | undefined => {
108
- let url
109
- try {
110
- url = new URL(urlStr)
111
- } catch {
144
+ if (!urlStr.startsWith('http://') && !urlStr.startsWith('https://')) {
112
145
  return undefined
113
146
  }
114
- if (!['http:', 'https:'].includes(url.protocol)) {
115
- return undefined
116
- } else if (!url.hostname) {
147
+
148
+ if (!canParseUrl(urlStr)) {
117
149
  return undefined
118
- } else {
119
- return urlStr
120
150
  }
151
+
152
+ return urlStr
121
153
  }
122
154
 
155
+ const canParseUrl =
156
+ URL.canParse ??
157
+ // URL.canParse is not available in Node.js < 18.17.0
158
+ ((urlStr: string): boolean => {
159
+ try {
160
+ new URL(urlStr)
161
+ return true
162
+ } catch {
163
+ return false
164
+ }
165
+ })
166
+
123
167
  // Types
124
168
  // --------
125
169
 
package/src/util.ts CHANGED
@@ -9,6 +9,21 @@ export const noUndefinedVals = <T>(
9
9
  return obj as Record<string, T>
10
10
  }
11
11
 
12
+ export function omit<
13
+ T extends undefined | Record<string, unknown>,
14
+ K extends keyof NonNullable<T>,
15
+ >(obj: T, keys: readonly K[]): T extends undefined ? undefined : Omit<T, K>
16
+ export function omit(
17
+ obj: Record<string, unknown>,
18
+ keys: readonly string[],
19
+ ): undefined | Record<string, unknown> {
20
+ if (!obj) return obj
21
+
22
+ return Object.fromEntries(
23
+ Object.entries(obj).filter((entry) => !keys.includes(entry[0])),
24
+ )
25
+ }
26
+
12
27
  export const jitter = (maxMs: number) => {
13
28
  return Math.round((Math.random() - 0.5) * maxMs * 2)
14
29
  }
@@ -1,4 +1,8 @@
1
1
  {
2
- "extends": "./tsconfig.json",
3
- "exclude": ["**/*.spec.ts", "**/*.test.ts"]
2
+ "extends": "../../tsconfig/isomorphic.json",
3
+ "compilerOptions": {
4
+ "rootDir": "./src",
5
+ "outDir": "./dist"
6
+ },
7
+ "include": ["./src"]
4
8
  }
package/tsconfig.json CHANGED
@@ -1,9 +1,7 @@
1
1
  {
2
- "extends": "../../tsconfig.json",
3
- "compilerOptions": {
4
- "rootDir": "./src",
5
- "outDir": "./dist", // Your outDir,
6
- "emitDeclarationOnly": true
7
- },
8
- "include": ["./src", "__tests__/**/**.ts"]
2
+ "include": [],
3
+ "references": [
4
+ { "path": "./tsconfig.build.json" },
5
+ { "path": "./tsconfig.tests.json" }
6
+ ]
9
7
  }
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "../../tsconfig/tests.json",
3
+ "compilerOptions": {
4
+ "rootDir": "."
5
+ },
6
+ "include": ["./tests"]
7
+ }
package/babel.config.js DELETED
@@ -1 +0,0 @@
1
- module.exports = require('../../babel.config.js')
package/build.js DELETED
@@ -1,15 +0,0 @@
1
- const { nodeExternalsPlugin } = require('esbuild-node-externals')
2
-
3
- const buildShallow =
4
- process.argv.includes('--shallow') || process.env.ATP_BUILD_SHALLOW === 'true'
5
-
6
- require('esbuild').build({
7
- logLevel: 'info',
8
- entryPoints: ['src/index.ts'],
9
- bundle: true,
10
- sourcemap: true,
11
- outdir: 'dist',
12
- platform: 'browser',
13
- format: 'cjs',
14
- plugins: buildShallow ? [nodeExternalsPlugin()] : [],
15
- })