@atproto/common 0.6.4 → 0.6.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @atproto/common
2
2
 
3
+ ## 0.6.5
4
+
5
+ ### Patch Changes
6
+
7
+ - [#5099](https://github.com/bluesky-social/atproto/pull/5099) [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Update TypeScript build to rely on references to composite internal projects
8
+
9
+ - [#5099](https://github.com/bluesky-social/atproto/pull/5099) [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Bundle only necessary files in the NPM tarball, including the `CHANGELOG.md` and `README.md` files (if present).
10
+
11
+ - [#5099](https://github.com/bluesky-social/atproto/pull/5099) [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Build with `noImplicitAny` enabled
12
+
13
+ - Updated dependencies [[`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07), [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07), [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07)]:
14
+ - @atproto/lex-cbor@0.1.3
15
+ - @atproto/lex-data@0.1.4
16
+ - @atproto/common-web@0.5.3
17
+
3
18
  ## 0.6.4
4
19
 
5
20
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/common",
3
- "version": "0.6.4",
3
+ "version": "0.6.5",
4
4
  "license": "MIT",
5
5
  "description": "Shared web-platform-friendly code for atproto libraries",
6
6
  "keywords": [
@@ -12,27 +12,32 @@
12
12
  "url": "https://github.com/bluesky-social/atproto",
13
13
  "directory": "packages/common"
14
14
  },
15
+ "files": [
16
+ "./dist",
17
+ "./README.md",
18
+ "./CHANGELOG.md"
19
+ ],
20
+ "type": "module",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.ts",
24
+ "default": "./dist/index.js"
25
+ }
26
+ },
15
27
  "engines": {
16
28
  "node": ">=22"
17
29
  },
18
30
  "dependencies": {
19
31
  "multiformats": "^13.0.0",
20
32
  "pino": "^10.3.1",
21
- "@atproto/common-web": "^0.5.2",
22
- "@atproto/lex-cbor": "^0.1.2",
23
- "@atproto/lex-data": "^0.1.3"
33
+ "@atproto/lex-cbor": "^0.1.3",
34
+ "@atproto/lex-data": "^0.1.4",
35
+ "@atproto/common-web": "^0.5.3"
24
36
  },
25
37
  "devDependencies": {
26
38
  "jest": "^30.0.0",
27
39
  "uint8arrays": "^5.0.0"
28
40
  },
29
- "type": "module",
30
- "exports": {
31
- ".": {
32
- "types": "./dist/index.d.ts",
33
- "default": "./dist/index.js"
34
- }
35
- },
36
41
  "scripts": {
37
42
  "test": "NODE_OPTIONS=--experimental-vm-modules jest",
38
43
  "build": "tsgo --build tsconfig.build.json"
package/jest.config.cjs DELETED
@@ -1,21 +0,0 @@
1
- /** @type {import('jest').Config} */
2
- module.exports = {
3
- displayName: 'Common',
4
- transform: {
5
- '^.+\\.(t|j)s$': [
6
- '@swc/jest',
7
- {
8
- jsc: {
9
- parser: { syntax: 'typescript', importAttributes: true },
10
- experimental: { keepImportAttributes: true },
11
- transform: {},
12
- },
13
- module: { type: 'es6' },
14
- },
15
- ],
16
- },
17
- extensionsToTreatAsEsm: ['.ts'],
18
- transformIgnorePatterns: [],
19
- setupFiles: ['<rootDir>/../../test.setup.ts'],
20
- moduleNameMapper: { '^(\\.\\.?\\/.+)\\.js$': ['$1.ts', '$1.js'] },
21
- }
package/src/buffers.ts DELETED
@@ -1,14 +0,0 @@
1
- export function ui8ToBuffer(bytes: Uint8Array): Buffer {
2
- return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength)
3
- }
4
-
5
- export function ui8ToArrayBuffer(bytes: Uint8Array): ArrayBuffer {
6
- if (bytes.buffer instanceof ArrayBuffer) {
7
- return bytes.buffer.slice(
8
- bytes.byteOffset,
9
- bytes.byteLength + bytes.byteOffset,
10
- )
11
- }
12
-
13
- return new Uint8Array(bytes).buffer
14
- }
package/src/dates.ts DELETED
@@ -1,20 +0,0 @@
1
- // Normalize date strings to simplified ISO so that the lexical sort preserves temporal sort.
2
- // Rather than failing on an invalid date format, returns valid unix epoch.
3
- export function toSimplifiedISOSafe(dateStr: string) {
4
- const date = new Date(dateStr)
5
- if (isNaN(date.getTime())) {
6
- return new Date(0).toISOString()
7
- }
8
- const iso = date.toISOString()
9
-
10
- // Date.toISOString() always returns `YYYY-MM-DDTHH:mm:ss.sssZ` or
11
- // `±YYYYYY-MM-DDTHH:mm:ss.sssZ`
12
- // (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
13
- // However, the leading `±` and 6 digit year can break lexical sorting, so we
14
- // need to catch those cases and return a safe value.
15
- if (iso.startsWith('-') || iso.startsWith('+')) {
16
- return new Date(0).toISOString()
17
- }
18
-
19
- return iso // YYYY-MM-DDTHH:mm:ss.sssZ
20
- }
package/src/env.ts DELETED
@@ -1,25 +0,0 @@
1
- import { parseIntWithFallback } from '@atproto/common-web'
2
-
3
- export const envInt = (name: string): number | undefined => {
4
- const str = process.env[name]
5
- return parseIntWithFallback(str, undefined)
6
- }
7
-
8
- export const envStr = (name: string): string | undefined => {
9
- const str = process.env[name]
10
- if (str === undefined || str.length === 0) return undefined
11
- return str
12
- }
13
-
14
- export const envBool = (name: string): boolean | undefined => {
15
- const str = process.env[name]
16
- if (str === 'true' || str === '1') return true
17
- if (str === 'false' || str === '0') return false
18
- return undefined
19
- }
20
-
21
- export const envList = (name: string): string[] => {
22
- const str = process.env[name]
23
- if (str === undefined || str.length === 0) return []
24
- return str.split(',')
25
- }
package/src/fs.ts DELETED
@@ -1,54 +0,0 @@
1
- import { constants } from 'node:fs'
2
- import fs from 'node:fs/promises'
3
- import { isErrnoException } from '@atproto/common-web'
4
-
5
- export const fileExists = async (location: string): Promise<boolean> => {
6
- try {
7
- await fs.access(location, constants.F_OK)
8
- return true
9
- } catch (err) {
10
- if (isErrnoException(err) && err.code === 'ENOENT') {
11
- return false
12
- }
13
- throw err
14
- }
15
- }
16
-
17
- export const readIfExists = async (
18
- filepath: string,
19
- ): Promise<Uint8Array | undefined> => {
20
- try {
21
- return await fs.readFile(filepath)
22
- } catch (err) {
23
- if (isErrnoException(err) && err.code === 'ENOENT') {
24
- return
25
- }
26
- throw err
27
- }
28
- }
29
-
30
- export const rmIfExists = async (
31
- filepath: string,
32
- recursive = false,
33
- ): Promise<void> => {
34
- await fs.rm(filepath, {
35
- force: true, // ignore errors if the file/directory does not exist
36
- recursive,
37
- maxRetries: 5,
38
- retryDelay: 50,
39
- })
40
- }
41
-
42
- export const renameIfExists = async (
43
- oldPath: string,
44
- newPath: string,
45
- ): Promise<void> => {
46
- try {
47
- await fs.rename(oldPath, newPath)
48
- } catch (err) {
49
- if (isErrnoException(err) && err.code === 'ENOENT') {
50
- return
51
- }
52
- throw err
53
- }
54
- }
package/src/index.ts DELETED
@@ -1,10 +0,0 @@
1
- export * from '@atproto/common-web'
2
- export * from './buffers.js'
3
- export * from './dates.js'
4
- export * from './env.js'
5
- export * from './fs.js'
6
- export * from './ipld.js'
7
- export * from './ipld-multi.js'
8
- export * from './logger.js'
9
- export * from './obfuscate.js'
10
- export * from './streams.js'
package/src/ipld-multi.ts DELETED
@@ -1,9 +0,0 @@
1
- import { decodeAll } from '@atproto/lex-cbor'
2
- import { LexValue } from '@atproto/lex-data'
3
-
4
- /**
5
- * @deprecated Use {@link decodeAll} from `@atproto/lex-cbor` instead.
6
- */
7
- export function cborDecodeMulti(encoded: Uint8Array): LexValue[] {
8
- return Array.from(decodeAll(encoded))
9
- }
package/src/ipld.ts DELETED
@@ -1,138 +0,0 @@
1
- import { createHash } from 'node:crypto'
2
- import { Transform } from 'node:stream'
3
- import { encode as encodeBlock } from 'multiformats/block'
4
- import type { BlockView, ByteView } from 'multiformats/block/interface'
5
- import { sha256 as hasher } from 'multiformats/hashes/sha2'
6
- import { cidForLex, decode, encode } from '@atproto/lex-cbor'
7
- import {
8
- CBOR_DATA_CODEC,
9
- type CID,
10
- Cid,
11
- LexValue,
12
- asMultiformatsCID,
13
- // eslint-disable-next-line
14
- cidForCbor,
15
- cidForRawHash,
16
- decodeCid,
17
- isCidForBytes,
18
- isTypedLexMap,
19
- validateCidString,
20
- } from '@atproto/lex-data'
21
-
22
- /**
23
- * @deprecated Use {@link encode} from `@atproto/lex-cbor` instead.
24
- */
25
- const cborEncodeLegacy = encode as <T = unknown>(data: T) => ByteView<T>
26
- export { cborEncodeLegacy as cborEncode }
27
-
28
- /**
29
- * @deprecated Use {@link decode} from `@atproto/lex-cbor` instead.
30
- */
31
- const cborDecodeLegacy = decode as <T = unknown>(bytes: ByteView<T>) => T
32
- export { cborDecodeLegacy as cborDecode }
33
-
34
- /**
35
- * @deprecated Use {@link encode} and {@link cidForCbor} from `@atproto/lex-cbor` instead.
36
- */
37
- export async function dataToCborBlock<T>(
38
- value: T,
39
- ): Promise<BlockView<T, 0x71, 0x12, 1>> {
40
- return encodeBlock<T, 0x71, 0x12>({
41
- value,
42
- codec: {
43
- name: 'at-cbor', // Not actually used
44
- code: CBOR_DATA_CODEC,
45
- encode: encode as (data: T) => ByteView<T>,
46
- },
47
- hasher,
48
- })
49
- }
50
-
51
- /**
52
- * @deprecated Use {@link cidForLex} from `@atproto/lex-cbor` instead.
53
- */
54
- async function cidForCborLegacy(data: unknown): Promise<CID> {
55
- return asMultiformatsCID(await cidForLex(data as LexValue))
56
- }
57
- export { cidForCborLegacy as cidForCbor }
58
-
59
- /**
60
- * @deprecated Use {@link validateCidString} from '@atproto/lex-data' instead.
61
- */
62
- export async function isValidCid(cidStr: string): Promise<boolean> {
63
- // @NOTE we keep the wrapper function to return a Promise (for backward
64
- // compatibility).
65
- return validateCidString(cidStr)
66
- }
67
-
68
- /**
69
- * @deprecated Use {@link decode} from `@atproto/lex-cbor`, and {@link isTypedLexMap} from `@atproto/lex-data` instead.
70
- */
71
- export function cborBytesToRecord(bytes: Uint8Array): Record<string, unknown> {
72
- const data = decode(bytes) as LexValue
73
- if (isTypedLexMap(data)) return data
74
-
75
- throw new Error(`Expected record with $type property`)
76
- }
77
-
78
- /**
79
- * @deprecated Use {@link isCidForBytes} from `@atproto/lex-cbor` instead.
80
- */
81
- export async function verifyCidForBytes(
82
- cid: Cid,
83
- bytes: Uint8Array,
84
- ): Promise<void> {
85
- if (!(await isCidForBytes(cid, bytes))) {
86
- throw new Error(`Not a valid CID for bytes (${cid.toString()})`)
87
- }
88
- }
89
-
90
- /**
91
- * @deprecated Use {@link cidForRawHash} from `@atproto/lex-cbor` instead.
92
- */
93
- export function sha256RawToCid(hash: Uint8Array): CID {
94
- return asMultiformatsCID(cidForRawHash(hash))
95
- }
96
-
97
- /**
98
- * @deprecated Use {@link decodeCid} from `@atproto/lex-cbor` instead.
99
- */
100
- export function parseCidFromBytes(bytes: Uint8Array): CID {
101
- return asMultiformatsCID(decodeCid(bytes, { flavor: 'dasl' }))
102
- }
103
-
104
- export class VerifyCidTransform extends Transform {
105
- constructor(public cid: Cid) {
106
- const hasher = createHash('sha256')
107
- super({
108
- transform(chunk, encoding, callback) {
109
- hasher.update(chunk)
110
- callback(null, chunk)
111
- },
112
- flush(callback) {
113
- try {
114
- const actual = sha256RawToCid(hasher.digest())
115
- if (actual.equals(cid)) {
116
- return callback()
117
- } else {
118
- return callback(new VerifyCidError(cid, actual))
119
- }
120
- } catch (err) {
121
- return callback(asError(err))
122
- }
123
- },
124
- })
125
- }
126
- }
127
-
128
- const asError = (err: unknown): Error =>
129
- err instanceof Error ? err : new Error('Unexpected error', { cause: err })
130
-
131
- export class VerifyCidError extends Error {
132
- constructor(
133
- public expected: Cid,
134
- public actual: Cid,
135
- ) {
136
- super('Bad cid check')
137
- }
138
- }
package/src/logger.ts DELETED
@@ -1,29 +0,0 @@
1
- import { type Logger, destination, pino } from 'pino'
2
-
3
- const enabled = /^(true|t|1)$/i.test(process.env.LOG_ENABLED ?? '0')
4
- const dest = process.env.LOG_DESTINATION
5
- const level = process.env.LOG_LEVEL || 'info'
6
- const systems = process.env.LOG_SYSTEMS?.trim()
7
- ? process.env.LOG_SYSTEMS.replace(',', ' ').split(/\s+/).filter(Boolean)
8
- : null
9
-
10
- const rootLogger = pino(
11
- { enabled, level },
12
- dest ? destination(dest) : undefined,
13
- )
14
-
15
- const subsystems = new Map<string, Logger>()
16
-
17
- export const subsystemLogger = (name: string): Logger => {
18
- if (subsystems.has(name)) return subsystems.get(name)!
19
-
20
- // can't disable child loggers, so we just set their level to "silent"
21
- // to effectively turn them off
22
- const subsystemEnabled = !systems || systems.includes(name)
23
- const subsystemLevel = enabled && subsystemEnabled ? level : 'silent'
24
-
25
- const logger = rootLogger.child({ name }, { level: subsystemLevel })
26
-
27
- subsystems.set(name, logger)
28
- return logger
29
- }
package/src/obfuscate.ts DELETED
@@ -1,85 +0,0 @@
1
- export function obfuscateEmail(email: string) {
2
- const [local, domain] = email.split('@')
3
- return `${obfuscateWord(local)}@${obfuscateWord(domain)}`
4
- }
5
-
6
- export function obfuscateWord(word: string) {
7
- return `${word.charAt(0)}***${word.charAt(word.length - 1)}`
8
- }
9
-
10
- export function obfuscateHeaders(headers: Record<string, string>) {
11
- const obfuscatedHeaders: Record<string, string> = {}
12
- for (const key in headers) {
13
- if (key.toLowerCase() === 'authorization') {
14
- obfuscatedHeaders[key] = obfuscateAuthHeader(headers[key])
15
- } else if (key.toLowerCase() === 'dpop') {
16
- obfuscatedHeaders[key] = obfuscateJwt(headers[key]) || 'Invalid'
17
- } else {
18
- obfuscatedHeaders[key] = headers[key]
19
- }
20
- }
21
- return obfuscatedHeaders
22
- }
23
-
24
- export function obfuscateAuthHeader(authHeader: string): string {
25
- // This is a hot path (runs on every request). Avoid using split() or regex.
26
-
27
- const spaceIdx = authHeader.indexOf(' ')
28
- if (spaceIdx === -1) return 'Invalid'
29
-
30
- const type = authHeader.slice(0, spaceIdx)
31
- switch (type.toLowerCase()) {
32
- case 'bearer':
33
- case 'dpop':
34
- return `${type} ${obfuscateBearer(authHeader.slice(spaceIdx + 1))}`
35
- case 'basic':
36
- return `${type} ${obfuscateBasic(authHeader.slice(spaceIdx + 1)) || 'Invalid'}`
37
- default:
38
- return `Invalid`
39
- }
40
- }
41
-
42
- export function obfuscateBasic(token: string): null | string {
43
- if (!token) return null
44
- const buffer = Buffer.from(token, 'base64')
45
- if (!buffer.length) return null // Buffer.from will silently ignore invalid base64 chars
46
- const authHeader = buffer.toString('utf8')
47
- const colIdx = authHeader.indexOf(':')
48
- if (colIdx === -1) return null
49
- const username = authHeader.slice(0, colIdx)
50
- return `${username}:***`
51
- }
52
-
53
- export function obfuscateBearer(token: string): string {
54
- return obfuscateJwt(token) || obfuscateToken(token)
55
- }
56
-
57
- export function obfuscateToken(token: string): string {
58
- if (token.length >= 12) return obfuscateWord(token)
59
- return token ? '***' : ''
60
- }
61
-
62
- export function obfuscateJwt(token: string): null | string {
63
- const firstDot = token.indexOf('.')
64
- if (firstDot === -1) return null
65
-
66
- const secondDot = token.indexOf('.', firstDot + 1)
67
- if (secondDot === -1) return null
68
-
69
- // Expected to be missing
70
- const thirdDot = token.indexOf('.', secondDot + 1)
71
- if (thirdDot !== -1) return null
72
-
73
- try {
74
- const payloadEnc = token.slice(firstDot + 1, secondDot)
75
- const payloadJson = Buffer.from(payloadEnc, 'base64').toString('utf8')
76
- const payload = JSON.parse(payloadJson)
77
- if (typeof payload.sub === 'string') return payload.sub
78
- } catch {
79
- // Invalid JWT
80
- return null
81
- }
82
-
83
- // Strip the signature
84
- return token.slice(0, secondDot) + '.obfuscated'
85
- }