@atproto/common 0.0.0-spaces-alpha-20260818021221

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 (48) hide show
  1. package/CHANGELOG.md +439 -0
  2. package/LICENSE.txt +7 -0
  3. package/README.md +15 -0
  4. package/dist/buffers.d.ts +3 -0
  5. package/dist/buffers.d.ts.map +1 -0
  6. package/dist/buffers.js +10 -0
  7. package/dist/buffers.js.map +1 -0
  8. package/dist/car.d.ts +40 -0
  9. package/dist/car.d.ts.map +1 -0
  10. package/dist/car.js +267 -0
  11. package/dist/car.js.map +1 -0
  12. package/dist/dates.d.ts +2 -0
  13. package/dist/dates.d.ts.map +1 -0
  14. package/dist/dates.js +19 -0
  15. package/dist/dates.js.map +1 -0
  16. package/dist/env.d.ts +5 -0
  17. package/dist/env.d.ts.map +1 -0
  18. package/dist/env.js +26 -0
  19. package/dist/env.js.map +1 -0
  20. package/dist/fs.d.ts +5 -0
  21. package/dist/fs.d.ts.map +1 -0
  22. package/dist/fs.js +46 -0
  23. package/dist/fs.js.map +1 -0
  24. package/dist/index.d.ts +12 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +12 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/ipld-multi.d.ts +6 -0
  29. package/dist/ipld-multi.d.ts.map +1 -0
  30. package/dist/ipld-multi.js +8 -0
  31. package/dist/ipld-multi.js.map +1 -0
  32. package/dist/ipld.d.ts +52 -0
  33. package/dist/ipld.d.ts.map +1 -0
  34. package/dist/ipld.js +111 -0
  35. package/dist/ipld.js.map +1 -0
  36. package/dist/logger.d.ts +16 -0
  37. package/dist/logger.d.ts.map +1 -0
  38. package/dist/logger.js +31 -0
  39. package/dist/logger.js.map +1 -0
  40. package/dist/obfuscate.d.ts +9 -0
  41. package/dist/obfuscate.d.ts.map +1 -0
  42. package/dist/obfuscate.js +85 -0
  43. package/dist/obfuscate.js.map +1 -0
  44. package/dist/streams.d.ts +32 -0
  45. package/dist/streams.d.ts.map +1 -0
  46. package/dist/streams.js +190 -0
  47. package/dist/streams.js.map +1 -0
  48. package/package.json +46 -0
