@learncard/helpers 1.6.0 → 1.6.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.
- package/dist/helpers.cjs.development.cjs +5165 -4388
- package/dist/helpers.cjs.development.cjs.map +3 -3
- package/dist/helpers.cjs.production.min.cjs +43 -43
- package/dist/helpers.cjs.production.min.cjs.map +4 -4
- package/dist/helpers.esm.js +620 -0
- package/dist/helpers.esm.js.map +3 -3
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/share-content-canonical.d.ts +13 -0
- package/dist/share-content-canonical.d.ts.map +1 -0
- package/package.json +2 -2
- package/src/index.ts +2 -0
- package/src/share-content-canonical.ts +128 -0
package/dist/index.d.cts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAgB,UAAU,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD;;;;GAIG;AACH,eAAO,MAAM,KAAK,GAAI,KAAK,MAAM,YAA6B,CAAC;AAE/D,8DAA8D;AAC9D,eAAO,MAAM,WAAW,GAAI,MAAM,OAAO,KAAG,IAAI,IAAI,GAEnD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,iBAAiB,EAAE,eA8B/B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,WAAW,GAAI,YAAY,OAAO,KAAG,OAQjD,CAAC;AAEF,+EAA+E;AAC/E,eAAO,MAAM,qBAAqB,GAAI,KAAK,EAAE,GAAG,UAAU,QAMzD,CAAC;AAGF,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,yBAAyB,CAAC;AAGxC,cAAc,aAAa,CAAC;AAG5B,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,OAAO,CAAC;AACtB,cAAc,eAAe,CAAC;AAG9B,cAAc,qBAAqB,CAAC;AAEpC;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,GAAI,MAAM,MAAM,KAAG,OAK1C,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAgB,UAAU,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD;;;;GAIG;AACH,eAAO,MAAM,KAAK,GAAI,KAAK,MAAM,YAA6B,CAAC;AAE/D,8DAA8D;AAC9D,eAAO,MAAM,WAAW,GAAI,MAAM,OAAO,KAAG,IAAI,IAAI,GAEnD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,iBAAiB,EAAE,eA8B/B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,WAAW,GAAI,YAAY,OAAO,KAAG,OAQjD,CAAC;AAEF,+EAA+E;AAC/E,eAAO,MAAM,qBAAqB,GAAI,KAAK,EAAE,GAAG,UAAU,QAMzD,CAAC;AAGF,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,yBAAyB,CAAC;AAGxC,cAAc,aAAa,CAAC;AAG5B,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,OAAO,CAAC;AACtB,cAAc,eAAe,CAAC;AAG9B,cAAc,qBAAqB,CAAC;AAEpC;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,GAAI,MAAM,MAAM,KAAG,OAK1C,CAAC;AAEF,cAAc,2BAA2B,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** C1 request serialization shared by the signing client and verifying service. */
|
|
2
|
+
export declare const DEFAULT_MAX_CANONICAL_BYTES: number;
|
|
3
|
+
export declare const DEFAULT_MAX_CANONICAL_DEPTH = 32;
|
|
4
|
+
export declare class ShareContentCanonicalizationError extends Error {
|
|
5
|
+
readonly code = "UNSUPPORTED_REQUEST_VALUE";
|
|
6
|
+
constructor(message: string);
|
|
7
|
+
}
|
|
8
|
+
/** Deterministic UTF-8 canonical serialization of a request body. */
|
|
9
|
+
export declare const canonicalizeShareContentRequestBody: (body: unknown, options?: {
|
|
10
|
+
maxBytes?: number;
|
|
11
|
+
maxDepth?: number;
|
|
12
|
+
}) => string;
|
|
13
|
+
//# sourceMappingURL=share-content-canonical.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"share-content-canonical.d.ts","sourceRoot":"","sources":["../src/share-content-canonical.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,eAAO,MAAM,2BAA2B,QAAkB,CAAC;AAC3D,eAAO,MAAM,2BAA2B,KAAK,CAAC;AAI9C,qBAAa,iCAAkC,SAAQ,KAAK;IACxD,QAAQ,CAAC,IAAI,+BAA+B;gBAEhC,OAAO,EAAE,MAAM;CAI9B;AAoGD,qEAAqE;AACrE,eAAO,MAAM,mCAAmC,GAC5C,MAAM,OAAO,EACb,UAAS;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,KACvD,MAUF,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@learncard/helpers",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"description": "Shared helpers for LearnCard packages",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"@noble/hashes": "^1.8.0",
|
|
56
56
|
"@sd-jwt/decode": "^0.19.0",
|
|
57
|
-
"@learncard/types": "5.
|
|
57
|
+
"@learncard/types": "5.22.0",
|
|
58
58
|
"@trpc/server": "11.8.0",
|
|
59
59
|
"immer": "^10.0.3",
|
|
60
60
|
"use-immer": "^0.9.0",
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/** C1 request serialization shared by the signing client and verifying service. */
|
|
2
|
+
export const DEFAULT_MAX_CANONICAL_BYTES = 2 * 1024 * 1024;
|
|
3
|
+
export const DEFAULT_MAX_CANONICAL_DEPTH = 32;
|
|
4
|
+
|
|
5
|
+
const LONE_SURROGATE_RE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
|
|
6
|
+
|
|
7
|
+
export class ShareContentCanonicalizationError extends Error {
|
|
8
|
+
readonly code = 'UNSUPPORTED_REQUEST_VALUE';
|
|
9
|
+
|
|
10
|
+
constructor(message: string) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = 'ShareContentCanonicalizationError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const serializeCanonical = (
|
|
17
|
+
value: unknown,
|
|
18
|
+
seen: Set<object>,
|
|
19
|
+
depth: number,
|
|
20
|
+
maxDepth: number
|
|
21
|
+
): string => {
|
|
22
|
+
if (value === null) return 'null';
|
|
23
|
+
|
|
24
|
+
switch (typeof value) {
|
|
25
|
+
case 'boolean':
|
|
26
|
+
return value ? 'true' : 'false';
|
|
27
|
+
|
|
28
|
+
case 'string': {
|
|
29
|
+
if (LONE_SURROGATE_RE.test(value)) {
|
|
30
|
+
throw new ShareContentCanonicalizationError(
|
|
31
|
+
'string contains an unpaired surrogate'
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return JSON.stringify(value);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
case 'number': {
|
|
39
|
+
if (!Number.isSafeInteger(value) || Object.is(value, -0)) {
|
|
40
|
+
throw new ShareContentCanonicalizationError(
|
|
41
|
+
'numbers must be safe integers without a negative-zero sign'
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return String(value);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
case 'object': {
|
|
49
|
+
if (depth >= maxDepth) {
|
|
50
|
+
throw new ShareContentCanonicalizationError(
|
|
51
|
+
'value exceeds the maximum nesting depth'
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (seen.has(value)) {
|
|
56
|
+
throw new ShareContentCanonicalizationError('cyclic value is not supported');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
seen.add(value);
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
if (Array.isArray(value)) {
|
|
63
|
+
const items: string[] = [];
|
|
64
|
+
|
|
65
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
66
|
+
if (!(index in value)) {
|
|
67
|
+
throw new ShareContentCanonicalizationError(
|
|
68
|
+
'sparse arrays are not supported'
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
items.push(serializeCanonical(value[index], seen, depth + 1, maxDepth));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return `[${items.join(',')}]`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const prototype = Object.getPrototypeOf(value);
|
|
79
|
+
|
|
80
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
81
|
+
throw new ShareContentCanonicalizationError('only plain objects are supported');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const record = value as Record<string, unknown>;
|
|
85
|
+
|
|
86
|
+
const entries = Object.keys(record)
|
|
87
|
+
.sort()
|
|
88
|
+
.map(key => {
|
|
89
|
+
if (LONE_SURROGATE_RE.test(key)) {
|
|
90
|
+
throw new ShareContentCanonicalizationError(
|
|
91
|
+
'object key contains an unpaired surrogate'
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return `${JSON.stringify(key)}:${serializeCanonical(
|
|
96
|
+
record[key],
|
|
97
|
+
seen,
|
|
98
|
+
depth + 1,
|
|
99
|
+
maxDepth
|
|
100
|
+
)}`;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return `{${entries.join(',')}}`;
|
|
104
|
+
} finally {
|
|
105
|
+
seen.delete(value);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
default:
|
|
110
|
+
throw new ShareContentCanonicalizationError(`unsupported value type: ${typeof value}`);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/** Deterministic UTF-8 canonical serialization of a request body. */
|
|
115
|
+
export const canonicalizeShareContentRequestBody = (
|
|
116
|
+
body: unknown,
|
|
117
|
+
options: { maxBytes?: number; maxDepth?: number } = {}
|
|
118
|
+
): string => {
|
|
119
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MAX_CANONICAL_BYTES;
|
|
120
|
+
const maxDepth = options.maxDepth ?? DEFAULT_MAX_CANONICAL_DEPTH;
|
|
121
|
+
const serialized = serializeCanonical(body, new Set(), 0, maxDepth);
|
|
122
|
+
|
|
123
|
+
if (new TextEncoder().encode(serialized).byteLength > maxBytes) {
|
|
124
|
+
throw new ShareContentCanonicalizationError('canonical request exceeds the maximum size');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return serialized;
|
|
128
|
+
};
|