@btoon/js 0.0.0-stage → 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 +328 -2
- package/dist/cjs/constants.d.ts +34 -0
- package/dist/cjs/constants.js +32 -0
- package/dist/cjs/core.d.ts +15 -0
- package/dist/cjs/core.js +275 -0
- package/dist/cjs/decode.d.ts +10 -0
- package/dist/cjs/decode.js +554 -0
- package/dist/cjs/encode.d.ts +2 -0
- package/dist/cjs/encode.js +946 -0
- package/dist/cjs/errors.d.ts +53 -0
- package/dist/cjs/errors.js +45 -0
- package/dist/cjs/index.d.ts +68 -0
- package/dist/cjs/index.js +86 -0
- package/dist/cjs/inflate.d.ts +9 -0
- package/dist/cjs/inflate.js +353 -0
- package/dist/cjs/node.d.ts +64 -0
- package/dist/cjs/node.js +82 -0
- package/dist/cjs/options.d.ts +95 -0
- package/dist/cjs/options.js +128 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/platform-node.d.ts +3 -0
- package/dist/cjs/platform-node.js +132 -0
- package/dist/cjs/platform-portable.d.ts +7 -0
- package/dist/cjs/platform-portable.js +16 -0
- package/dist/cjs/sha256.d.ts +5 -0
- package/dist/cjs/sha256.js +141 -0
- package/dist/cjs/utf8.d.ts +19 -0
- package/dist/cjs/utf8.js +107 -0
- package/dist/cjs/values.d.ts +58 -0
- package/dist/cjs/values.js +129 -0
- package/dist/cjs/web.d.ts +25 -0
- package/dist/cjs/web.js +151 -0
- package/dist/esm/constants.d.ts +34 -0
- package/dist/esm/constants.js +29 -0
- package/dist/esm/core.d.ts +15 -0
- package/dist/esm/core.js +269 -0
- package/dist/esm/decode.d.ts +10 -0
- package/dist/esm/decode.js +549 -0
- package/dist/esm/encode.d.ts +2 -0
- package/dist/esm/encode.js +943 -0
- package/dist/esm/errors.d.ts +53 -0
- package/dist/esm/errors.js +39 -0
- package/dist/esm/index.d.ts +68 -0
- package/dist/esm/index.js +74 -0
- package/dist/esm/inflate.d.ts +9 -0
- package/dist/esm/inflate.js +349 -0
- package/dist/esm/node.d.ts +64 -0
- package/dist/esm/node.js +70 -0
- package/dist/esm/options.d.ts +95 -0
- package/dist/esm/options.js +124 -0
- package/dist/esm/platform-node.d.ts +3 -0
- package/dist/esm/platform-node.js +96 -0
- package/dist/esm/platform-portable.d.ts +7 -0
- package/dist/esm/platform-portable.js +13 -0
- package/dist/esm/sha256.d.ts +5 -0
- package/dist/esm/sha256.js +136 -0
- package/dist/esm/utf8.d.ts +19 -0
- package/dist/esm/utf8.js +99 -0
- package/dist/esm/values.d.ts +58 -0
- package/dist/esm/values.js +120 -0
- package/dist/esm/web.d.ts +25 -0
- package/dist/esm/web.js +143 -0
- package/package.json +85 -4
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/** True for a Uint8Array from any realm (including Node.js Buffers). */
|
|
2
|
+
export declare function isUint8Array(v: unknown): v is Uint8Array;
|
|
3
|
+
/**
|
|
4
|
+
* A generic MessagePack extension value: a signed 8-bit type id and an opaque payload.
|
|
5
|
+
*
|
|
6
|
+
* Decoding returns `Ext` for every extension id BTOON does not define (application ids
|
|
7
|
+
* 0…127 and the reserved negative ids). Encoding writes it verbatim with the smallest
|
|
8
|
+
* header. An `Ext` may also carry one of the ids BTOON defines (-1…-6, -10) — this is how
|
|
9
|
+
* to emit a `date` (-2) or `datetime` (-3) value, which have no JavaScript type of their
|
|
10
|
+
* own — but its payload must then be well-formed for that type, or encoding fails with
|
|
11
|
+
* `invalid_argument`.
|
|
12
|
+
*/
|
|
13
|
+
export declare class Ext {
|
|
14
|
+
/** Extension type id, an integer in [-128, 127]. */
|
|
15
|
+
readonly type: number;
|
|
16
|
+
/** The payload bytes (not copied by the constructor). */
|
|
17
|
+
readonly data: Uint8Array;
|
|
18
|
+
constructor(type: number, data: Uint8Array);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A BTOON timestamp (extension -1): an instant with nanosecond precision and an optional
|
|
22
|
+
* UTC offset.
|
|
23
|
+
*
|
|
24
|
+
* `seconds` and `nanoseconds` always describe the instant in UTC. `offsetMinutes` is
|
|
25
|
+
* presentation metadata ("local time is UTC + offset") and is encoded only when present.
|
|
26
|
+
* Decoding returns `Timestamp` objects when `timestamps: 'object'` is set, and for
|
|
27
|
+
* instants outside the range of `Date`.
|
|
28
|
+
*/
|
|
29
|
+
export declare class Timestamp {
|
|
30
|
+
/** Whole seconds since the Unix epoch (UTC), in the int64 range. */
|
|
31
|
+
readonly seconds: bigint;
|
|
32
|
+
/** Nanoseconds within the second, in [0, 999 999 999]. */
|
|
33
|
+
readonly nanoseconds: number;
|
|
34
|
+
/** UTC offset in minutes, in [-1080, 1080], or `undefined` for a plain UTC timestamp. */
|
|
35
|
+
readonly offsetMinutes?: number;
|
|
36
|
+
/**
|
|
37
|
+
* @param seconds Seconds since the Unix epoch; a `bigint`, or a `number` that is a safe integer.
|
|
38
|
+
* @param nanoseconds Nanoseconds within the second, an integer in [0, 1e9).
|
|
39
|
+
* @param offsetMinutes Optional UTC offset in minutes, an integer in [-1080, 1080].
|
|
40
|
+
* @throws {BtoonError} `invalid_argument` if a field is out of range.
|
|
41
|
+
*/
|
|
42
|
+
constructor(seconds: bigint | number, nanoseconds: number, offsetMinutes?: number);
|
|
43
|
+
/**
|
|
44
|
+
* The instant as a `Date`. Sub-millisecond nanoseconds are truncated toward the past and
|
|
45
|
+
* the offset is dropped (a `Date` has no offset).
|
|
46
|
+
* @throws {BtoonError} `invalid_argument` if the instant is outside the range of `Date`.
|
|
47
|
+
*/
|
|
48
|
+
toDate(): Date;
|
|
49
|
+
/**
|
|
50
|
+
* The UTC timestamp of a `Date` (millisecond precision, no offset).
|
|
51
|
+
* @throws {BtoonError} `invalid_argument` for an invalid `Date`.
|
|
52
|
+
*/
|
|
53
|
+
static fromDate(date: Date): Timestamp;
|
|
54
|
+
}
|
|
55
|
+
/** Milliseconds of a Date (from any realm); throws for an invalid Date. */
|
|
56
|
+
export declare function dateTime(date: unknown): number;
|
|
57
|
+
export declare function isExt(v: object): v is Ext;
|
|
58
|
+
export declare function isTimestamp(v: object): v is Timestamp;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { EXT_BRAND, MAX_DATE_MS, MAX_OFFSET_MINUTES, NANOS_PER_SECOND, TIMESTAMP_BRAND, } from './constants.js';
|
|
2
|
+
import { fail } from './errors.js';
|
|
3
|
+
const INT64_MIN = -(1n << 63n);
|
|
4
|
+
const INT64_MAX = (1n << 63n) - 1n;
|
|
5
|
+
/** True for a Uint8Array from any realm (including Node.js Buffers). */
|
|
6
|
+
export function isUint8Array(v) {
|
|
7
|
+
return ArrayBuffer.isView(v) && v[Symbol.toStringTag] === 'Uint8Array';
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A generic MessagePack extension value: a signed 8-bit type id and an opaque payload.
|
|
11
|
+
*
|
|
12
|
+
* Decoding returns `Ext` for every extension id BTOON does not define (application ids
|
|
13
|
+
* 0…127 and the reserved negative ids). Encoding writes it verbatim with the smallest
|
|
14
|
+
* header. An `Ext` may also carry one of the ids BTOON defines (-1…-6, -10) — this is how
|
|
15
|
+
* to emit a `date` (-2) or `datetime` (-3) value, which have no JavaScript type of their
|
|
16
|
+
* own — but its payload must then be well-formed for that type, or encoding fails with
|
|
17
|
+
* `invalid_argument`.
|
|
18
|
+
*/
|
|
19
|
+
export class Ext {
|
|
20
|
+
constructor(type, data) {
|
|
21
|
+
if (typeof type !== 'number' || !Number.isInteger(type) || type < -128 || type > 127) {
|
|
22
|
+
throw fail('invalid_argument', `Ext type must be an integer in [-128, 127], got ${String(type)}`);
|
|
23
|
+
}
|
|
24
|
+
if (!isUint8Array(data)) {
|
|
25
|
+
throw fail('invalid_argument', 'Ext data must be a Uint8Array');
|
|
26
|
+
}
|
|
27
|
+
this.type = type;
|
|
28
|
+
this.data = data;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
Object.defineProperty(Ext.prototype, EXT_BRAND, { value: true });
|
|
32
|
+
/**
|
|
33
|
+
* A BTOON timestamp (extension -1): an instant with nanosecond precision and an optional
|
|
34
|
+
* UTC offset.
|
|
35
|
+
*
|
|
36
|
+
* `seconds` and `nanoseconds` always describe the instant in UTC. `offsetMinutes` is
|
|
37
|
+
* presentation metadata ("local time is UTC + offset") and is encoded only when present.
|
|
38
|
+
* Decoding returns `Timestamp` objects when `timestamps: 'object'` is set, and for
|
|
39
|
+
* instants outside the range of `Date`.
|
|
40
|
+
*/
|
|
41
|
+
export class Timestamp {
|
|
42
|
+
/**
|
|
43
|
+
* @param seconds Seconds since the Unix epoch; a `bigint`, or a `number` that is a safe integer.
|
|
44
|
+
* @param nanoseconds Nanoseconds within the second, an integer in [0, 1e9).
|
|
45
|
+
* @param offsetMinutes Optional UTC offset in minutes, an integer in [-1080, 1080].
|
|
46
|
+
* @throws {BtoonError} `invalid_argument` if a field is out of range.
|
|
47
|
+
*/
|
|
48
|
+
constructor(seconds, nanoseconds, offsetMinutes) {
|
|
49
|
+
let s;
|
|
50
|
+
if (typeof seconds === 'bigint') {
|
|
51
|
+
s = seconds;
|
|
52
|
+
}
|
|
53
|
+
else if (typeof seconds === 'number' && Number.isSafeInteger(seconds)) {
|
|
54
|
+
s = BigInt(seconds);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
throw fail('invalid_argument', 'Timestamp seconds must be a bigint or a safe integer');
|
|
58
|
+
}
|
|
59
|
+
if (s < INT64_MIN || s > INT64_MAX) {
|
|
60
|
+
throw fail('invalid_argument', 'Timestamp seconds must fit in a signed 64-bit integer');
|
|
61
|
+
}
|
|
62
|
+
if (typeof nanoseconds !== 'number' ||
|
|
63
|
+
!Number.isInteger(nanoseconds) ||
|
|
64
|
+
nanoseconds < 0 ||
|
|
65
|
+
nanoseconds >= NANOS_PER_SECOND) {
|
|
66
|
+
throw fail('invalid_argument', 'Timestamp nanoseconds must be an integer in [0, 999999999]');
|
|
67
|
+
}
|
|
68
|
+
if (offsetMinutes !== undefined &&
|
|
69
|
+
(typeof offsetMinutes !== 'number' ||
|
|
70
|
+
!Number.isInteger(offsetMinutes) ||
|
|
71
|
+
offsetMinutes < -MAX_OFFSET_MINUTES ||
|
|
72
|
+
offsetMinutes > MAX_OFFSET_MINUTES)) {
|
|
73
|
+
throw fail('invalid_argument', 'Timestamp offsetMinutes must be an integer in [-1080, 1080]');
|
|
74
|
+
}
|
|
75
|
+
this.seconds = s;
|
|
76
|
+
this.nanoseconds = nanoseconds;
|
|
77
|
+
this.offsetMinutes = offsetMinutes;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The instant as a `Date`. Sub-millisecond nanoseconds are truncated toward the past and
|
|
81
|
+
* the offset is dropped (a `Date` has no offset).
|
|
82
|
+
* @throws {BtoonError} `invalid_argument` if the instant is outside the range of `Date`.
|
|
83
|
+
*/
|
|
84
|
+
toDate() {
|
|
85
|
+
const ms = this.seconds * 1000n + BigInt(Math.floor(this.nanoseconds / 1e6));
|
|
86
|
+
if (ms > BigInt(MAX_DATE_MS) || ms < -BigInt(MAX_DATE_MS)) {
|
|
87
|
+
throw fail('invalid_argument', 'Timestamp is outside the range of Date');
|
|
88
|
+
}
|
|
89
|
+
return new Date(Number(ms));
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The UTC timestamp of a `Date` (millisecond precision, no offset).
|
|
93
|
+
* @throws {BtoonError} `invalid_argument` for an invalid `Date`.
|
|
94
|
+
*/
|
|
95
|
+
static fromDate(date) {
|
|
96
|
+
const ms = dateTime(date);
|
|
97
|
+
const seconds = Math.floor(ms / 1000);
|
|
98
|
+
return new Timestamp(BigInt(seconds), (ms - seconds * 1000) * 1e6);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
Object.defineProperty(Timestamp.prototype, TIMESTAMP_BRAND, { value: true });
|
|
102
|
+
/** Milliseconds of a Date (from any realm); throws for an invalid Date. */
|
|
103
|
+
export function dateTime(date) {
|
|
104
|
+
let ms;
|
|
105
|
+
try {
|
|
106
|
+
ms = Date.prototype.getTime.call(date);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
throw fail('invalid_argument', 'expected a Date');
|
|
110
|
+
}
|
|
111
|
+
if (!Number.isFinite(ms))
|
|
112
|
+
throw fail('invalid_argument', 'cannot encode an invalid Date');
|
|
113
|
+
return ms;
|
|
114
|
+
}
|
|
115
|
+
export function isExt(v) {
|
|
116
|
+
return v[EXT_BRAND] === true;
|
|
117
|
+
}
|
|
118
|
+
export function isTimestamp(v) {
|
|
119
|
+
return v[TIMESTAMP_BRAND] === true;
|
|
120
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CompressionAlgorithm } from './options.js';
|
|
2
|
+
/** The Compression Streams format name for `alg`, if this runtime supports it. */
|
|
3
|
+
export declare function webFormat(alg: CompressionAlgorithm, kind: 'CompressionStream' | 'DecompressionStream'): string | undefined;
|
|
4
|
+
export declare function webCompress(format: string, data: Uint8Array): Promise<Uint8Array>;
|
|
5
|
+
/**
|
|
6
|
+
* Decompresses with a DecompressionStream, stopping as soon as the output exceeds
|
|
7
|
+
* `limit` (the declared size). For zlib it also checks that the payload ends with the
|
|
8
|
+
* Adler-32 of the output: older runtimes silently ignore bytes after the end of the
|
|
9
|
+
* stream, which a strict decoder must reject.
|
|
10
|
+
*/
|
|
11
|
+
export declare function webDecompress(alg: CompressionAlgorithm, format: string, data: Uint8Array, limit: number): Promise<Uint8Array>;
|
|
12
|
+
type Subtle = {
|
|
13
|
+
importKey(format: 'raw', key: Uint8Array, alg: {
|
|
14
|
+
name: string;
|
|
15
|
+
hash: string;
|
|
16
|
+
}, extractable: boolean, usages: string[]): Promise<unknown>;
|
|
17
|
+
sign(alg: string, key: unknown, data: Uint8Array): Promise<ArrayBuffer>;
|
|
18
|
+
verify(alg: string, key: unknown, signature: Uint8Array, data: Uint8Array): Promise<boolean>;
|
|
19
|
+
};
|
|
20
|
+
/** WebCrypto's SubtleCrypto, if available (absent in Node 18 and on insecure origins). */
|
|
21
|
+
export declare function subtle(): Subtle | undefined;
|
|
22
|
+
export declare function webHmacSign(s: Subtle, key: Uint8Array, data: Uint8Array): Promise<Uint8Array>;
|
|
23
|
+
/** Constant-time verification (crypto.subtle.verify compares internally). */
|
|
24
|
+
export declare function webHmacVerify(s: Subtle, key: Uint8Array, mac: Uint8Array, data: Uint8Array): Promise<boolean>;
|
|
25
|
+
export {};
|
package/dist/esm/web.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// Asynchronous implementations on top of web platform APIs: Compression Streams and
|
|
2
|
+
// WebCrypto. Used by encodeAsync/decodeAsync wherever the runtime provides them.
|
|
3
|
+
import { BtoonError, fail } from './errors.js';
|
|
4
|
+
import { adler32 } from './inflate.js';
|
|
5
|
+
const FORMATS = { zlib: 'deflate', brotli: 'brotli', zstd: 'zstd' };
|
|
6
|
+
const CHUNK = 64 * 1024;
|
|
7
|
+
const supported = new Map();
|
|
8
|
+
function streamCtor(kind) {
|
|
9
|
+
const ctor = globalThis[kind];
|
|
10
|
+
return typeof ctor === 'function' ? ctor : undefined;
|
|
11
|
+
}
|
|
12
|
+
/** The Compression Streams format name for `alg`, if this runtime supports it. */
|
|
13
|
+
export function webFormat(alg, kind) {
|
|
14
|
+
const format = FORMATS[alg];
|
|
15
|
+
const Ctor = streamCtor(kind);
|
|
16
|
+
if (format === undefined || Ctor === undefined)
|
|
17
|
+
return undefined;
|
|
18
|
+
const key = `${kind}:${format}`;
|
|
19
|
+
let ok = supported.get(key);
|
|
20
|
+
if (ok === undefined) {
|
|
21
|
+
try {
|
|
22
|
+
new Ctor(format);
|
|
23
|
+
ok = true;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
ok = false;
|
|
27
|
+
}
|
|
28
|
+
supported.set(key, ok);
|
|
29
|
+
}
|
|
30
|
+
return ok ? format : undefined;
|
|
31
|
+
}
|
|
32
|
+
function concat(chunks, total) {
|
|
33
|
+
if (chunks.length === 1 && chunks[0].byteLength === total)
|
|
34
|
+
return chunks[0];
|
|
35
|
+
const out = new Uint8Array(total);
|
|
36
|
+
let p = 0;
|
|
37
|
+
for (const c of chunks) {
|
|
38
|
+
out.set(c, p);
|
|
39
|
+
p += c.byteLength;
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
async function readAll(reader, limit, alg) {
|
|
44
|
+
const chunks = [];
|
|
45
|
+
let total = 0;
|
|
46
|
+
for (;;) {
|
|
47
|
+
const { done, value } = await reader.read();
|
|
48
|
+
if (done)
|
|
49
|
+
break;
|
|
50
|
+
total += value.byteLength;
|
|
51
|
+
if (total > limit) {
|
|
52
|
+
reader.cancel().catch(() => undefined);
|
|
53
|
+
throw fail('invalid_envelope', `${alg} payload decompresses to more than the declared size`);
|
|
54
|
+
}
|
|
55
|
+
chunks.push(value);
|
|
56
|
+
}
|
|
57
|
+
return concat(chunks, total);
|
|
58
|
+
}
|
|
59
|
+
/** Feeds `data` in chunks, honouring backpressure, then closes the writer. */
|
|
60
|
+
async function pump(writer, data) {
|
|
61
|
+
for (let i = 0; i < data.length; i += CHUNK) {
|
|
62
|
+
await writer.ready;
|
|
63
|
+
await writer.write(data.subarray(i, Math.min(i + CHUNK, data.length)));
|
|
64
|
+
}
|
|
65
|
+
await writer.close();
|
|
66
|
+
}
|
|
67
|
+
function messageOf(e) {
|
|
68
|
+
return e instanceof Error ? e.message || e.name : String(e);
|
|
69
|
+
}
|
|
70
|
+
export async function webCompress(format, data) {
|
|
71
|
+
const stream = new (streamCtor('CompressionStream'))(format);
|
|
72
|
+
const writer = stream.writable.getWriter();
|
|
73
|
+
const writing = pump(writer, data);
|
|
74
|
+
writing.catch(() => undefined);
|
|
75
|
+
const out = await readAll(stream.readable.getReader(), Infinity, format);
|
|
76
|
+
await writing;
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Decompresses with a DecompressionStream, stopping as soon as the output exceeds
|
|
81
|
+
* `limit` (the declared size). For zlib it also checks that the payload ends with the
|
|
82
|
+
* Adler-32 of the output: older runtimes silently ignore bytes after the end of the
|
|
83
|
+
* stream, which a strict decoder must reject.
|
|
84
|
+
*/
|
|
85
|
+
export async function webDecompress(alg, format, data, limit) {
|
|
86
|
+
const stream = new (streamCtor('DecompressionStream'))(format);
|
|
87
|
+
const writer = stream.writable.getWriter();
|
|
88
|
+
const writing = pump(writer, data);
|
|
89
|
+
writing.catch(() => undefined);
|
|
90
|
+
let out;
|
|
91
|
+
try {
|
|
92
|
+
out = await readAll(stream.readable.getReader(), limit, alg);
|
|
93
|
+
await writing;
|
|
94
|
+
}
|
|
95
|
+
catch (e) {
|
|
96
|
+
writer.abort(e).catch(() => undefined);
|
|
97
|
+
if (e instanceof BtoonError)
|
|
98
|
+
throw e;
|
|
99
|
+
throw fail('invalid_envelope', `${alg} payload is corrupt: ${messageOf(e)}`, e);
|
|
100
|
+
}
|
|
101
|
+
if (alg === 'zlib') {
|
|
102
|
+
const n = data.length;
|
|
103
|
+
const trailer = n >= 4 ? ((data[n - 4] << 24) | (data[n - 3] << 16) | (data[n - 2] << 8) | data[n - 1]) >>> 0 : -1;
|
|
104
|
+
if (trailer !== adler32(out))
|
|
105
|
+
throw fail('invalid_envelope', 'zlib payload has trailing bytes');
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
/** WebCrypto's SubtleCrypto, if available (absent in Node 18 and on insecure origins). */
|
|
110
|
+
export function subtle() {
|
|
111
|
+
const c = globalThis.crypto;
|
|
112
|
+
const s = c?.subtle;
|
|
113
|
+
return s && typeof s.importKey === 'function' && typeof s.sign === 'function' && typeof s.verify === 'function'
|
|
114
|
+
? s
|
|
115
|
+
: undefined;
|
|
116
|
+
}
|
|
117
|
+
async function importKey(s, key, usage) {
|
|
118
|
+
try {
|
|
119
|
+
return await s.importKey('raw', key, { name: 'HMAC', hash: 'SHA-256' }, false, [usage]);
|
|
120
|
+
}
|
|
121
|
+
catch (e) {
|
|
122
|
+
throw fail('invalid_argument', `cannot use hmacKey with WebCrypto: ${messageOf(e)}`, e);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
export async function webHmacSign(s, key, data) {
|
|
126
|
+
const k = await importKey(s, key, 'sign');
|
|
127
|
+
try {
|
|
128
|
+
return new Uint8Array(await s.sign('HMAC', k, data));
|
|
129
|
+
}
|
|
130
|
+
catch (e) {
|
|
131
|
+
throw fail('invalid_argument', `WebCrypto HMAC signing failed: ${messageOf(e)}`, e);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/** Constant-time verification (crypto.subtle.verify compares internally). */
|
|
135
|
+
export async function webHmacVerify(s, key, mac, data) {
|
|
136
|
+
const k = await importKey(s, key, 'verify');
|
|
137
|
+
try {
|
|
138
|
+
return await s.verify('HMAC', k, mac, data);
|
|
139
|
+
}
|
|
140
|
+
catch (e) {
|
|
141
|
+
throw fail('authentication_failed', `WebCrypto HMAC verification failed: ${messageOf(e)}`, e);
|
|
142
|
+
}
|
|
143
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,87 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@btoon/js",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "BTOON (Binary TOON) for JavaScript and TypeScript: canonical, hardened MessagePack with a columnar encoding for tables. Pure TypeScript, zero dependencies; runs in browsers, Node.js, Deno, Bun and edge runtimes.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"btoon",
|
|
7
|
+
"messagepack",
|
|
8
|
+
"msgpack",
|
|
9
|
+
"binary",
|
|
10
|
+
"serialization",
|
|
11
|
+
"canonical",
|
|
12
|
+
"tabular",
|
|
13
|
+
"columnar",
|
|
14
|
+
"browser",
|
|
15
|
+
"deno",
|
|
16
|
+
"bun"
|
|
17
|
+
],
|
|
18
|
+
"homepage": "https://btoon.net",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/BTOON-project/btoon-javascript.git"
|
|
22
|
+
},
|
|
23
|
+
"bugs": "https://github.com/BTOON-project/btoon-javascript/issues",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"author": "BTOON Contributors",
|
|
26
|
+
"type": "module",
|
|
27
|
+
"sideEffects": false,
|
|
28
|
+
"main": "./dist/cjs/index.js",
|
|
29
|
+
"module": "./dist/esm/index.js",
|
|
30
|
+
"types": "./dist/cjs/index.d.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"deno": {
|
|
34
|
+
"import": { "types": "./dist/esm/index.d.ts", "default": "./dist/esm/index.js" },
|
|
35
|
+
"require": { "types": "./dist/cjs/index.d.ts", "default": "./dist/cjs/index.js" }
|
|
36
|
+
},
|
|
37
|
+
"browser": {
|
|
38
|
+
"import": { "types": "./dist/esm/index.d.ts", "default": "./dist/esm/index.js" },
|
|
39
|
+
"require": { "types": "./dist/cjs/index.d.ts", "default": "./dist/cjs/index.js" }
|
|
40
|
+
},
|
|
41
|
+
"worker": {
|
|
42
|
+
"import": { "types": "./dist/esm/index.d.ts", "default": "./dist/esm/index.js" },
|
|
43
|
+
"require": { "types": "./dist/cjs/index.d.ts", "default": "./dist/cjs/index.js" }
|
|
44
|
+
},
|
|
45
|
+
"workerd": {
|
|
46
|
+
"import": { "types": "./dist/esm/index.d.ts", "default": "./dist/esm/index.js" },
|
|
47
|
+
"require": { "types": "./dist/cjs/index.d.ts", "default": "./dist/cjs/index.js" }
|
|
48
|
+
},
|
|
49
|
+
"edge-light": {
|
|
50
|
+
"import": { "types": "./dist/esm/index.d.ts", "default": "./dist/esm/index.js" },
|
|
51
|
+
"require": { "types": "./dist/cjs/index.d.ts", "default": "./dist/cjs/index.js" }
|
|
52
|
+
},
|
|
53
|
+
"react-native": {
|
|
54
|
+
"import": { "types": "./dist/esm/index.d.ts", "default": "./dist/esm/index.js" },
|
|
55
|
+
"require": { "types": "./dist/cjs/index.d.ts", "default": "./dist/cjs/index.js" }
|
|
56
|
+
},
|
|
57
|
+
"node": {
|
|
58
|
+
"import": { "types": "./dist/esm/node.d.ts", "default": "./dist/esm/node.js" },
|
|
59
|
+
"require": { "types": "./dist/cjs/node.d.ts", "default": "./dist/cjs/node.js" }
|
|
60
|
+
},
|
|
61
|
+
"import": { "types": "./dist/esm/index.d.ts", "default": "./dist/esm/index.js" },
|
|
62
|
+
"require": { "types": "./dist/cjs/index.d.ts", "default": "./dist/cjs/index.js" },
|
|
63
|
+
"default": "./dist/esm/index.js"
|
|
64
|
+
},
|
|
65
|
+
"./package.json": "./package.json"
|
|
66
|
+
},
|
|
67
|
+
"files": [
|
|
68
|
+
"dist",
|
|
69
|
+
"README.md",
|
|
70
|
+
"LICENSE"
|
|
71
|
+
],
|
|
72
|
+
"scripts": {
|
|
73
|
+
"build": "node scripts/build.mjs",
|
|
74
|
+
"typecheck": "node scripts/tsc.mjs -p tsconfig.json --noEmit",
|
|
75
|
+
"test": "node scripts/test.mjs",
|
|
76
|
+
"bench": "node bench/bench.mjs",
|
|
77
|
+
"vectors:sync": "node scripts/sync-vectors.mjs",
|
|
78
|
+
"prepack": "npm run build"
|
|
79
|
+
},
|
|
80
|
+
"engines": {
|
|
81
|
+
"node": ">=18"
|
|
82
|
+
},
|
|
83
|
+
"devDependencies": {
|
|
84
|
+
"@types/node": "^22.20.0",
|
|
85
|
+
"typescript": "~5.9.3"
|
|
86
|
+
}
|
|
87
|
+
}
|