package/dist/ipld.js ADDED
@@ -0,0 +1,111 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { Transform } from 'node:stream';
3
+ import { encode as encodeBlock } from 'multiformats/block';
4
+ import { sha256 as hasher } from 'multiformats/hashes/sha2';
5
+ import { cidForLex, decode, encode } from '@atproto/lex-cbor';
6
+ import { CBOR_DATA_CODEC, asMultiformatsCID,
7
+ // eslint-disable-next-line
8
+ cidForCbor, cidForRawHash, decodeCid, isCidForBytes, isTypedLexMap, validateCidString, } from '@atproto/lex-data';
9
+ /**
10
+ * @deprecated Use {@link encode} from `@atproto/lex-cbor` instead.
11
+ */
12
+ const cborEncodeLegacy = encode;
13
+ export { cborEncodeLegacy as cborEncode };
14
+ /**
15
+ * @deprecated Use {@link decode} from `@atproto/lex-cbor` instead.
16
+ */
17
+ const cborDecodeLegacy = decode;
18
+ export { cborDecodeLegacy as cborDecode };
19
+ /**
20
+ * @deprecated Use {@link encode} and {@link cidForCbor} from `@atproto/lex-cbor` instead.
21
+ */
22
+ export async function dataToCborBlock(value) {
23
+ return encodeBlock({
24
+ value,
25
+ codec: {
26
+ name: 'at-cbor', // Not actually used
27
+ code: CBOR_DATA_CODEC,
28
+ encode: encode,
29
+ },
30
+ hasher,
31
+ });
32
+ }
33
+ /**
34
+ * @deprecated Use {@link cidForLex} from `@atproto/lex-cbor` instead.
35
+ */
36
+ async function cidForCborLegacy(data) {
37
+ return asMultiformatsCID(await cidForLex(data));
38
+ }
39
+ export { cidForCborLegacy as cidForCbor };
40
+ /**
41
+ * @deprecated Use {@link validateCidString} from '@atproto/lex-data' instead.
42
+ */
43
+ export async function isValidCid(cidStr) {
44
+ // @NOTE we keep the wrapper function to return a Promise (for backward
45
+ // compatibility).
46
+ return validateCidString(cidStr);
47
+ }
48
+ /**
49
+ * @deprecated Use {@link decode} from `@atproto/lex-cbor`, and {@link isTypedLexMap} from `@atproto/lex-data` instead.
50
+ */
51
+ export function cborBytesToRecord(bytes) {
52
+ const data = decode(bytes);
53
+ if (isTypedLexMap(data))
54
+ return data;
55
+ throw new Error(`Expected record with $type property`);
56
+ }
57
+ /**
58
+ * @deprecated Use {@link isCidForBytes} from `@atproto/lex-cbor` instead.
59
+ */
60
+ export async function verifyCidForBytes(cid, bytes) {
61
+ if (!(await isCidForBytes(cid, bytes))) {
62
+ throw new Error(`Not a valid CID for bytes (${cid.toString()})`);
63
+ }
64
+ }
65
+ /**
66
+ * @deprecated Use {@link cidForRawHash} from `@atproto/lex-cbor` instead.
67
+ */
68
+ export function sha256RawToCid(hash) {
69
+ return asMultiformatsCID(cidForRawHash(hash));
70
+ }
71
+ /**
72
+ * @deprecated Use {@link decodeCid} from `@atproto/lex-cbor` instead.
73
+ */
74
+ export function parseCidFromBytes(bytes) {
75
+ return asMultiformatsCID(decodeCid(bytes, { flavor: 'dasl' }));
76
+ }
77
+ export class VerifyCidTransform extends Transform {
78
+ constructor(cid) {
79
+ const hasher = createHash('sha256');
80
+ super({
81
+ transform(chunk, encoding, callback) {
82
+ hasher.update(chunk);
83
+ callback(null, chunk);
84
+ },
85
+ flush(callback) {
86
+ try {
87
+ const actual = sha256RawToCid(hasher.digest());
88
+ if (actual.equals(cid)) {
89
+ return callback();
90
+ }
91
+ else {
92
+ return callback(new VerifyCidError(cid, actual));
93
+ }
94
+ }
95
+ catch (err) {
96
+ return callback(asError(err));
97
+ }
98
+ },
99
+ });
100
+ this.cid = cid;
101
+ }
102
+ }
103
+ const asError = (err) => err instanceof Error ? err : new Error('Unexpected error', { cause: err });
104
+ export class VerifyCidError extends Error {
105
+ constructor(expected, actual) {
106
+ super('Bad cid check');
107
+ this.expected = expected;
108
+ this.actual = actual;
109
+ }
110
+ }
111
+ //# sourceMappingURL=ipld.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ipld.js","sourceRoot":"","sources":["../src/ipld.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto'\nimport { Transform } from 'node:stream'\nimport { encode as encodeBlock } from 'multiformats/block'\nimport type { BlockView, ByteView } from 'multiformats/block/interface'\nimport { sha256 as hasher } from 'multiformats/hashes/sha2'\nimport { cidForLex, decode, encode } from '@atproto/lex-cbor'\nimport {\n CBOR_DATA_CODEC,\n type CID,\n type Cid,\n type LexValue,\n asMultiformatsCID,\n // eslint-disable-next-line\n cidForCbor,\n cidForRawHash,\n decodeCid,\n isCidForBytes,\n isTypedLexMap,\n validateCidString,\n} from '@atproto/lex-data'\n\n/**\n * @deprecated Use {@link encode} from `@atproto/lex-cbor` instead.\n */\nconst cborEncodeLegacy = encode as <T = unknown>(data: T) => ByteView<T>\nexport { cborEncodeLegacy as cborEncode }\n\n/**\n * @deprecated Use {@link decode} from `@atproto/lex-cbor` instead.\n */\nconst cborDecodeLegacy = decode as <T = unknown>(bytes: ByteView<T>) => T\nexport { cborDecodeLegacy as cborDecode }\n\n/**\n * @deprecated Use {@link encode} and {@link cidForCbor} from `@atproto/lex-cbor` instead.\n */\nexport async function dataToCborBlock<T>(\n value: T,\n): Promise<BlockView<T, 0x71, 0x12, 1>> {\n return encodeBlock<T, 0x71, 0x12>({\n value,\n codec: {\n name: 'at-cbor', // Not actually used\n code: CBOR_DATA_CODEC,\n encode: encode as (data: T) => ByteView<T>,\n },\n hasher,\n })\n}\n\n/**\n * @deprecated Use {@link cidForLex} from `@atproto/lex-cbor` instead.\n */\nasync function cidForCborLegacy(data: unknown): Promise<CID> {\n return asMultiformatsCID(await cidForLex(data as LexValue))\n}\nexport { cidForCborLegacy as cidForCbor }\n\n/**\n * @deprecated Use {@link validateCidString} from '@atproto/lex-data' instead.\n */\nexport async function isValidCid(cidStr: string): Promise<boolean> {\n // @NOTE we keep the wrapper function to return a Promise (for backward\n // compatibility).\n return validateCidString(cidStr)\n}\n\n/**\n * @deprecated Use {@link decode} from `@atproto/lex-cbor`, and {@link isTypedLexMap} from `@atproto/lex-data` instead.\n */\nexport function cborBytesToRecord(bytes: Uint8Array): Record<string, unknown> {\n const data = decode(bytes) as LexValue\n if (isTypedLexMap(data)) return data\n\n throw new Error(`Expected record with $type property`)\n}\n\n/**\n * @deprecated Use {@link isCidForBytes} from `@atproto/lex-cbor` instead.\n */\nexport async function verifyCidForBytes(\n cid: Cid,\n bytes: Uint8Array,\n): Promise<void> {\n if (!(await isCidForBytes(cid, bytes))) {\n throw new Error(`Not a valid CID for bytes (${cid.toString()})`)\n }\n}\n\n/**\n * @deprecated Use {@link cidForRawHash} from `@atproto/lex-cbor` instead.\n */\nexport function sha256RawToCid(hash: Uint8Array): CID {\n return asMultiformatsCID(cidForRawHash(hash))\n}\n\n/**\n * @deprecated Use {@link decodeCid} from `@atproto/lex-cbor` instead.\n */\nexport function parseCidFromBytes(bytes: Uint8Array): CID {\n return asMultiformatsCID(decodeCid(bytes, { flavor: 'dasl' }))\n}\n\nexport class VerifyCidTransform extends Transform {\n constructor(public cid: Cid) {\n const hasher = createHash('sha256')\n super({\n transform(chunk, encoding, callback) {\n hasher.update(chunk)\n callback(null, chunk)\n },\n flush(callback) {\n try {\n const actual = sha256RawToCid(hasher.digest())\n if (actual.equals(cid)) {\n return callback()\n } else {\n return callback(new VerifyCidError(cid, actual))\n }\n } catch (err) {\n return callback(asError(err))\n }\n },\n })\n }\n}\n\nconst asError = (err: unknown): Error =>\n err instanceof Error ? err : new Error('Unexpected error', { cause: err })\n\nexport class VerifyCidError extends Error {\n constructor(\n public expected: Cid,\n public actual: Cid,\n ) {\n super('Bad cid check')\n }\n}\n"]}
@@ -0,0 +1,16 @@
1
+ import { type Logger, type SerializerFn } from 'pino';
2
+ export declare function createLogger(name: string, options?: {
3
+ serializers?: {
4
+ [key: string]: SerializerFn;
5
+ };
6
+ msgPrefix?: string;
7
+ }): Logger;
8
+ /**
9
+ * @deprecated The use of singleton loggers is deprecated as they don't allow
10
+ * for proper configuration of serializers, and are not really used across the
11
+ * "atproto" mono repo. Indeed, whenever used, loggers are created and
12
+ * re-exported from a central place, achieving the same effect as a singleton
13
+ * logger, but with more flexibility. Use {@link createLogger} instead.
14
+ */
15
+ export declare function subsystemLogger(name: string): Logger;
16
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,YAAY,EAAqB,MAAM,MAAM,CAAA;AAcxE,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;IACR,WAAW,CAAC,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAAA;KAAE,CAAA;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,GACA,MAAM,CAOR;AAID;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKpD"}
package/dist/logger.js ADDED
@@ -0,0 +1,31 @@
1
+ import { destination, pino } from 'pino';
2
+ const enabled = /^(true|t|1)$/i.test(process.env.LOG_ENABLED ?? '0');
3
+ const dest = process.env.LOG_DESTINATION;
4
+ const level = process.env.LOG_LEVEL || 'info';
5
+ const systems = process.env.LOG_SYSTEMS?.trim()
6
+ ? process.env.LOG_SYSTEMS.replace(',', ' ').split(/\s+/).filter(Boolean)
7
+ : null;
8
+ const rootLogger = /*#__PURE__*/ pino({ enabled, level }, dest ? destination(dest) : undefined);
9
+ export function createLogger(name, options) {
10
+ // can't disable child loggers, so we just set their level to "silent"
11
+ // to effectively turn them off
12
+ const subsystemEnabled = !systems || systems.includes(name);
13
+ const subsystemLevel = enabled && subsystemEnabled ? level : 'silent';
14
+ return rootLogger.child({ name }, { ...options, level: subsystemLevel });
15
+ }
16
+ const subsystems = new Map();
17
+ /**
18
+ * @deprecated The use of singleton loggers is deprecated as they don't allow
19
+ * for proper configuration of serializers, and are not really used across the
20
+ * "atproto" mono repo. Indeed, whenever used, loggers are created and
21
+ * re-exported from a central place, achieving the same effect as a singleton
22
+ * logger, but with more flexibility. Use {@link createLogger} instead.
23
+ */
24
+ export function subsystemLogger(name) {
25
+ if (subsystems.has(name))
26
+ return subsystems.get(name);
27
+ const logger = createLogger(name);
28
+ subsystems.set(name, logger);
29
+ return logger;
30
+ }
31
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkC,WAAW,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAExE,MAAM,OAAO,GAAG,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,CAAA;AACpE,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAA;AACxC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,MAAM,CAAA;AAC7C,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,EAAE;IAC7C,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;IACxE,CAAC,CAAC,IAAI,CAAA;AAER,MAAM,UAAU,GAAG,aAAa,CAAC,IAAI,CACnC,EAAE,OAAO,EAAE,KAAK,EAAE,EAClB,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CACrC,CAAA;AAED,MAAM,UAAU,YAAY,CAC1B,IAAY,EACZ,OAGC;IAED,sEAAsE;IACtE,+BAA+B;IAC/B,MAAM,gBAAgB,GAAG,CAAC,OAAO,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;IAC3D,MAAM,cAAc,GAAG,OAAO,IAAI,gBAAgB,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAA;IAErE,OAAO,UAAU,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAA;AAC1E,CAAC;AAED,MAAM,UAAU,GAAG,IAAI,GAAG,EAAkB,CAAA;AAE5C;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,UAAU,CAAC,GAAG,CAAC,IAAI,CAAE,CAAA;IACtD,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,CAAA;IACjC,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IAC5B,OAAO,MAAM,CAAA;AACf,CAAC","sourcesContent":["import { type Logger, type SerializerFn, destination, pino } from 'pino'\n\nconst enabled = /^(true|t|1)$/i.test(process.env.LOG_ENABLED ?? '0')\nconst dest = process.env.LOG_DESTINATION\nconst level = process.env.LOG_LEVEL || 'info'\nconst systems = process.env.LOG_SYSTEMS?.trim()\n ? process.env.LOG_SYSTEMS.replace(',', ' ').split(/\\s+/).filter(Boolean)\n : null\n\nconst rootLogger = /*#__PURE__*/ pino(\n { enabled, level },\n dest ? destination(dest) : undefined,\n)\n\nexport function createLogger(\n name: string,\n options?: {\n serializers?: { [key: string]: SerializerFn }\n msgPrefix?: string\n },\n): Logger {\n // can't disable child loggers, so we just set their level to \"silent\"\n // to effectively turn them off\n const subsystemEnabled = !systems || systems.includes(name)\n const subsystemLevel = enabled && subsystemEnabled ? level : 'silent'\n\n return rootLogger.child({ name }, { ...options, level: subsystemLevel })\n}\n\nconst subsystems = new Map<string, Logger>()\n\n/**\n * @deprecated The use of singleton loggers is deprecated as they don't allow\n * for proper configuration of serializers, and are not really used across the\n * \"atproto\" mono repo. Indeed, whenever used, loggers are created and\n * re-exported from a central place, achieving the same effect as a singleton\n * logger, but with more flexibility. Use {@link createLogger} instead.\n */\nexport function subsystemLogger(name: string): Logger {\n if (subsystems.has(name)) return subsystems.get(name)!\n const logger = createLogger(name)\n subsystems.set(name, logger)\n return logger\n}\n"]}
@@ -0,0 +1,9 @@
1
+ export declare function obfuscateEmail(email: string): string;
2
+ export declare function obfuscateWord(word: string): string;
3
+ export declare function obfuscateHeaders(headers: Record<string, string>): Record<string, string>;
4
+ export declare function obfuscateAuthHeader(authHeader: string): string;
5
+ export declare function obfuscateBasic(token: string): null | string;
6
+ export declare function obfuscateBearer(token: string): string;
7
+ export declare function obfuscateToken(token: string): string;
8
+ export declare function obfuscateJwt(token: string): null | string;
9
+ //# sourceMappingURL=obfuscate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"obfuscate.d.ts","sourceRoot":"","sources":["../src/obfuscate.ts"],"names":[],"mappings":"AAAA,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,UAG3C;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,UAEzC;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,0BAY/D;AAED,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAgB9D;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAS3D;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGpD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAuBzD"}
@@ -0,0 +1,85 @@
1
+ export function obfuscateEmail(email) {
2
+ const [local, domain] = email.split('@');
3
+ return `${obfuscateWord(local)}@${obfuscateWord(domain)}`;
4
+ }
5
+ export function obfuscateWord(word) {
6
+ return `${word.charAt(0)}***${word.charAt(word.length - 1)}`;
7
+ }
8
+ export function obfuscateHeaders(headers) {
9
+ const obfuscatedHeaders = {};
10
+ for (const key in headers) {
11
+ if (key.toLowerCase() === 'authorization') {
12
+ obfuscatedHeaders[key] = obfuscateAuthHeader(headers[key]);
13
+ }
14
+ else if (key.toLowerCase() === 'dpop') {
15
+ obfuscatedHeaders[key] = obfuscateJwt(headers[key]) || 'Invalid';
16
+ }
17
+ else {
18
+ obfuscatedHeaders[key] = headers[key];
19
+ }
20
+ }
21
+ return obfuscatedHeaders;
22
+ }
23
+ export function obfuscateAuthHeader(authHeader) {
24
+ // This is a hot path (runs on every request). Avoid using split() or regex.
25
+ const spaceIdx = authHeader.indexOf(' ');
26
+ if (spaceIdx === -1)
27
+ return 'Invalid';
28
+ const type = authHeader.slice(0, spaceIdx);
29
+ switch (type.toLowerCase()) {
30
+ case 'bearer':
31
+ case 'dpop':
32
+ return `${type} ${obfuscateBearer(authHeader.slice(spaceIdx + 1))}`;
33
+ case 'basic':
34
+ return `${type} ${obfuscateBasic(authHeader.slice(spaceIdx + 1)) || 'Invalid'}`;
35
+ default:
36
+ return `Invalid`;
37
+ }
38
+ }
39
+ export function obfuscateBasic(token) {
40
+ if (!token)
41
+ return null;
42
+ const buffer = Buffer.from(token, 'base64');
43
+ if (!buffer.length)
44
+ return null; // Buffer.from will silently ignore invalid base64 chars
45
+ const authHeader = buffer.toString('utf8');
46
+ const colIdx = authHeader.indexOf(':');
47
+ if (colIdx === -1)
48
+ return null;
49
+ const username = authHeader.slice(0, colIdx);
50
+ return `${username}:***`;
51
+ }
52
+ export function obfuscateBearer(token) {
53
+ return obfuscateJwt(token) || obfuscateToken(token);
54
+ }
55
+ export function obfuscateToken(token) {
56
+ if (token.length >= 12)
57
+ return obfuscateWord(token);
58
+ return token ? '***' : '';
59
+ }
60
+ export function obfuscateJwt(token) {
61
+ const firstDot = token.indexOf('.');
62
+ if (firstDot === -1)
63
+ return null;
64
+ const secondDot = token.indexOf('.', firstDot + 1);
65
+ if (secondDot === -1)
66
+ return null;
67
+ // Expected to be missing
68
+ const thirdDot = token.indexOf('.', secondDot + 1);
69
+ if (thirdDot !== -1)
70
+ return null;
71
+ try {
72
+ const payloadEnc = token.slice(firstDot + 1, secondDot);
73
+ const payloadJson = Buffer.from(payloadEnc, 'base64').toString('utf8');
74
+ const payload = JSON.parse(payloadJson);
75
+ if (typeof payload.sub === 'string')
76
+ return payload.sub;
77
+ }
78
+ catch {
79
+ // Invalid JWT
80
+ return null;
81
+ }
82
+ // Strip the signature
83
+ return token.slice(0, secondDot) + '.obfuscated';
84
+ }
85
+ //# sourceMappingURL=obfuscate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"obfuscate.js","sourceRoot":"","sources":["../src/obfuscate.ts"],"names":[],"mappings":"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","sourcesContent":["export function obfuscateEmail(email: string) {\n const [local, domain] = email.split('@')\n return `${obfuscateWord(local)}@${obfuscateWord(domain)}`\n}\n\nexport function obfuscateWord(word: string) {\n return `${word.charAt(0)}***${word.charAt(word.length - 1)}`\n}\n\nexport function obfuscateHeaders(headers: Record<string, string>) {\n const obfuscatedHeaders: Record<string, string> = {}\n for (const key in headers) {\n if (key.toLowerCase() === 'authorization') {\n obfuscatedHeaders[key] = obfuscateAuthHeader(headers[key])\n } else if (key.toLowerCase() === 'dpop') {\n obfuscatedHeaders[key] = obfuscateJwt(headers[key]) || 'Invalid'\n } else {\n obfuscatedHeaders[key] = headers[key]\n }\n }\n return obfuscatedHeaders\n}\n\nexport function obfuscateAuthHeader(authHeader: string): string {\n // This is a hot path (runs on every request). Avoid using split() or regex.\n\n const spaceIdx = authHeader.indexOf(' ')\n if (spaceIdx === -1) return 'Invalid'\n\n const type = authHeader.slice(0, spaceIdx)\n switch (type.toLowerCase()) {\n case 'bearer':\n case 'dpop':\n return `${type} ${obfuscateBearer(authHeader.slice(spaceIdx + 1))}`\n case 'basic':\n return `${type} ${obfuscateBasic(authHeader.slice(spaceIdx + 1)) || 'Invalid'}`\n default:\n return `Invalid`\n }\n}\n\nexport function obfuscateBasic(token: string): null | string {\n if (!token) return null\n const buffer = Buffer.from(token, 'base64')\n if (!buffer.length) return null // Buffer.from will silently ignore invalid base64 chars\n const authHeader = buffer.toString('utf8')\n const colIdx = authHeader.indexOf(':')\n if (colIdx === -1) return null\n const username = authHeader.slice(0, colIdx)\n return `${username}:***`\n}\n\nexport function obfuscateBearer(token: string): string {\n return obfuscateJwt(token) || obfuscateToken(token)\n}\n\nexport function obfuscateToken(token: string): string {\n if (token.length >= 12) return obfuscateWord(token)\n return token ? '***' : ''\n}\n\nexport function obfuscateJwt(token: string): null | string {\n const firstDot = token.indexOf('.')\n if (firstDot === -1) return null\n\n const secondDot = token.indexOf('.', firstDot + 1)\n if (secondDot === -1) return null\n\n // Expected to be missing\n const thirdDot = token.indexOf('.', secondDot + 1)\n if (thirdDot !== -1) return null\n\n try {\n const payloadEnc = token.slice(firstDot + 1, secondDot)\n const payloadJson = Buffer.from(payloadEnc, 'base64').toString('utf8')\n const payload = JSON.parse(payloadJson)\n if (typeof payload.sub === 'string') return payload.sub\n } catch {\n // Invalid JWT\n return null\n }\n\n // Strip the signature\n return token.slice(0, secondDot) + '.obfuscated'\n}\n"]}
@@ -0,0 +1,32 @@
1
+ import { type Duplex, Readable, type Stream, Transform, type TransformCallback } from 'node:stream';
2
+ export declare const forwardStreamErrors: (...streams: Stream[]) => void;
3
+ export declare const cloneStream: (stream: Readable) => Readable;
4
+ export declare const streamSize: (stream: Readable) => Promise<number>;
5
+ export declare const streamToBytes: (stream: AsyncIterable<Uint8Array>) => Promise<Uint8Array<ArrayBuffer>>;
6
+ export declare const streamToNodeBuffer: (stream: Iterable<Uint8Array> | AsyncIterable<Uint8Array>) => Promise<Buffer>;
7
+ export declare const byteIterableToStream: (iter: AsyncIterable<Uint8Array>) => Readable;
8
+ /**
9
+ * Coalesce a stream of Uint8Array chunks into larger chunks of at least the
10
+ * specified size ({@link minChunkSize}). This is useful for optimizing
11
+ * downstream processing that benefits from larger chunk sizes, such as
12
+ * compression or hashing.
13
+ */
14
+ export declare const coalesceByteStream: (stream: Iterable<Uint8Array> | AsyncIterable<Uint8Array>, minChunkSize: number) => Readable;
15
+ export declare const bytesToStream: (bytes: Uint8Array) => Readable;
16
+ export declare class MaxSizeChecker extends Transform {
17
+ maxSize: number;
18
+ createError: () => Error;
19
+ totalSize: number;
20
+ constructor(maxSize: number, createError: () => Error);
21
+ _transform(chunk: Uint8Array, _enc: BufferEncoding, cb: TransformCallback): void;
22
+ }
23
+ export declare function decodeStream(stream: Readable, contentEncoding?: string | string[]): Readable;
24
+ export declare function decodeStream(stream: AsyncIterable<Uint8Array>, contentEncoding?: string | string[]): AsyncIterable<Uint8Array> | Readable;
25
+ /**
26
+ * Create a series of decoding streams based on the content-encoding header. The
27
+ * resulting streams should be piped together to decode the content.
28
+ *
29
+ * @see {@link https://datatracker.ietf.org/doc/html/rfc9110#section-8.4.1}
30
+ */
31
+ export declare function createDecoders(contentEncoding?: string | string[]): Duplex[];
32
+ //# sourceMappingURL=streams.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streams.d.ts","sourceRoot":"","sources":["../src/streams.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,MAAM,EAEX,QAAQ,EACR,KAAK,MAAM,EACX,SAAS,EACT,KAAK,iBAAiB,EAEvB,MAAM,aAAa,CAAA;AAGpB,eAAO,MAAM,mBAAmB,eAAgB,MAAM,EAAE,SAOvD,CAAA;AAED,eAAO,MAAM,WAAW,WAAY,QAAQ,KAAG,QAI9C,CAAA;AAED,eAAO,MAAM,UAAU,WAAkB,QAAQ,KAAG,OAAO,CAAC,MAAM,CAMjE,CAAA;AAED,eAAO,MAAM,aAAa,WAAkB,aAAa,CAAC,UAAU,CAAC,qCAInB,CAAA;AAGlD,eAAO,MAAM,kBAAkB,WACrB,QAAQ,CAAC,UAAU,CAAC,GAAG,aAAa,CAAC,UAAU,CAAC,KACvD,OAAO,CAAC,MAAM,CAYhB,CAAA;AAED,eAAO,MAAM,oBAAoB,SACzB,aAAa,CAAC,UAAU,CAAC,KAC9B,QAEF,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,WACrB,QAAQ,CAAC,UAAU,CAAC,GAAG,aAAa,CAAC,UAAU,CAAC,gBAC1C,MAAM,KACnB,QAkEF,CAAA;AAED,eAAO,MAAM,aAAa,UAAW,UAAU,KAAG,QAKjD,CAAA;AAED,qBAAa,cAAe,SAAQ,SAAS;IAGlC,OAAO,EAAE,MAAM;IACf,WAAW,EAAE,MAAM,KAAK;IAHjC,SAAS,SAAI;IACb,YACS,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,KAAK,EAGhC;IACD,UAAU,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,cAAc,EAAE,EAAE,EAAE,iBAAiB,QAOxE;CACF;AAED,wBAAgB,YAAY,CAC1B,MAAM,EAAE,QAAQ,EAChB,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAClC,QAAQ,CAAA;AACX,wBAAgB,YAAY,CAC1B,MAAM,EAAE,aAAa,CAAC,UAAU,CAAC,EACjC,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAClC,aAAa,CAAC,UAAU,CAAC,GAAG,QAAQ,CAAA;AAUvC;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,EAAE,CAqB5E"}
@@ -0,0 +1,190 @@
1
+ import { PassThrough, Readable, Transform, pipeline, } from 'node:stream';
2
+ import { createBrotliDecompress, createGunzip, createInflate } from 'node:zlib';
3
+ export const forwardStreamErrors = (...streams) => {
4
+ for (let i = 1; i < streams.length; ++i) {
5
+ const prev = streams[i - 1];
6
+ const next = streams[i];
7
+ prev.once('error', (err) => next.emit('error', err));
8
+ }
9
+ };
10
+ export const cloneStream = (stream) => {
11
+ const passthrough = new PassThrough();
12
+ forwardStreamErrors(stream, passthrough);
13
+ return stream.pipe(passthrough);
14
+ };
15
+ export const streamSize = async (stream) => {
16
+ let size = 0;
17
+ for await (const chunk of stream) {
18
+ size += Buffer.byteLength(chunk);
19
+ }
20
+ return size;
21
+ };
22
+ export const streamToBytes = async (stream) =>
23
+ // @NOTE Though Buffer is a sub-class of Uint8Array, we have observed
24
+ // inconsistencies when using a Buffer in place of Uint8Array. For this
25
+ // reason, we convert the Buffer to a Uint8Array.
26
+ new Uint8Array(await streamToNodeBuffer(stream));
27
+ // streamToBuffer identifier name already taken by @atproto/common-web
28
+ export const streamToNodeBuffer = async (stream) => {
29
+ const chunks = [];
30
+ let totalLength = 0; // keep track of total length for Buffer.concat
31
+ for await (const chunk of stream) {
32
+ if (chunk instanceof Uint8Array) {
33
+ chunks.push(chunk);
34
+ totalLength += Buffer.byteLength(chunk);
35
+ }
36
+ else {
37
+ throw new TypeError('expected Uint8Array');
38
+ }
39
+ }
40
+ return Buffer.concat(chunks, totalLength);
41
+ };
42
+ export const byteIterableToStream = (iter) => {
43
+ return Readable.from(iter, { objectMode: false });
44
+ };
45
+ /**
46
+ * Coalesce a stream of Uint8Array chunks into larger chunks of at least the
47
+ * specified size ({@link minChunkSize}). This is useful for optimizing
48
+ * downstream processing that benefits from larger chunk sizes, such as
49
+ * compression or hashing.
50
+ */
51
+ export const coalesceByteStream = (stream, minChunkSize) => {
52
+ if (!Number.isInteger(minChunkSize) || minChunkSize < 1) {
53
+ throw new TypeError('minChunkSize must be a positive integer');
54
+ }
55
+ // @NOTE On Node 22, this *does* return a PassThrough ("@types/node"
56
+ // incorrectly types it as Writable).
57
+ return pipeline(stream, coalesce, (_err) => {
58
+ // Errors are expected to be handled through the stream
59
+ });
60
+ // @NOTE This implementation is not NodeJS specific and could be exported as
61
+ // utility (from "@atproto/common-web") if needed. We don't do it now to avoid
62
+ // increasing the API surface of our packages.
63
+ async function* coalesce(iter) {
64
+ // @NOTE This implementation avoids as many un-necessary copies as possible
65
+ // and only buffers incoming chunks when they are smaller than the
66
+ // coalescing buffer.
67
+ let buffer = new Uint8Array(minChunkSize);
68
+ let offset = 0;
69
+ for await (const chunk of iter) {
70
+ const freeSpace = buffer.length - offset;
71
+ if (freeSpace > chunk.length) {
72
+ // If the incoming chunk is smaller than the free space, we copy the
73
+ // entire chunk into the coalescing buffer and continue to the next
74
+ // chunk
75
+ buffer.set(chunk, offset);
76
+ offset += chunk.length;
77
+ }
78
+ else if (offset === 0 && chunk.length >= minChunkSize) {
79
+ // If the coalescing buffer is empty and the incoming chunk is larger
80
+ // than the coalescing buffer, we can skip any copying and yield the
81
+ // incoming chunk directly
82
+ yield chunk;
83
+ }
84
+ else {
85
+ // Otherwise, we need to copy as much of the incoming chunk as we can
86
+ // into the coalescing buffer and yield the full coalescing buffer.
87
+ buffer.set(chunk.subarray(0, freeSpace), offset);
88
+ yield buffer;
89
+ // We create a new coalescing buffer (for future use)
90
+ buffer = new Uint8Array(minChunkSize);
91
+ offset = 0;
92
+ const remainingBytes = chunk.subarray(freeSpace);
93
+ if (remainingBytes.length > minChunkSize) {
94
+ // If the remaining of chunk is still too big to fit in the
95
+ // coalescing buffer, we yield it directly without copying it
96
+ yield remainingBytes;
97
+ }
98
+ else if (remainingBytes.length > 0) {
99
+ // Otherwise, we copy the remaining bytes into the coalescing buffer
100
+ // and continue to the next chunk
101
+ buffer.set(remainingBytes, offset);
102
+ offset += remainingBytes.length;
103
+ }
104
+ }
105
+ }
106
+ // Yield any remaining bytes in the coalescing buffer
107
+ if (offset > 0) {
108
+ yield buffer.subarray(0, offset);
109
+ }
110
+ }
111
+ };
112
+ export const bytesToStream = (bytes) => {
113
+ const stream = new Readable();
114
+ stream.push(bytes);
115
+ stream.push(null);
116
+ return stream;
117
+ };
118
+ export class MaxSizeChecker extends Transform {
119
+ constructor(maxSize, createError) {
120
+ super();
121
+ this.maxSize = maxSize;
122
+ this.createError = createError;
123
+ this.totalSize = 0;
124
+ }
125
+ _transform(chunk, _enc, cb) {
126
+ this.totalSize += chunk.length;
127
+ if (this.totalSize > this.maxSize) {
128
+ cb(this.createError());
129
+ }
130
+ else {
131
+ cb(null, chunk);
132
+ }
133
+ }
134
+ }
135
+ export function decodeStream(stream, contentEncoding) {
136
+ const decoders = createDecoders(contentEncoding);
137
+ if (decoders.length === 0)
138
+ return stream;
139
+ return pipeline([stream, ...decoders], () => { });
140
+ }
141
+ /**
142
+ * Create a series of decoding streams based on the content-encoding header. The
143
+ * resulting streams should be piped together to decode the content.
144
+ *
145
+ * @see {@link https://datatracker.ietf.org/doc/html/rfc9110#section-8.4.1}
146
+ */
147
+ export function createDecoders(contentEncoding) {
148
+ const decoders = [];
149
+ if (contentEncoding?.length) {
150
+ const encodings = Array.isArray(contentEncoding)
151
+ ? contentEncoding.flatMap(commaSplit)
152
+ : contentEncoding.split(',');
153
+ for (const encoding of encodings) {
154
+ const normalizedEncoding = normalizeEncoding(encoding);
155
+ // @NOTE
156
+ // > The default (identity) encoding [...] is used only in the
157
+ // > Accept-Encoding header, and SHOULD NOT be used in the
158
+ // > Content-Encoding header.
159
+ if (normalizedEncoding === 'identity')
160
+ continue;
161
+ decoders.push(createDecoder(normalizedEncoding));
162
+ }
163
+ }
164
+ return decoders.reverse();
165
+ }
166
+ function commaSplit(header) {
167
+ return header.split(',');
168
+ }
169
+ function normalizeEncoding(encoding) {
170
+ // https://www.rfc-editor.org/rfc/rfc7231#section-3.1.2.1
171
+ // > All content-coding values are case-insensitive...
172
+ return encoding.trim().toLowerCase();
173
+ }
174
+ function createDecoder(normalizedEncoding) {
175
+ switch (normalizedEncoding) {
176
+ // https://www.rfc-editor.org/rfc/rfc9112.html#section-7.2
177
+ case 'gzip':
178
+ case 'x-gzip':
179
+ return createGunzip();
180
+ case 'deflate':
181
+ return createInflate();
182
+ case 'br':
183
+ return createBrotliDecompress();
184
+ case 'identity':
185
+ return new PassThrough();
186
+ default:
187
+ throw new TypeError(`Unsupported content-encoding: "${normalizedEncoding}"`);
188
+ }
189
+ }
190
+ //# sourceMappingURL=streams.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streams.js","sourceRoot":"","sources":["../src/streams.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type Duplex,\n PassThrough,\n Readable,\n type Stream,\n Transform,\n type TransformCallback,\n pipeline,\n} from 'node:stream'\nimport { createBrotliDecompress, createGunzip, createInflate } from 'node:zlib'\n\nexport const forwardStreamErrors = (...streams: Stream[]) => {\n for (let i = 1; i < streams.length; ++i) {\n const prev = streams[i - 1]\n const next = streams[i]\n\n prev.once('error', (err) => next.emit('error', err))\n }\n}\n\nexport const cloneStream = (stream: Readable): Readable => {\n const passthrough = new PassThrough()\n forwardStreamErrors(stream, passthrough)\n return stream.pipe(passthrough)\n}\n\nexport const streamSize = async (stream: Readable): Promise<number> => {\n let size = 0\n for await (const chunk of stream) {\n size += Buffer.byteLength(chunk)\n }\n return size\n}\n\nexport const streamToBytes = async (stream: AsyncIterable<Uint8Array>) =>\n // @NOTE Though Buffer is a sub-class of Uint8Array, we have observed\n // inconsistencies when using a Buffer in place of Uint8Array. For this\n // reason, we convert the Buffer to a Uint8Array.\n new Uint8Array(await streamToNodeBuffer(stream))\n\n// streamToBuffer identifier name already taken by @atproto/common-web\nexport const streamToNodeBuffer = async (\n stream: Iterable<Uint8Array> | AsyncIterable<Uint8Array>,\n): Promise<Buffer> => {\n const chunks: Uint8Array[] = []\n let totalLength = 0 // keep track of total length for Buffer.concat\n for await (const chunk of stream) {\n if (chunk instanceof Uint8Array) {\n chunks.push(chunk)\n totalLength += Buffer.byteLength(chunk)\n } else {\n throw new TypeError('expected Uint8Array')\n }\n }\n return Buffer.concat(chunks, totalLength)\n}\n\nexport const byteIterableToStream = (\n iter: AsyncIterable<Uint8Array>,\n): Readable => {\n return Readable.from(iter, { objectMode: false })\n}\n\n/**\n * Coalesce a stream of Uint8Array chunks into larger chunks of at least the\n * specified size ({@link minChunkSize}). This is useful for optimizing\n * downstream processing that benefits from larger chunk sizes, such as\n * compression or hashing.\n */\nexport const coalesceByteStream = (\n stream: Iterable<Uint8Array> | AsyncIterable<Uint8Array>,\n minChunkSize: number,\n): Readable => {\n if (!Number.isInteger(minChunkSize) || minChunkSize < 1) {\n throw new TypeError('minChunkSize must be a positive integer')\n }\n\n // @NOTE On Node 22, this *does* return a PassThrough (\"@types/node\"\n // incorrectly types it as Writable).\n return pipeline(stream, coalesce, (_err) => {\n // Errors are expected to be handled through the stream\n }) as PassThrough\n\n // @NOTE This implementation is not NodeJS specific and could be exported as\n // utility (from \"@atproto/common-web\") if needed. We don't do it now to avoid\n // increasing the API surface of our packages.\n async function* coalesce(\n iter: Iterable<Uint8Array> | AsyncIterable<Uint8Array>,\n ): AsyncGenerator<Uint8Array> {\n // @NOTE This implementation avoids as many un-necessary copies as possible\n // and only buffers incoming chunks when they are smaller than the\n // coalescing buffer.\n\n let buffer = new Uint8Array(minChunkSize)\n let offset = 0\n\n for await (const chunk of iter) {\n const freeSpace = buffer.length - offset\n if (freeSpace > chunk.length) {\n // If the incoming chunk is smaller than the free space, we copy the\n // entire chunk into the coalescing buffer and continue to the next\n // chunk\n buffer.set(chunk, offset)\n offset += chunk.length\n } else if (offset === 0 && chunk.length >= minChunkSize) {\n // If the coalescing buffer is empty and the incoming chunk is larger\n // than the coalescing buffer, we can skip any copying and yield the\n // incoming chunk directly\n yield chunk\n } else {\n // Otherwise, we need to copy as much of the incoming chunk as we can\n // into the coalescing buffer and yield the full coalescing buffer.\n buffer.set(chunk.subarray(0, freeSpace), offset)\n yield buffer\n\n // We create a new coalescing buffer (for future use)\n buffer = new Uint8Array(minChunkSize)\n offset = 0\n\n const remainingBytes = chunk.subarray(freeSpace)\n if (remainingBytes.length > minChunkSize) {\n // If the remaining of chunk is still too big to fit in the\n // coalescing buffer, we yield it directly without copying it\n yield remainingBytes\n } else if (remainingBytes.length > 0) {\n // Otherwise, we copy the remaining bytes into the coalescing buffer\n // and continue to the next chunk\n buffer.set(remainingBytes, offset)\n offset += remainingBytes.length\n }\n }\n }\n\n // Yield any remaining bytes in the coalescing buffer\n if (offset > 0) {\n yield buffer.subarray(0, offset)\n }\n }\n}\n\nexport const bytesToStream = (bytes: Uint8Array): Readable => {\n const stream = new Readable()\n stream.push(bytes)\n stream.push(null)\n return stream\n}\n\nexport class MaxSizeChecker extends Transform {\n totalSize = 0\n constructor(\n public maxSize: number,\n public createError: () => Error,\n ) {\n super()\n }\n _transform(chunk: Uint8Array, _enc: BufferEncoding, cb: TransformCallback) {\n this.totalSize += chunk.length\n if (this.totalSize > this.maxSize) {\n cb(this.createError())\n } else {\n cb(null, chunk)\n }\n }\n}\n\nexport function decodeStream(\n stream: Readable,\n contentEncoding?: string | string[],\n): Readable\nexport function decodeStream(\n stream: AsyncIterable<Uint8Array>,\n contentEncoding?: string | string[],\n): AsyncIterable<Uint8Array> | Readable\nexport function decodeStream(\n stream: Readable | AsyncIterable<Uint8Array>,\n contentEncoding?: string | string[],\n): Readable | AsyncIterable<Uint8Array> {\n const decoders = createDecoders(contentEncoding)\n if (decoders.length === 0) return stream\n return pipeline([stream as Readable, ...decoders], () => {}) as Duplex\n}\n\n/**\n * Create a series of decoding streams based on the content-encoding header. The\n * resulting streams should be piped together to decode the content.\n *\n * @see {@link https://datatracker.ietf.org/doc/html/rfc9110#section-8.4.1}\n */\nexport function createDecoders(contentEncoding?: string | string[]): Duplex[] {\n const decoders: Duplex[] = []\n\n if (contentEncoding?.length) {\n const encodings: string[] = Array.isArray(contentEncoding)\n ? contentEncoding.flatMap(commaSplit)\n : contentEncoding.split(',')\n for (const encoding of encodings) {\n const normalizedEncoding = normalizeEncoding(encoding)\n\n // @NOTE\n // > The default (identity) encoding [...] is used only in the\n // > Accept-Encoding header, and SHOULD NOT be used in the\n // > Content-Encoding header.\n if (normalizedEncoding === 'identity') continue\n\n decoders.push(createDecoder(normalizedEncoding))\n }\n }\n\n return decoders.reverse()\n}\n\nfunction commaSplit(header: string): string[] {\n return header.split(',')\n}\n\nfunction normalizeEncoding(encoding: string) {\n // https://www.rfc-editor.org/rfc/rfc7231#section-3.1.2.1\n // > All content-coding values are case-insensitive...\n return encoding.trim().toLowerCase()\n}\n\nfunction createDecoder(normalizedEncoding: string): Duplex {\n switch (normalizedEncoding) {\n // https://www.rfc-editor.org/rfc/rfc9112.html#section-7.2\n case 'gzip':\n case 'x-gzip':\n return createGunzip()\n case 'deflate':\n return createInflate()\n case 'br':\n return createBrotliDecompress()\n case 'identity':\n return new PassThrough()\n default:\n throw new TypeError(\n `Unsupported content-encoding: \"${normalizedEncoding}\"`,\n )\n }\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@atproto/common",
3
+ "version": "0.0.0-spaces-alpha-20260818021221",
4
+ "license": "MIT",
5
+ "description": "Shared web-platform-friendly code for atproto libraries",
6
+ "keywords": [
7
+ "atproto"
8
+ ],
9
+ "homepage": "https://atproto.com",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/bluesky-social/atproto",
13
+ "directory": "packages/common"
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
+ },
27
+ "engines": {
28
+ "node": ">=22"
29
+ },
30
+ "dependencies": {
31
+ "multiformats": "^13.0.0",
32
+ "pino": "^10.3.1",
33
+ "varint": "^6.0.0",
34
+ "@atproto/common-web": "^0.0.0-spaces-alpha-20260818021221",
35
+ "@atproto/lex-data": "^0.1.7",
36
+ "@atproto/lex-cbor": "^0.1.6"
37
+ },
38
+ "devDependencies": {
39
+ "jest": "^30.0.0",
40
+ "uint8arrays": "^5.0.0"
41
+ },
42
+ "scripts": {
43
+ "test": "NODE_OPTIONS=--experimental-vm-modules jest",
44
+ "build": "tsc --build tsconfig.build.json"
45
+ }
46
+ }