@variantlab/react-native 0.1.0
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/LICENSE +21 -0
- package/README.md +23 -0
- package/dist/debug.cjs +940 -0
- package/dist/debug.cjs.map +1 -0
- package/dist/debug.d.cts +206 -0
- package/dist/debug.d.ts +206 -0
- package/dist/debug.js +926 -0
- package/dist/debug.js.map +1 -0
- package/dist/index.cjs +548 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +322 -0
- package/dist/index.d.ts +322 -0
- package/dist/index.js +487 -0
- package/dist/index.js.map +1 -0
- package/dist/qr.cjs +252 -0
- package/dist/qr.cjs.map +1 -0
- package/dist/qr.d.cts +55 -0
- package/dist/qr.d.ts +55 -0
- package/dist/qr.js +247 -0
- package/dist/qr.js.map +1 -0
- package/dist/types-CnSyez2D.d.cts +36 -0
- package/dist/types-CnSyez2D.d.ts +36 -0
- package/package.json +124 -0
package/dist/qr.cjs
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/deep-link/validate.ts
|
|
4
|
+
var ID_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
5
|
+
var POLLUTION_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
6
|
+
var MAX_OVERRIDES = 100;
|
|
7
|
+
var MAX_PAYLOAD_BYTES = 1024;
|
|
8
|
+
function validatePayload(input, now = Date.now()) {
|
|
9
|
+
if (input === null || typeof input !== "object" || Array.isArray(input)) {
|
|
10
|
+
return fail("not-an-object");
|
|
11
|
+
}
|
|
12
|
+
if (hasPollution(input)) return fail("prototype-pollution");
|
|
13
|
+
const obj = input;
|
|
14
|
+
if (obj.v !== 1) return fail("bad-version");
|
|
15
|
+
const overrides = obj.overrides;
|
|
16
|
+
if (overrides === null || typeof overrides !== "object" || Array.isArray(overrides)) {
|
|
17
|
+
return fail("missing-overrides");
|
|
18
|
+
}
|
|
19
|
+
const overrideEntries = Object.entries(overrides);
|
|
20
|
+
if (overrideEntries.length > MAX_OVERRIDES) return fail("overrides-too-large");
|
|
21
|
+
const safeOverrides = /* @__PURE__ */ Object.create(null);
|
|
22
|
+
for (const [key, value] of overrideEntries) {
|
|
23
|
+
if (POLLUTION_KEYS.has(key)) return fail("prototype-pollution");
|
|
24
|
+
if (!ID_RE.test(key)) return fail("bad-override-key");
|
|
25
|
+
if (typeof value !== "string") return fail("bad-override-value");
|
|
26
|
+
if (!ID_RE.test(value)) return fail("bad-override-value");
|
|
27
|
+
safeOverrides[key] = value;
|
|
28
|
+
}
|
|
29
|
+
let context;
|
|
30
|
+
if (obj.context !== void 0) {
|
|
31
|
+
if (obj.context === null || typeof obj.context !== "object" || Array.isArray(obj.context)) {
|
|
32
|
+
return fail("bad-context");
|
|
33
|
+
}
|
|
34
|
+
const sanitized = sanitizeContext(obj.context);
|
|
35
|
+
if (sanitized === null) return fail("bad-context");
|
|
36
|
+
context = sanitized;
|
|
37
|
+
}
|
|
38
|
+
let expires;
|
|
39
|
+
if (obj.expires !== void 0) {
|
|
40
|
+
if (typeof obj.expires !== "number" || !Number.isFinite(obj.expires)) {
|
|
41
|
+
return fail("bad-context");
|
|
42
|
+
}
|
|
43
|
+
if (obj.expires < now) return fail("expired");
|
|
44
|
+
expires = obj.expires;
|
|
45
|
+
}
|
|
46
|
+
const reStringified = JSON.stringify({
|
|
47
|
+
v: 1,
|
|
48
|
+
overrides: safeOverrides,
|
|
49
|
+
...context !== void 0 ? { context } : {},
|
|
50
|
+
...expires !== void 0 ? { expires } : {}
|
|
51
|
+
});
|
|
52
|
+
if (reStringified.length > MAX_PAYLOAD_BYTES) return fail("payload-too-large");
|
|
53
|
+
const payload = {
|
|
54
|
+
v: 1,
|
|
55
|
+
overrides: safeOverrides,
|
|
56
|
+
...context !== void 0 ? { context } : {},
|
|
57
|
+
...expires !== void 0 ? { expires } : {}
|
|
58
|
+
};
|
|
59
|
+
return { ok: true, payload };
|
|
60
|
+
}
|
|
61
|
+
function hasPollution(input) {
|
|
62
|
+
if (input === null || typeof input !== "object") return false;
|
|
63
|
+
if (Array.isArray(input)) {
|
|
64
|
+
for (const item of input) if (hasPollution(item)) return true;
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
for (const key of Object.keys(input)) {
|
|
68
|
+
if (POLLUTION_KEYS.has(key)) return true;
|
|
69
|
+
if (hasPollution(input[key])) return true;
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
function sanitizeContext(input) {
|
|
74
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
75
|
+
for (const key of Object.keys(input)) {
|
|
76
|
+
if (POLLUTION_KEYS.has(key)) return null;
|
|
77
|
+
const value = input[key];
|
|
78
|
+
if (value === void 0) continue;
|
|
79
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
80
|
+
out[key] = value;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (key === "attributes" && typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
84
|
+
const attrs = /* @__PURE__ */ Object.create(null);
|
|
85
|
+
for (const aKey of Object.keys(value)) {
|
|
86
|
+
if (POLLUTION_KEYS.has(aKey)) return null;
|
|
87
|
+
const aVal = value[aKey];
|
|
88
|
+
if (typeof aVal === "string" || typeof aVal === "number" || typeof aVal === "boolean") {
|
|
89
|
+
attrs[aKey] = aVal;
|
|
90
|
+
} else {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
out[key] = attrs;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
function fail(reason) {
|
|
102
|
+
return { ok: false, reason };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// src/deep-link/encode.ts
|
|
106
|
+
var PREFIX_RAW = 0;
|
|
107
|
+
function encodeSharePayload(payload) {
|
|
108
|
+
const validated = validatePayload(payload);
|
|
109
|
+
if (!validated.ok) {
|
|
110
|
+
throw new Error(`Cannot encode invalid share payload: ${validated.reason}`);
|
|
111
|
+
}
|
|
112
|
+
const json = JSON.stringify(validated.payload);
|
|
113
|
+
const utf8 = new TextEncoder().encode(json);
|
|
114
|
+
const framed = new Uint8Array(utf8.length + 1);
|
|
115
|
+
framed[0] = PREFIX_RAW;
|
|
116
|
+
framed.set(utf8, 1);
|
|
117
|
+
return bytesToBase64Url(framed);
|
|
118
|
+
}
|
|
119
|
+
function decodeSharePayload(encoded, now) {
|
|
120
|
+
let bytes;
|
|
121
|
+
try {
|
|
122
|
+
bytes = base64UrlToBytes(encoded);
|
|
123
|
+
} catch {
|
|
124
|
+
return { ok: false, reason: "not-an-object" };
|
|
125
|
+
}
|
|
126
|
+
if (bytes.length === 0) return { ok: false, reason: "not-an-object" };
|
|
127
|
+
let jsonBytes;
|
|
128
|
+
if (bytes[0] === PREFIX_RAW) {
|
|
129
|
+
jsonBytes = bytes.subarray(1);
|
|
130
|
+
} else {
|
|
131
|
+
jsonBytes = bytes;
|
|
132
|
+
}
|
|
133
|
+
let parsed;
|
|
134
|
+
try {
|
|
135
|
+
const json = new TextDecoder("utf-8", { fatal: true }).decode(jsonBytes);
|
|
136
|
+
parsed = JSON.parse(json);
|
|
137
|
+
} catch {
|
|
138
|
+
return { ok: false, reason: "not-an-object" };
|
|
139
|
+
}
|
|
140
|
+
return validatePayload(parsed, now);
|
|
141
|
+
}
|
|
142
|
+
var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
143
|
+
var DECODE_TABLE = (() => {
|
|
144
|
+
const table = new Int8Array(128);
|
|
145
|
+
table.fill(-1);
|
|
146
|
+
for (let i = 0; i < ALPHABET.length; i++) {
|
|
147
|
+
table[ALPHABET.charCodeAt(i)] = i;
|
|
148
|
+
}
|
|
149
|
+
table["+".charCodeAt(0)] = 62;
|
|
150
|
+
table["/".charCodeAt(0)] = 63;
|
|
151
|
+
return table;
|
|
152
|
+
})();
|
|
153
|
+
function bytesToBase64Url(bytes) {
|
|
154
|
+
let out = "";
|
|
155
|
+
let i = 0;
|
|
156
|
+
for (; i + 3 <= bytes.length; i += 3) {
|
|
157
|
+
const b0 = bytes[i];
|
|
158
|
+
const b1 = bytes[i + 1];
|
|
159
|
+
const b2 = bytes[i + 2];
|
|
160
|
+
out += ALPHABET[b0 >> 2];
|
|
161
|
+
out += ALPHABET[(b0 & 3) << 4 | b1 >> 4];
|
|
162
|
+
out += ALPHABET[(b1 & 15) << 2 | b2 >> 6];
|
|
163
|
+
out += ALPHABET[b2 & 63];
|
|
164
|
+
}
|
|
165
|
+
if (i < bytes.length) {
|
|
166
|
+
const b0 = bytes[i];
|
|
167
|
+
out += ALPHABET[b0 >> 2];
|
|
168
|
+
if (i + 1 < bytes.length) {
|
|
169
|
+
const b1 = bytes[i + 1];
|
|
170
|
+
out += ALPHABET[(b0 & 3) << 4 | b1 >> 4];
|
|
171
|
+
out += ALPHABET[(b1 & 15) << 2];
|
|
172
|
+
} else {
|
|
173
|
+
out += ALPHABET[(b0 & 3) << 4];
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return out;
|
|
177
|
+
}
|
|
178
|
+
function base64UrlToBytes(input) {
|
|
179
|
+
let s = input;
|
|
180
|
+
while (s.length > 0 && s[s.length - 1] === "=") s = s.slice(0, -1);
|
|
181
|
+
if (s.length === 0) return new Uint8Array(0);
|
|
182
|
+
const remainder = s.length & 3;
|
|
183
|
+
if (remainder === 1) throw new Error("Invalid base64url length");
|
|
184
|
+
const fullGroups = s.length - remainder >> 2;
|
|
185
|
+
const outLen = fullGroups * 3 + (remainder === 0 ? 0 : remainder - 1);
|
|
186
|
+
const out = new Uint8Array(outLen);
|
|
187
|
+
let outIdx = 0;
|
|
188
|
+
let i = 0;
|
|
189
|
+
for (let g = 0; g < fullGroups; g++, i += 4) {
|
|
190
|
+
const c0 = decodeChar(s.charCodeAt(i));
|
|
191
|
+
const c1 = decodeChar(s.charCodeAt(i + 1));
|
|
192
|
+
const c2 = decodeChar(s.charCodeAt(i + 2));
|
|
193
|
+
const c3 = decodeChar(s.charCodeAt(i + 3));
|
|
194
|
+
out[outIdx++] = c0 << 2 | c1 >> 4;
|
|
195
|
+
out[outIdx++] = (c1 & 15) << 4 | c2 >> 2;
|
|
196
|
+
out[outIdx++] = (c2 & 3) << 6 | c3;
|
|
197
|
+
}
|
|
198
|
+
if (remainder >= 2) {
|
|
199
|
+
const c0 = decodeChar(s.charCodeAt(i));
|
|
200
|
+
const c1 = decodeChar(s.charCodeAt(i + 1));
|
|
201
|
+
out[outIdx++] = c0 << 2 | c1 >> 4;
|
|
202
|
+
if (remainder === 3) {
|
|
203
|
+
const c2 = decodeChar(s.charCodeAt(i + 2));
|
|
204
|
+
out[outIdx++] = (c1 & 15) << 4 | c2 >> 2;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return out;
|
|
208
|
+
}
|
|
209
|
+
function decodeChar(code) {
|
|
210
|
+
if (code >= 128) throw new Error("Invalid base64url character");
|
|
211
|
+
const v = DECODE_TABLE[code] ?? -1;
|
|
212
|
+
if (v < 0) throw new Error("Invalid base64url character");
|
|
213
|
+
return v;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// src/qr.ts
|
|
217
|
+
var VARIANTLAB_URL_SCHEME = "variantlab";
|
|
218
|
+
var VARIANTLAB_APPLY_PATH = "apply";
|
|
219
|
+
function buildQrUrl(payload) {
|
|
220
|
+
const encoded = encodeSharePayload(payload);
|
|
221
|
+
return `${VARIANTLAB_URL_SCHEME}://${VARIANTLAB_APPLY_PATH}?d=${encoded}`;
|
|
222
|
+
}
|
|
223
|
+
function parseQrUrl(url, now) {
|
|
224
|
+
const q = extractDataParam(url);
|
|
225
|
+
if (q === null) return { ok: false, reason: "not-an-object" };
|
|
226
|
+
return decodeSharePayload(q, now);
|
|
227
|
+
}
|
|
228
|
+
function extractDataParam(url) {
|
|
229
|
+
const q = url.indexOf("?");
|
|
230
|
+
if (q < 0) return null;
|
|
231
|
+
const hashEnd = url.indexOf("#", q);
|
|
232
|
+
const query = hashEnd < 0 ? url.slice(q + 1) : url.slice(q + 1, hashEnd);
|
|
233
|
+
for (const pair of query.split("&")) {
|
|
234
|
+
const eq = pair.indexOf("=");
|
|
235
|
+
if (eq < 0) continue;
|
|
236
|
+
const key = pair.slice(0, eq);
|
|
237
|
+
if (key !== "d") continue;
|
|
238
|
+
try {
|
|
239
|
+
return decodeURIComponent(pair.slice(eq + 1));
|
|
240
|
+
} catch {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
exports.VARIANTLAB_APPLY_PATH = VARIANTLAB_APPLY_PATH;
|
|
248
|
+
exports.VARIANTLAB_URL_SCHEME = VARIANTLAB_URL_SCHEME;
|
|
249
|
+
exports.buildQrUrl = buildQrUrl;
|
|
250
|
+
exports.parseQrUrl = parseQrUrl;
|
|
251
|
+
//# sourceMappingURL=qr.cjs.map
|
|
252
|
+
//# sourceMappingURL=qr.cjs.map
|
package/dist/qr.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/deep-link/validate.ts","../src/deep-link/encode.ts","../src/qr.ts"],"names":[],"mappings":"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","file":"qr.cjs","sourcesContent":["/**\n * Validate a candidate share payload before applying it.\n *\n * Share payloads come from untrusted input — a deep link, a QR code,\n * a clipboard paste — so we treat them as adversarial. The validator\n * walks the structure with `Object.create(null)` semantics in mind:\n *\n * - Reject prototype-pollution keys (`__proto__`, `constructor`,\n * `prototype`) anywhere in the tree.\n * - Enforce strict id regexes on every override key/value so a\n * malformed payload cannot smuggle non-printable bytes into a\n * `setVariant` call.\n * - Cap the override count and total decoded size to bound memory.\n * - Honor an optional `expires` field so QR codes shared earlier\n * in the day cannot be replayed forever.\n *\n * The validator returns a `ValidationResult` discriminated union\n * instead of throwing because callers (deep-link handler, QR scanner)\n * almost always want to silently surface a failure to the UI rather\n * than treat it as a runtime error.\n */\nimport type { SharePayload, ValidationFailure, ValidationResult } from \"./types.js\";\n\n/** Match the same id regex as `experiments.json`. */\nconst ID_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;\nconst POLLUTION_KEYS = new Set([\"__proto__\", \"constructor\", \"prototype\"]);\n\nconst MAX_OVERRIDES = 100;\nconst MAX_PAYLOAD_BYTES = 1024;\n\nexport function validatePayload(input: unknown, now: number = Date.now()): ValidationResult {\n if (input === null || typeof input !== \"object\" || Array.isArray(input)) {\n return fail(\"not-an-object\");\n }\n\n if (hasPollution(input)) return fail(\"prototype-pollution\");\n\n const obj = input as Record<string, unknown>;\n\n if (obj.v !== 1) return fail(\"bad-version\");\n\n const overrides = obj.overrides;\n if (overrides === null || typeof overrides !== \"object\" || Array.isArray(overrides)) {\n return fail(\"missing-overrides\");\n }\n\n const overrideEntries = Object.entries(overrides as Record<string, unknown>);\n if (overrideEntries.length > MAX_OVERRIDES) return fail(\"overrides-too-large\");\n\n const safeOverrides: Record<string, string> = Object.create(null) as Record<string, string>;\n for (const [key, value] of overrideEntries) {\n if (POLLUTION_KEYS.has(key)) return fail(\"prototype-pollution\");\n if (!ID_RE.test(key)) return fail(\"bad-override-key\");\n if (typeof value !== \"string\") return fail(\"bad-override-value\");\n if (!ID_RE.test(value)) return fail(\"bad-override-value\");\n safeOverrides[key] = value;\n }\n\n let context: SharePayload[\"context\"];\n if (obj.context !== undefined) {\n if (obj.context === null || typeof obj.context !== \"object\" || Array.isArray(obj.context)) {\n return fail(\"bad-context\");\n }\n const sanitized = sanitizeContext(obj.context as Record<string, unknown>);\n if (sanitized === null) return fail(\"bad-context\");\n context = sanitized;\n }\n\n let expires: number | undefined;\n if (obj.expires !== undefined) {\n if (typeof obj.expires !== \"number\" || !Number.isFinite(obj.expires)) {\n return fail(\"bad-context\");\n }\n if (obj.expires < now) return fail(\"expired\");\n expires = obj.expires;\n }\n\n // Re-stringify with the sanitized fields and check the encoded size.\n const reStringified = JSON.stringify({\n v: 1,\n overrides: safeOverrides,\n ...(context !== undefined ? { context } : {}),\n ...(expires !== undefined ? { expires } : {}),\n });\n if (reStringified.length > MAX_PAYLOAD_BYTES) return fail(\"payload-too-large\");\n\n const payload: SharePayload = {\n v: 1,\n overrides: safeOverrides,\n ...(context !== undefined ? { context } : {}),\n ...(expires !== undefined ? { expires } : {}),\n };\n return { ok: true, payload };\n}\n\nfunction hasPollution(input: unknown): boolean {\n if (input === null || typeof input !== \"object\") return false;\n if (Array.isArray(input)) {\n for (const item of input) if (hasPollution(item)) return true;\n return false;\n }\n for (const key of Object.keys(input as object)) {\n if (POLLUTION_KEYS.has(key)) return true;\n if (hasPollution((input as Record<string, unknown>)[key])) return true;\n }\n return false;\n}\n\nfunction sanitizeContext(input: Record<string, unknown>): SharePayload[\"context\"] | null {\n const out: Record<string, unknown> = Object.create(null) as Record<string, unknown>;\n for (const key of Object.keys(input)) {\n if (POLLUTION_KEYS.has(key)) return null;\n const value = input[key];\n if (value === undefined) continue;\n if (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n out[key] = value;\n continue;\n }\n if (\n key === \"attributes\" &&\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value)\n ) {\n const attrs: Record<string, unknown> = Object.create(null) as Record<string, unknown>;\n for (const aKey of Object.keys(value as object)) {\n if (POLLUTION_KEYS.has(aKey)) return null;\n const aVal = (value as Record<string, unknown>)[aKey];\n if (typeof aVal === \"string\" || typeof aVal === \"number\" || typeof aVal === \"boolean\") {\n attrs[aKey] = aVal;\n } else {\n return null;\n }\n }\n out[key] = attrs;\n continue;\n }\n return null;\n }\n return out as SharePayload[\"context\"];\n}\n\nfunction fail(reason: ValidationFailure): ValidationResult {\n return { ok: false, reason };\n}\n","/**\n * Encode and decode share payloads for deep links and QR codes.\n *\n * The wire format is documented in `docs/features/qr-sharing.md`:\n *\n * 1. Serialise the payload as compact JSON.\n * 2. Optionally gzip-like compress (deferred — we ship plain base64\n * first; the wire format is forward-compatible because the prefix\n * byte tells the decoder which mode was used).\n * 3. Base64url-encode the bytes (RFC 4648 §5: `+` → `-`, `/` → `_`,\n * no `=` padding).\n *\n * We implement base64url by hand because we cannot rely on\n * `globalThis.btoa` / `Buffer` being present on every RN runtime,\n * and pulling in a base64 polyfill would burst the bundle budget.\n * The encoder/decoder operates on UTF-8 byte arrays via `TextEncoder`\n * / `TextDecoder`, both of which ship in Hermes.\n *\n * The format prefix byte is `0` for \"raw JSON\" and reserved for future\n * compression schemes (`1` = deflate-raw, `2` = brotli, etc.). On\n * decode we tolerate a missing prefix to keep older clients working.\n */\nimport type { SharePayload, ValidationResult } from \"./types.js\";\nimport { validatePayload } from \"./validate.js\";\n\nconst PREFIX_RAW = 0;\n\n/**\n * Encode a payload to its on-the-wire base64url string. Throws on\n * the rare case where validation fails on a payload constructed by\n * the caller themselves — that's a developer error worth surfacing.\n */\nexport function encodeSharePayload(payload: SharePayload): string {\n const validated = validatePayload(payload);\n if (!validated.ok) {\n throw new Error(`Cannot encode invalid share payload: ${validated.reason}`);\n }\n const json = JSON.stringify(validated.payload);\n const utf8 = new TextEncoder().encode(json);\n const framed = new Uint8Array(utf8.length + 1);\n framed[0] = PREFIX_RAW;\n framed.set(utf8, 1);\n return bytesToBase64Url(framed);\n}\n\n/**\n * Decode a base64url string back into a validated payload. Returns\n * a `ValidationResult` so callers can branch on `ok` rather than\n * wrap the call in try/catch.\n */\nexport function decodeSharePayload(encoded: string, now?: number): ValidationResult {\n let bytes: Uint8Array;\n try {\n bytes = base64UrlToBytes(encoded);\n } catch {\n return { ok: false, reason: \"not-an-object\" };\n }\n if (bytes.length === 0) return { ok: false, reason: \"not-an-object\" };\n\n // Detect prefix; tolerate missing prefix for forward compatibility.\n let jsonBytes: Uint8Array;\n if (bytes[0] === PREFIX_RAW) {\n jsonBytes = bytes.subarray(1);\n } else {\n jsonBytes = bytes;\n }\n\n let parsed: unknown;\n try {\n const json = new TextDecoder(\"utf-8\", { fatal: true }).decode(jsonBytes);\n parsed = JSON.parse(json);\n } catch {\n return { ok: false, reason: \"not-an-object\" };\n }\n\n return validatePayload(parsed, now);\n}\n\n// ---------- base64url ------------------------------------------------------\n\nconst ALPHABET = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_\";\n\nconst DECODE_TABLE: Int8Array = (() => {\n const table = new Int8Array(128);\n table.fill(-1);\n for (let i = 0; i < ALPHABET.length; i++) {\n table[ALPHABET.charCodeAt(i)] = i;\n }\n // Accept the standard b64 variants too so links pasted from a\n // non-url-safe encoder still decode.\n table[\"+\".charCodeAt(0)] = 62;\n table[\"/\".charCodeAt(0)] = 63;\n return table;\n})();\n\nexport function bytesToBase64Url(bytes: Uint8Array): string {\n let out = \"\";\n let i = 0;\n for (; i + 3 <= bytes.length; i += 3) {\n const b0 = bytes[i] as number;\n const b1 = bytes[i + 1] as number;\n const b2 = bytes[i + 2] as number;\n out += ALPHABET[b0 >> 2];\n out += ALPHABET[((b0 & 0x03) << 4) | (b1 >> 4)];\n out += ALPHABET[((b1 & 0x0f) << 2) | (b2 >> 6)];\n out += ALPHABET[b2 & 0x3f];\n }\n if (i < bytes.length) {\n const b0 = bytes[i] as number;\n out += ALPHABET[b0 >> 2];\n if (i + 1 < bytes.length) {\n const b1 = bytes[i + 1] as number;\n out += ALPHABET[((b0 & 0x03) << 4) | (b1 >> 4)];\n out += ALPHABET[(b1 & 0x0f) << 2];\n } else {\n out += ALPHABET[(b0 & 0x03) << 4];\n }\n }\n return out;\n}\n\nexport function base64UrlToBytes(input: string): Uint8Array {\n // Strip optional `=` padding from standard base64.\n let s = input;\n while (s.length > 0 && s[s.length - 1] === \"=\") s = s.slice(0, -1);\n if (s.length === 0) return new Uint8Array(0);\n\n const remainder = s.length & 3;\n if (remainder === 1) throw new Error(\"Invalid base64url length\");\n\n const fullGroups = (s.length - remainder) >> 2;\n const outLen = fullGroups * 3 + (remainder === 0 ? 0 : remainder - 1);\n const out = new Uint8Array(outLen);\n\n let outIdx = 0;\n let i = 0;\n for (let g = 0; g < fullGroups; g++, i += 4) {\n const c0 = decodeChar(s.charCodeAt(i));\n const c1 = decodeChar(s.charCodeAt(i + 1));\n const c2 = decodeChar(s.charCodeAt(i + 2));\n const c3 = decodeChar(s.charCodeAt(i + 3));\n out[outIdx++] = (c0 << 2) | (c1 >> 4);\n out[outIdx++] = ((c1 & 0x0f) << 4) | (c2 >> 2);\n out[outIdx++] = ((c2 & 0x03) << 6) | c3;\n }\n if (remainder >= 2) {\n const c0 = decodeChar(s.charCodeAt(i));\n const c1 = decodeChar(s.charCodeAt(i + 1));\n out[outIdx++] = (c0 << 2) | (c1 >> 4);\n if (remainder === 3) {\n const c2 = decodeChar(s.charCodeAt(i + 2));\n out[outIdx++] = ((c1 & 0x0f) << 4) | (c2 >> 2);\n }\n }\n return out;\n}\n\nfunction decodeChar(code: number): number {\n if (code >= 128) throw new Error(\"Invalid base64url character\");\n const v = DECODE_TABLE[code] ?? -1;\n if (v < 0) throw new Error(\"Invalid base64url character\");\n return v;\n}\n","/**\n * `@variantlab/react-native/qr` — QR code sharing helpers.\n *\n * Exposes two tiny helpers: one to turn a variantlab `SharePayload`\n * into an `otpauth://`-style URL that can be rendered as a QR code,\n * and one to consume such a URL when it's scanned back in. The actual\n * QR rendering is **not** included in this package — `react-native-svg`\n * is a huge optional peer, and the Phase 1 kickoff prompt in\n * `docs/phases/phase-1-kickoff-prompts.md` §6.5 says to ship a\n * renderer-agnostic stub:\n *\n * import QRCode from \"react-native-qrcode-svg\";\n * import { buildQrUrl } from \"@variantlab/react-native/qr\";\n *\n * const url = buildQrUrl(engine, { v: 1, overrides: { hero: \"b\" } });\n * <QRCode value={url} size={220} />\n *\n * The URL format is `variantlab://apply?d=<base64url>` where the\n * base64url payload is identical to the deep-link encoder. That means\n * a QR code and a deep link are interchangeable: scanning a QR is\n * literally equivalent to tapping a shared URL.\n *\n * A richer QR feature (signing with HMAC, scan validation, in-overlay\n * scanner) is scheduled for Phase 2. This entrypoint exists today so\n * the wire format is stable and tree-shakeable from day one.\n */\n\nimport { decodeSharePayload, encodeSharePayload } from \"./deep-link/encode.js\";\nimport type { SharePayload, ValidationResult } from \"./deep-link/types.js\";\n\n/** The scheme used for variantlab's own share URLs. */\nexport const VARIANTLAB_URL_SCHEME = \"variantlab\";\n/** The canonical path that applies a share payload. */\nexport const VARIANTLAB_APPLY_PATH = \"apply\";\n\n/**\n * Build a `variantlab://apply?d=...` URL from a share payload.\n *\n * Safe to call with any well-formed payload — invalid payloads throw\n * (see `encodeSharePayload`). Pair the return value with any\n * off-the-shelf `<QRCode />` component.\n */\nexport function buildQrUrl(payload: SharePayload): string {\n const encoded = encodeSharePayload(payload);\n return `${VARIANTLAB_URL_SCHEME}://${VARIANTLAB_APPLY_PATH}?d=${encoded}`;\n}\n\n/**\n * Parse a scanned QR URL and return the validated payload. Mirrors\n * the deep-link handler's tolerance: we accept both\n * `variantlab://apply?d=...` and any other scheme whose query string\n * carries a `d=` parameter, so host apps can reuse their own URL\n * scheme (e.g. `myapp://variantlab?d=...`).\n *\n * Returns a `ValidationResult` rather than throwing, matching\n * `decodeSharePayload`.\n */\nexport function parseQrUrl(url: string, now?: number): ValidationResult {\n const q = extractDataParam(url);\n if (q === null) return { ok: false, reason: \"not-an-object\" };\n return decodeSharePayload(q, now);\n}\n\n/**\n * Extracts the `d` query parameter from a URL string without relying\n * on `URL` (RN's URL polyfill is historically flaky). Returns `null`\n * if the URL has no `d=` param.\n */\nfunction extractDataParam(url: string): string | null {\n const q = url.indexOf(\"?\");\n if (q < 0) return null;\n const hashEnd = url.indexOf(\"#\", q);\n const query = hashEnd < 0 ? url.slice(q + 1) : url.slice(q + 1, hashEnd);\n for (const pair of query.split(\"&\")) {\n const eq = pair.indexOf(\"=\");\n if (eq < 0) continue;\n const key = pair.slice(0, eq);\n if (key !== \"d\") continue;\n try {\n return decodeURIComponent(pair.slice(eq + 1));\n } catch {\n return null;\n }\n }\n return null;\n}\n"]}
|
package/dist/qr.d.cts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { S as SharePayload, V as ValidationResult } from './types-CnSyez2D.cjs';
|
|
2
|
+
import '@variantlab/core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `@variantlab/react-native/qr` — QR code sharing helpers.
|
|
6
|
+
*
|
|
7
|
+
* Exposes two tiny helpers: one to turn a variantlab `SharePayload`
|
|
8
|
+
* into an `otpauth://`-style URL that can be rendered as a QR code,
|
|
9
|
+
* and one to consume such a URL when it's scanned back in. The actual
|
|
10
|
+
* QR rendering is **not** included in this package — `react-native-svg`
|
|
11
|
+
* is a huge optional peer, and the Phase 1 kickoff prompt in
|
|
12
|
+
* `docs/phases/phase-1-kickoff-prompts.md` §6.5 says to ship a
|
|
13
|
+
* renderer-agnostic stub:
|
|
14
|
+
*
|
|
15
|
+
* import QRCode from "react-native-qrcode-svg";
|
|
16
|
+
* import { buildQrUrl } from "@variantlab/react-native/qr";
|
|
17
|
+
*
|
|
18
|
+
* const url = buildQrUrl(engine, { v: 1, overrides: { hero: "b" } });
|
|
19
|
+
* <QRCode value={url} size={220} />
|
|
20
|
+
*
|
|
21
|
+
* The URL format is `variantlab://apply?d=<base64url>` where the
|
|
22
|
+
* base64url payload is identical to the deep-link encoder. That means
|
|
23
|
+
* a QR code and a deep link are interchangeable: scanning a QR is
|
|
24
|
+
* literally equivalent to tapping a shared URL.
|
|
25
|
+
*
|
|
26
|
+
* A richer QR feature (signing with HMAC, scan validation, in-overlay
|
|
27
|
+
* scanner) is scheduled for Phase 2. This entrypoint exists today so
|
|
28
|
+
* the wire format is stable and tree-shakeable from day one.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/** The scheme used for variantlab's own share URLs. */
|
|
32
|
+
declare const VARIANTLAB_URL_SCHEME = "variantlab";
|
|
33
|
+
/** The canonical path that applies a share payload. */
|
|
34
|
+
declare const VARIANTLAB_APPLY_PATH = "apply";
|
|
35
|
+
/**
|
|
36
|
+
* Build a `variantlab://apply?d=...` URL from a share payload.
|
|
37
|
+
*
|
|
38
|
+
* Safe to call with any well-formed payload — invalid payloads throw
|
|
39
|
+
* (see `encodeSharePayload`). Pair the return value with any
|
|
40
|
+
* off-the-shelf `<QRCode />` component.
|
|
41
|
+
*/
|
|
42
|
+
declare function buildQrUrl(payload: SharePayload): string;
|
|
43
|
+
/**
|
|
44
|
+
* Parse a scanned QR URL and return the validated payload. Mirrors
|
|
45
|
+
* the deep-link handler's tolerance: we accept both
|
|
46
|
+
* `variantlab://apply?d=...` and any other scheme whose query string
|
|
47
|
+
* carries a `d=` parameter, so host apps can reuse their own URL
|
|
48
|
+
* scheme (e.g. `myapp://variantlab?d=...`).
|
|
49
|
+
*
|
|
50
|
+
* Returns a `ValidationResult` rather than throwing, matching
|
|
51
|
+
* `decodeSharePayload`.
|
|
52
|
+
*/
|
|
53
|
+
declare function parseQrUrl(url: string, now?: number): ValidationResult;
|
|
54
|
+
|
|
55
|
+
export { VARIANTLAB_APPLY_PATH, VARIANTLAB_URL_SCHEME, buildQrUrl, parseQrUrl };
|
package/dist/qr.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { S as SharePayload, V as ValidationResult } from './types-CnSyez2D.js';
|
|
2
|
+
import '@variantlab/core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `@variantlab/react-native/qr` — QR code sharing helpers.
|
|
6
|
+
*
|
|
7
|
+
* Exposes two tiny helpers: one to turn a variantlab `SharePayload`
|
|
8
|
+
* into an `otpauth://`-style URL that can be rendered as a QR code,
|
|
9
|
+
* and one to consume such a URL when it's scanned back in. The actual
|
|
10
|
+
* QR rendering is **not** included in this package — `react-native-svg`
|
|
11
|
+
* is a huge optional peer, and the Phase 1 kickoff prompt in
|
|
12
|
+
* `docs/phases/phase-1-kickoff-prompts.md` §6.5 says to ship a
|
|
13
|
+
* renderer-agnostic stub:
|
|
14
|
+
*
|
|
15
|
+
* import QRCode from "react-native-qrcode-svg";
|
|
16
|
+
* import { buildQrUrl } from "@variantlab/react-native/qr";
|
|
17
|
+
*
|
|
18
|
+
* const url = buildQrUrl(engine, { v: 1, overrides: { hero: "b" } });
|
|
19
|
+
* <QRCode value={url} size={220} />
|
|
20
|
+
*
|
|
21
|
+
* The URL format is `variantlab://apply?d=<base64url>` where the
|
|
22
|
+
* base64url payload is identical to the deep-link encoder. That means
|
|
23
|
+
* a QR code and a deep link are interchangeable: scanning a QR is
|
|
24
|
+
* literally equivalent to tapping a shared URL.
|
|
25
|
+
*
|
|
26
|
+
* A richer QR feature (signing with HMAC, scan validation, in-overlay
|
|
27
|
+
* scanner) is scheduled for Phase 2. This entrypoint exists today so
|
|
28
|
+
* the wire format is stable and tree-shakeable from day one.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/** The scheme used for variantlab's own share URLs. */
|
|
32
|
+
declare const VARIANTLAB_URL_SCHEME = "variantlab";
|
|
33
|
+
/** The canonical path that applies a share payload. */
|
|
34
|
+
declare const VARIANTLAB_APPLY_PATH = "apply";
|
|
35
|
+
/**
|
|
36
|
+
* Build a `variantlab://apply?d=...` URL from a share payload.
|
|
37
|
+
*
|
|
38
|
+
* Safe to call with any well-formed payload — invalid payloads throw
|
|
39
|
+
* (see `encodeSharePayload`). Pair the return value with any
|
|
40
|
+
* off-the-shelf `<QRCode />` component.
|
|
41
|
+
*/
|
|
42
|
+
declare function buildQrUrl(payload: SharePayload): string;
|
|
43
|
+
/**
|
|
44
|
+
* Parse a scanned QR URL and return the validated payload. Mirrors
|
|
45
|
+
* the deep-link handler's tolerance: we accept both
|
|
46
|
+
* `variantlab://apply?d=...` and any other scheme whose query string
|
|
47
|
+
* carries a `d=` parameter, so host apps can reuse their own URL
|
|
48
|
+
* scheme (e.g. `myapp://variantlab?d=...`).
|
|
49
|
+
*
|
|
50
|
+
* Returns a `ValidationResult` rather than throwing, matching
|
|
51
|
+
* `decodeSharePayload`.
|
|
52
|
+
*/
|
|
53
|
+
declare function parseQrUrl(url: string, now?: number): ValidationResult;
|
|
54
|
+
|
|
55
|
+
export { VARIANTLAB_APPLY_PATH, VARIANTLAB_URL_SCHEME, buildQrUrl, parseQrUrl };
|
package/dist/qr.js
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
// src/deep-link/validate.ts
|
|
2
|
+
var ID_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
3
|
+
var POLLUTION_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
4
|
+
var MAX_OVERRIDES = 100;
|
|
5
|
+
var MAX_PAYLOAD_BYTES = 1024;
|
|
6
|
+
function validatePayload(input, now = Date.now()) {
|
|
7
|
+
if (input === null || typeof input !== "object" || Array.isArray(input)) {
|
|
8
|
+
return fail("not-an-object");
|
|
9
|
+
}
|
|
10
|
+
if (hasPollution(input)) return fail("prototype-pollution");
|
|
11
|
+
const obj = input;
|
|
12
|
+
if (obj.v !== 1) return fail("bad-version");
|
|
13
|
+
const overrides = obj.overrides;
|
|
14
|
+
if (overrides === null || typeof overrides !== "object" || Array.isArray(overrides)) {
|
|
15
|
+
return fail("missing-overrides");
|
|
16
|
+
}
|
|
17
|
+
const overrideEntries = Object.entries(overrides);
|
|
18
|
+
if (overrideEntries.length > MAX_OVERRIDES) return fail("overrides-too-large");
|
|
19
|
+
const safeOverrides = /* @__PURE__ */ Object.create(null);
|
|
20
|
+
for (const [key, value] of overrideEntries) {
|
|
21
|
+
if (POLLUTION_KEYS.has(key)) return fail("prototype-pollution");
|
|
22
|
+
if (!ID_RE.test(key)) return fail("bad-override-key");
|
|
23
|
+
if (typeof value !== "string") return fail("bad-override-value");
|
|
24
|
+
if (!ID_RE.test(value)) return fail("bad-override-value");
|
|
25
|
+
safeOverrides[key] = value;
|
|
26
|
+
}
|
|
27
|
+
let context;
|
|
28
|
+
if (obj.context !== void 0) {
|
|
29
|
+
if (obj.context === null || typeof obj.context !== "object" || Array.isArray(obj.context)) {
|
|
30
|
+
return fail("bad-context");
|
|
31
|
+
}
|
|
32
|
+
const sanitized = sanitizeContext(obj.context);
|
|
33
|
+
if (sanitized === null) return fail("bad-context");
|
|
34
|
+
context = sanitized;
|
|
35
|
+
}
|
|
36
|
+
let expires;
|
|
37
|
+
if (obj.expires !== void 0) {
|
|
38
|
+
if (typeof obj.expires !== "number" || !Number.isFinite(obj.expires)) {
|
|
39
|
+
return fail("bad-context");
|
|
40
|
+
}
|
|
41
|
+
if (obj.expires < now) return fail("expired");
|
|
42
|
+
expires = obj.expires;
|
|
43
|
+
}
|
|
44
|
+
const reStringified = JSON.stringify({
|
|
45
|
+
v: 1,
|
|
46
|
+
overrides: safeOverrides,
|
|
47
|
+
...context !== void 0 ? { context } : {},
|
|
48
|
+
...expires !== void 0 ? { expires } : {}
|
|
49
|
+
});
|
|
50
|
+
if (reStringified.length > MAX_PAYLOAD_BYTES) return fail("payload-too-large");
|
|
51
|
+
const payload = {
|
|
52
|
+
v: 1,
|
|
53
|
+
overrides: safeOverrides,
|
|
54
|
+
...context !== void 0 ? { context } : {},
|
|
55
|
+
...expires !== void 0 ? { expires } : {}
|
|
56
|
+
};
|
|
57
|
+
return { ok: true, payload };
|
|
58
|
+
}
|
|
59
|
+
function hasPollution(input) {
|
|
60
|
+
if (input === null || typeof input !== "object") return false;
|
|
61
|
+
if (Array.isArray(input)) {
|
|
62
|
+
for (const item of input) if (hasPollution(item)) return true;
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
for (const key of Object.keys(input)) {
|
|
66
|
+
if (POLLUTION_KEYS.has(key)) return true;
|
|
67
|
+
if (hasPollution(input[key])) return true;
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
function sanitizeContext(input) {
|
|
72
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
73
|
+
for (const key of Object.keys(input)) {
|
|
74
|
+
if (POLLUTION_KEYS.has(key)) return null;
|
|
75
|
+
const value = input[key];
|
|
76
|
+
if (value === void 0) continue;
|
|
77
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
78
|
+
out[key] = value;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (key === "attributes" && typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
82
|
+
const attrs = /* @__PURE__ */ Object.create(null);
|
|
83
|
+
for (const aKey of Object.keys(value)) {
|
|
84
|
+
if (POLLUTION_KEYS.has(aKey)) return null;
|
|
85
|
+
const aVal = value[aKey];
|
|
86
|
+
if (typeof aVal === "string" || typeof aVal === "number" || typeof aVal === "boolean") {
|
|
87
|
+
attrs[aKey] = aVal;
|
|
88
|
+
} else {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
out[key] = attrs;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
function fail(reason) {
|
|
100
|
+
return { ok: false, reason };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/deep-link/encode.ts
|
|
104
|
+
var PREFIX_RAW = 0;
|
|
105
|
+
function encodeSharePayload(payload) {
|
|
106
|
+
const validated = validatePayload(payload);
|
|
107
|
+
if (!validated.ok) {
|
|
108
|
+
throw new Error(`Cannot encode invalid share payload: ${validated.reason}`);
|
|
109
|
+
}
|
|
110
|
+
const json = JSON.stringify(validated.payload);
|
|
111
|
+
const utf8 = new TextEncoder().encode(json);
|
|
112
|
+
const framed = new Uint8Array(utf8.length + 1);
|
|
113
|
+
framed[0] = PREFIX_RAW;
|
|
114
|
+
framed.set(utf8, 1);
|
|
115
|
+
return bytesToBase64Url(framed);
|
|
116
|
+
}
|
|
117
|
+
function decodeSharePayload(encoded, now) {
|
|
118
|
+
let bytes;
|
|
119
|
+
try {
|
|
120
|
+
bytes = base64UrlToBytes(encoded);
|
|
121
|
+
} catch {
|
|
122
|
+
return { ok: false, reason: "not-an-object" };
|
|
123
|
+
}
|
|
124
|
+
if (bytes.length === 0) return { ok: false, reason: "not-an-object" };
|
|
125
|
+
let jsonBytes;
|
|
126
|
+
if (bytes[0] === PREFIX_RAW) {
|
|
127
|
+
jsonBytes = bytes.subarray(1);
|
|
128
|
+
} else {
|
|
129
|
+
jsonBytes = bytes;
|
|
130
|
+
}
|
|
131
|
+
let parsed;
|
|
132
|
+
try {
|
|
133
|
+
const json = new TextDecoder("utf-8", { fatal: true }).decode(jsonBytes);
|
|
134
|
+
parsed = JSON.parse(json);
|
|
135
|
+
} catch {
|
|
136
|
+
return { ok: false, reason: "not-an-object" };
|
|
137
|
+
}
|
|
138
|
+
return validatePayload(parsed, now);
|
|
139
|
+
}
|
|
140
|
+
var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
141
|
+
var DECODE_TABLE = (() => {
|
|
142
|
+
const table = new Int8Array(128);
|
|
143
|
+
table.fill(-1);
|
|
144
|
+
for (let i = 0; i < ALPHABET.length; i++) {
|
|
145
|
+
table[ALPHABET.charCodeAt(i)] = i;
|
|
146
|
+
}
|
|
147
|
+
table["+".charCodeAt(0)] = 62;
|
|
148
|
+
table["/".charCodeAt(0)] = 63;
|
|
149
|
+
return table;
|
|
150
|
+
})();
|
|
151
|
+
function bytesToBase64Url(bytes) {
|
|
152
|
+
let out = "";
|
|
153
|
+
let i = 0;
|
|
154
|
+
for (; i + 3 <= bytes.length; i += 3) {
|
|
155
|
+
const b0 = bytes[i];
|
|
156
|
+
const b1 = bytes[i + 1];
|
|
157
|
+
const b2 = bytes[i + 2];
|
|
158
|
+
out += ALPHABET[b0 >> 2];
|
|
159
|
+
out += ALPHABET[(b0 & 3) << 4 | b1 >> 4];
|
|
160
|
+
out += ALPHABET[(b1 & 15) << 2 | b2 >> 6];
|
|
161
|
+
out += ALPHABET[b2 & 63];
|
|
162
|
+
}
|
|
163
|
+
if (i < bytes.length) {
|
|
164
|
+
const b0 = bytes[i];
|
|
165
|
+
out += ALPHABET[b0 >> 2];
|
|
166
|
+
if (i + 1 < bytes.length) {
|
|
167
|
+
const b1 = bytes[i + 1];
|
|
168
|
+
out += ALPHABET[(b0 & 3) << 4 | b1 >> 4];
|
|
169
|
+
out += ALPHABET[(b1 & 15) << 2];
|
|
170
|
+
} else {
|
|
171
|
+
out += ALPHABET[(b0 & 3) << 4];
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
function base64UrlToBytes(input) {
|
|
177
|
+
let s = input;
|
|
178
|
+
while (s.length > 0 && s[s.length - 1] === "=") s = s.slice(0, -1);
|
|
179
|
+
if (s.length === 0) return new Uint8Array(0);
|
|
180
|
+
const remainder = s.length & 3;
|
|
181
|
+
if (remainder === 1) throw new Error("Invalid base64url length");
|
|
182
|
+
const fullGroups = s.length - remainder >> 2;
|
|
183
|
+
const outLen = fullGroups * 3 + (remainder === 0 ? 0 : remainder - 1);
|
|
184
|
+
const out = new Uint8Array(outLen);
|
|
185
|
+
let outIdx = 0;
|
|
186
|
+
let i = 0;
|
|
187
|
+
for (let g = 0; g < fullGroups; g++, i += 4) {
|
|
188
|
+
const c0 = decodeChar(s.charCodeAt(i));
|
|
189
|
+
const c1 = decodeChar(s.charCodeAt(i + 1));
|
|
190
|
+
const c2 = decodeChar(s.charCodeAt(i + 2));
|
|
191
|
+
const c3 = decodeChar(s.charCodeAt(i + 3));
|
|
192
|
+
out[outIdx++] = c0 << 2 | c1 >> 4;
|
|
193
|
+
out[outIdx++] = (c1 & 15) << 4 | c2 >> 2;
|
|
194
|
+
out[outIdx++] = (c2 & 3) << 6 | c3;
|
|
195
|
+
}
|
|
196
|
+
if (remainder >= 2) {
|
|
197
|
+
const c0 = decodeChar(s.charCodeAt(i));
|
|
198
|
+
const c1 = decodeChar(s.charCodeAt(i + 1));
|
|
199
|
+
out[outIdx++] = c0 << 2 | c1 >> 4;
|
|
200
|
+
if (remainder === 3) {
|
|
201
|
+
const c2 = decodeChar(s.charCodeAt(i + 2));
|
|
202
|
+
out[outIdx++] = (c1 & 15) << 4 | c2 >> 2;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return out;
|
|
206
|
+
}
|
|
207
|
+
function decodeChar(code) {
|
|
208
|
+
if (code >= 128) throw new Error("Invalid base64url character");
|
|
209
|
+
const v = DECODE_TABLE[code] ?? -1;
|
|
210
|
+
if (v < 0) throw new Error("Invalid base64url character");
|
|
211
|
+
return v;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// src/qr.ts
|
|
215
|
+
var VARIANTLAB_URL_SCHEME = "variantlab";
|
|
216
|
+
var VARIANTLAB_APPLY_PATH = "apply";
|
|
217
|
+
function buildQrUrl(payload) {
|
|
218
|
+
const encoded = encodeSharePayload(payload);
|
|
219
|
+
return `${VARIANTLAB_URL_SCHEME}://${VARIANTLAB_APPLY_PATH}?d=${encoded}`;
|
|
220
|
+
}
|
|
221
|
+
function parseQrUrl(url, now) {
|
|
222
|
+
const q = extractDataParam(url);
|
|
223
|
+
if (q === null) return { ok: false, reason: "not-an-object" };
|
|
224
|
+
return decodeSharePayload(q, now);
|
|
225
|
+
}
|
|
226
|
+
function extractDataParam(url) {
|
|
227
|
+
const q = url.indexOf("?");
|
|
228
|
+
if (q < 0) return null;
|
|
229
|
+
const hashEnd = url.indexOf("#", q);
|
|
230
|
+
const query = hashEnd < 0 ? url.slice(q + 1) : url.slice(q + 1, hashEnd);
|
|
231
|
+
for (const pair of query.split("&")) {
|
|
232
|
+
const eq = pair.indexOf("=");
|
|
233
|
+
if (eq < 0) continue;
|
|
234
|
+
const key = pair.slice(0, eq);
|
|
235
|
+
if (key !== "d") continue;
|
|
236
|
+
try {
|
|
237
|
+
return decodeURIComponent(pair.slice(eq + 1));
|
|
238
|
+
} catch {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export { VARIANTLAB_APPLY_PATH, VARIANTLAB_URL_SCHEME, buildQrUrl, parseQrUrl };
|
|
246
|
+
//# sourceMappingURL=qr.js.map
|
|
247
|
+
//# sourceMappingURL=qr.js.map
|