@atproto/common-web
Advanced tools
Comparing version 0.3.0 to 0.3.1
# @atproto/common-web | ||
## 0.3.1 | ||
### Patch Changes | ||
- [#2770](https://github.com/bluesky-social/atproto/pull/2770) [`a07b21151`](https://github.com/bluesky-social/atproto/commit/a07b21151f1850340c4b7797ebb11521b1a6cdf3) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Add omit() utility | ||
- [#2770](https://github.com/bluesky-social/atproto/pull/2770) [`a07b21151`](https://github.com/bluesky-social/atproto/commit/a07b21151f1850340c4b7797ebb11521b1a6cdf3) Thanks [@matthieusieben](https://github.com/matthieusieben)! - DID document parsing optimization | ||
- [#2835](https://github.com/bluesky-social/atproto/pull/2835) [`eb20ff64a`](https://github.com/bluesky-social/atproto/commit/eb20ff64a2d8e3061c652e1e247bf9b0fe3c41a6) Thanks [@matthieusieben](https://github.com/matthieusieben)! - ponyfill URL.canParse | ||
## 0.3.0 | ||
@@ -4,0 +14,0 @@ |
@@ -1,2 +0,2 @@ | ||
export declare const readFromGenerator: <T>(gen: AsyncGenerator<T, any, unknown>, isDone: (last?: T | undefined) => Promise<boolean> | boolean, waitFor?: Promise<unknown>, maxLength?: number) => Promise<T[]>; | ||
export declare const readFromGenerator: <T>(gen: AsyncGenerator<T>, isDone: (last?: T) => Promise<boolean> | boolean, waitFor?: Promise<unknown>, maxLength?: number) => Promise<T[]>; | ||
export type Deferrable = { | ||
@@ -3,0 +3,0 @@ resolve: () => void; |
@@ -1,2 +0,1 @@ | ||
import { ZodError } from 'zod'; | ||
export interface Checkable<T> { | ||
@@ -9,3 +8,3 @@ parse: (obj: unknown) => T; | ||
success: false; | ||
error: ZodError; | ||
error: Error; | ||
}; | ||
@@ -18,4 +17,5 @@ } | ||
export declare const is: <T>(obj: unknown, def: Checkable<T>) => obj is T; | ||
export declare const create: <T>(def: Checkable<T>) => (v: unknown) => v is T; | ||
export declare const assure: <T>(def: Checkable<T>, obj: unknown) => T; | ||
export declare const isObject: (obj: unknown) => obj is Record<string, unknown>; | ||
//# sourceMappingURL=check.d.ts.map |
"use strict"; | ||
// Explicitly not using "zod" types here to avoid mismatching types due to | ||
// version differences. | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.isObject = exports.assure = exports.is = void 0; | ||
exports.isObject = exports.assure = exports.create = exports.is = void 0; | ||
const is = (obj, def) => { | ||
@@ -8,2 +10,4 @@ return def.safeParse(obj).success; | ||
exports.is = is; | ||
const create = (def) => (v) => def.safeParse(v).success; | ||
exports.create = create; | ||
const assure = (def, obj) => { | ||
@@ -10,0 +14,0 @@ return def.parse(obj); |
@@ -14,3 +14,3 @@ import { z } from 'zod'; | ||
type: string; | ||
serviceEndpoint: (string | Record<string, unknown>) & (string | Record<string, unknown> | undefined); | ||
serviceEndpoint: string | Record<string, unknown>; | ||
}[] | undefined; | ||
@@ -62,7 +62,7 @@ }; | ||
type: string; | ||
serviceEndpoint: (string | Record<string, unknown>) & (string | Record<string, unknown> | undefined); | ||
serviceEndpoint: string | Record<string, unknown>; | ||
}, { | ||
id: string; | ||
type: string; | ||
serviceEndpoint: (string | Record<string, unknown>) & (string | Record<string, unknown> | undefined); | ||
serviceEndpoint: string | Record<string, unknown>; | ||
}>, "many">>; | ||
@@ -81,3 +81,3 @@ }, "strip", z.ZodTypeAny, { | ||
type: string; | ||
serviceEndpoint: (string | Record<string, unknown>) & (string | Record<string, unknown> | undefined); | ||
serviceEndpoint: string | Record<string, unknown>; | ||
}[] | undefined; | ||
@@ -96,3 +96,3 @@ }, { | ||
type: string; | ||
serviceEndpoint: (string | Record<string, unknown>) & (string | Record<string, unknown> | undefined); | ||
serviceEndpoint: string | Record<string, unknown>; | ||
}[] | undefined; | ||
@@ -99,0 +99,0 @@ }>; |
@@ -21,9 +21,12 @@ "use strict"; | ||
const aka = doc.alsoKnownAs; | ||
if (!aka) | ||
return undefined; | ||
const found = aka.find((name) => name.startsWith('at://')); | ||
if (!found) | ||
return undefined; | ||
// strip off at:// prefix | ||
return found.slice(5); | ||
if (aka) { | ||
for (let i = 0; i < aka.length; i++) { | ||
const alias = aka[i]; | ||
if (alias.startsWith('at://')) { | ||
// strip off "at://" prefix | ||
return alias.slice(5); | ||
} | ||
} | ||
} | ||
return undefined; | ||
}; | ||
@@ -37,17 +40,13 @@ exports.getHandle = getHandle; | ||
const getVerificationMaterial = (doc, keyId) => { | ||
const did = (0, exports.getDid)(doc); | ||
let keys = doc.verificationMethod; | ||
if (!keys) | ||
// /!\ Hot path | ||
const key = findItemById(doc, 'verificationMethod', `#${keyId}`); | ||
if (!key) { | ||
return undefined; | ||
if (typeof keys !== 'object') | ||
} | ||
if (!key.publicKeyMultibase) { | ||
return undefined; | ||
if (!Array.isArray(keys)) { | ||
keys = [keys]; | ||
} | ||
const found = keys.find((key) => key.id === `#${keyId}` || key.id === `${did}#${keyId}`); | ||
if (!found?.publicKeyMultibase) | ||
return undefined; | ||
return { | ||
type: found.type, | ||
publicKeyMultibase: found.publicKeyMultibase, | ||
type: key.type, | ||
publicKeyMultibase: key.publicKeyMultibase, | ||
}; | ||
@@ -85,42 +84,58 @@ }; | ||
const getServiceEndpoint = (doc, opts) => { | ||
const did = (0, exports.getDid)(doc); | ||
let services = doc.service; | ||
if (!services) | ||
// /!\ Hot path | ||
const service = findItemById(doc, 'service', opts.id); | ||
if (!service) { | ||
return undefined; | ||
if (typeof services !== 'object') | ||
return undefined; | ||
if (!Array.isArray(services)) { | ||
services = [services]; | ||
} | ||
const found = services.find((service) => service.id === opts.id || service.id === `${did}${opts.id}`); | ||
if (!found) | ||
if (opts.type && service.type !== opts.type) { | ||
return undefined; | ||
if (opts.type && found.type !== opts.type) { | ||
return undefined; | ||
} | ||
if (typeof found.serviceEndpoint !== 'string') { | ||
if (typeof service.serviceEndpoint !== 'string') { | ||
return undefined; | ||
} | ||
return validateUrl(found.serviceEndpoint); | ||
return validateUrl(service.serviceEndpoint); | ||
}; | ||
exports.getServiceEndpoint = getServiceEndpoint; | ||
function findItemById(doc, type, id) { | ||
// /!\ Hot path | ||
const items = doc[type]; | ||
if (items) { | ||
for (let i = 0; i < items.length; i++) { | ||
const item = items[i]; | ||
const itemId = item.id; | ||
if (itemId[0] === '#' | ||
? itemId === id | ||
: // Optimized version of: itemId === `${doc.id}${id}` | ||
itemId.length === doc.id.length + id.length && | ||
itemId[doc.id.length] === '#' && | ||
itemId.endsWith(id) && | ||
itemId.startsWith(doc.id) // <== We could probably skip this check | ||
) { | ||
return item; | ||
} | ||
} | ||
} | ||
return undefined; | ||
} | ||
// Check protocol and hostname to prevent potential SSRF | ||
const validateUrl = (urlStr) => { | ||
let url; | ||
try { | ||
url = new URL(urlStr); | ||
} | ||
catch { | ||
if (!urlStr.startsWith('http://') && !urlStr.startsWith('https://')) { | ||
return undefined; | ||
} | ||
if (!['http:', 'https:'].includes(url.protocol)) { | ||
if (!canParseUrl(urlStr)) { | ||
return undefined; | ||
} | ||
else if (!url.hostname) { | ||
return undefined; | ||
} | ||
else { | ||
return urlStr; | ||
} | ||
return urlStr; | ||
}; | ||
const canParseUrl = URL.canParse ?? | ||
// URL.canParse is not available in Node.js < 18.17.0 | ||
((urlStr) => { | ||
try { | ||
new URL(urlStr); | ||
return true; | ||
} | ||
catch { | ||
return false; | ||
} | ||
}); | ||
// Types | ||
@@ -127,0 +142,0 @@ // -------- |
/// <reference types="node" /> | ||
export declare const noUndefinedVals: <T>(obj: Record<string, T | undefined>) => Record<string, T>; | ||
export declare function omit<T extends undefined | Record<string, unknown>, K extends keyof NonNullable<T>>(obj: T, keys: readonly K[]): T extends undefined ? undefined : Omit<T, K>; | ||
export declare const jitter: (maxMs: number) => number; | ||
@@ -4,0 +5,0 @@ export declare const wait: (ms: number) => Promise<unknown>; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.parseIntWithFallback = exports.dedupeStrs = exports.range = exports.chunkArray = exports.errHasMsg = exports.isErrnoException = exports.asyncFilter = exports.s32decode = exports.s32encode = exports.streamToBuffer = exports.flattenUint8Arrays = exports.bailableWait = exports.wait = exports.jitter = exports.noUndefinedVals = void 0; | ||
exports.parseIntWithFallback = exports.dedupeStrs = exports.range = exports.chunkArray = exports.errHasMsg = exports.isErrnoException = exports.asyncFilter = exports.s32decode = exports.s32encode = exports.streamToBuffer = exports.flattenUint8Arrays = exports.bailableWait = exports.wait = exports.jitter = exports.omit = exports.noUndefinedVals = void 0; | ||
const noUndefinedVals = (obj) => { | ||
@@ -13,2 +13,8 @@ Object.keys(obj).forEach((k) => { | ||
exports.noUndefinedVals = noUndefinedVals; | ||
function omit(obj, keys) { | ||
if (!obj) | ||
return obj; | ||
return Object.fromEntries(Object.entries(obj).filter((entry) => !keys.includes(entry[0]))); | ||
} | ||
exports.omit = omit; | ||
const jitter = (maxMs) => { | ||
@@ -15,0 +21,0 @@ return Math.round((Math.random() - 0.5) * maxMs * 2); |
{ | ||
"name": "@atproto/common-web", | ||
"version": "0.3.0", | ||
"version": "0.3.1", | ||
"license": "MIT", | ||
@@ -21,3 +21,3 @@ "description": "Shared web-platform-friendly code for atproto libraries", | ||
"uint8arrays": "3.0.0", | ||
"zod": "^3.21.4" | ||
"zod": "^3.23.8" | ||
}, | ||
@@ -24,0 +24,0 @@ "devDependencies": { |
export const keyBy = <T>(arr: T[], key: string): Record<string, T> => { | ||
return arr.reduce((acc, cur) => { | ||
acc[cur[key]] = cur | ||
return acc | ||
}, {} as Record<string, T>) | ||
return arr.reduce( | ||
(acc, cur) => { | ||
acc[cur[key]] = cur | ||
return acc | ||
}, | ||
{} as Record<string, T>, | ||
) | ||
} | ||
@@ -7,0 +10,0 @@ |
@@ -1,2 +0,3 @@ | ||
import { ZodError } from 'zod' | ||
// Explicitly not using "zod" types here to avoid mismatching types due to | ||
// version differences. | ||
@@ -7,3 +8,3 @@ export interface Checkable<T> { | ||
obj: unknown, | ||
) => { success: true; data: T } | { success: false; error: ZodError } | ||
) => { success: true; data: T } | { success: false; error: Error } | ||
} | ||
@@ -20,2 +21,7 @@ | ||
export const create = | ||
<T>(def: Checkable<T>) => | ||
(v: unknown): v is T => | ||
def.safeParse(v).success | ||
export const assure = <T>(def: Checkable<T>, obj: unknown): T => { | ||
@@ -22,0 +28,0 @@ return def.parse(obj) |
@@ -20,7 +20,12 @@ import { z } from 'zod' | ||
const aka = doc.alsoKnownAs | ||
if (!aka) return undefined | ||
const found = aka.find((name) => name.startsWith('at://')) | ||
if (!found) return undefined | ||
// strip off at:// prefix | ||
return found.slice(5) | ||
if (aka) { | ||
for (let i = 0; i < aka.length; i++) { | ||
const alias = aka[i] | ||
if (alias.startsWith('at://')) { | ||
// strip off "at://" prefix | ||
return alias.slice(5) | ||
} | ||
} | ||
} | ||
return undefined | ||
} | ||
@@ -39,16 +44,16 @@ | ||
): { type: string; publicKeyMultibase: string } | undefined => { | ||
const did = getDid(doc) | ||
let keys = doc.verificationMethod | ||
if (!keys) return undefined | ||
if (typeof keys !== 'object') return undefined | ||
if (!Array.isArray(keys)) { | ||
keys = [keys] | ||
// /!\ Hot path | ||
const key = findItemById(doc, 'verificationMethod', `#${keyId}`) | ||
if (!key) { | ||
return undefined | ||
} | ||
const found = keys.find( | ||
(key) => key.id === `#${keyId}` || key.id === `${did}#${keyId}`, | ||
) | ||
if (!found?.publicKeyMultibase) return undefined | ||
if (!key.publicKeyMultibase) { | ||
return undefined | ||
} | ||
return { | ||
type: found.type, | ||
publicKeyMultibase: found.publicKeyMultibase, | ||
type: key.type, | ||
publicKeyMultibase: key.publicKeyMultibase, | ||
} | ||
@@ -88,39 +93,78 @@ } | ||
) => { | ||
const did = getDid(doc) | ||
let services = doc.service | ||
if (!services) return undefined | ||
if (typeof services !== 'object') return undefined | ||
if (!Array.isArray(services)) { | ||
services = [services] | ||
// /!\ Hot path | ||
const service = findItemById(doc, 'service', opts.id) | ||
if (!service) { | ||
return undefined | ||
} | ||
const found = services.find( | ||
(service) => service.id === opts.id || service.id === `${did}${opts.id}`, | ||
) | ||
if (!found) return undefined | ||
if (opts.type && found.type !== opts.type) { | ||
if (opts.type && service.type !== opts.type) { | ||
return undefined | ||
} | ||
if (typeof found.serviceEndpoint !== 'string') { | ||
if (typeof service.serviceEndpoint !== 'string') { | ||
return undefined | ||
} | ||
return validateUrl(found.serviceEndpoint) | ||
return validateUrl(service.serviceEndpoint) | ||
} | ||
function findItemById< | ||
D extends DidDocument, | ||
T extends 'verificationMethod' | 'service', | ||
>(doc: D, type: T, id: string): NonNullable<D[T]>[number] | undefined | ||
function findItemById( | ||
doc: DidDocument, | ||
type: 'verificationMethod' | 'service', | ||
id: string, | ||
) { | ||
// /!\ Hot path | ||
const items = doc[type] | ||
if (items) { | ||
for (let i = 0; i < items.length; i++) { | ||
const item = items[i] | ||
const itemId = item.id | ||
if ( | ||
itemId[0] === '#' | ||
? itemId === id | ||
: // Optimized version of: itemId === `${doc.id}${id}` | ||
itemId.length === doc.id.length + id.length && | ||
itemId[doc.id.length] === '#' && | ||
itemId.endsWith(id) && | ||
itemId.startsWith(doc.id) // <== We could probably skip this check | ||
) { | ||
return item | ||
} | ||
} | ||
} | ||
return undefined | ||
} | ||
// Check protocol and hostname to prevent potential SSRF | ||
const validateUrl = (urlStr: string): string | undefined => { | ||
let url | ||
try { | ||
url = new URL(urlStr) | ||
} catch { | ||
if (!urlStr.startsWith('http://') && !urlStr.startsWith('https://')) { | ||
return undefined | ||
} | ||
if (!['http:', 'https:'].includes(url.protocol)) { | ||
if (!canParseUrl(urlStr)) { | ||
return undefined | ||
} else if (!url.hostname) { | ||
return undefined | ||
} else { | ||
return urlStr | ||
} | ||
return urlStr | ||
} | ||
const canParseUrl = | ||
URL.canParse ?? | ||
// URL.canParse is not available in Node.js < 18.17.0 | ||
((urlStr: string): boolean => { | ||
try { | ||
new URL(urlStr) | ||
return true | ||
} catch { | ||
return false | ||
} | ||
}) | ||
// Types | ||
@@ -127,0 +171,0 @@ // -------- |
@@ -12,2 +12,17 @@ export const noUndefinedVals = <T>( | ||
export function omit< | ||
T extends undefined | Record<string, unknown>, | ||
K extends keyof NonNullable<T>, | ||
>(obj: T, keys: readonly K[]): T extends undefined ? undefined : Omit<T, K> | ||
export function omit( | ||
obj: Record<string, unknown>, | ||
keys: readonly string[], | ||
): undefined | Record<string, unknown> { | ||
if (!obj) return obj | ||
return Object.fromEntries( | ||
Object.entries(obj).filter((entry) => !keys.includes(entry[0])), | ||
) | ||
} | ||
export const jitter = (maxMs: number) => { | ||
@@ -14,0 +29,0 @@ return Math.round((Math.random() - 0.5) * maxMs * 2) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
119864
2537
0
Updatedzod@^3.23.8