@bcts/uniform-resources 1.0.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +48 -0
- package/README.md +17 -0
- package/dist/index.cjs +8373 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +761 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +761 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.iife.js +8377 -0
- package/dist/index.iife.js.map +1 -0
- package/dist/index.mjs +8336 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +75 -0
- package/src/error.ts +88 -0
- package/src/fountain.ts +397 -0
- package/src/index.ts +61 -0
- package/src/multipart-decoder.ts +204 -0
- package/src/multipart-encoder.ts +166 -0
- package/src/ur-codable.ts +48 -0
- package/src/ur-decodable.ts +56 -0
- package/src/ur-encodable.ts +47 -0
- package/src/ur-type.ts +87 -0
- package/src/ur.ts +215 -0
- package/src/utils.ts +802 -0
- package/src/xoshiro.ts +180 -0
package/src/ur.ts
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import type { Cbor } from "@bcts/dcbor";
|
|
2
|
+
import { decodeCbor } from "@bcts/dcbor";
|
|
3
|
+
import { InvalidSchemeError, TypeUnspecifiedError, UnexpectedTypeError, URError } from "./error.js";
|
|
4
|
+
import { URType } from "./ur-type.js";
|
|
5
|
+
import { encodeBytewords, decodeBytewords, BytewordsStyle } from "./utils.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A Uniform Resource (UR) is a URI-encoded CBOR object.
|
|
9
|
+
*
|
|
10
|
+
* URs are defined in [BCR-2020-005: Uniform Resources](https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-005-ur.md).
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { UR } from '@bcts/uniform-resources';
|
|
15
|
+
* import { CBOR } from '@bcts/dcbor';
|
|
16
|
+
*
|
|
17
|
+
* // Create a UR from a CBOR object
|
|
18
|
+
* const cbor = CBOR.fromArray([1, 2, 3]);
|
|
19
|
+
* const ur = UR.new('test', cbor);
|
|
20
|
+
*
|
|
21
|
+
* // Encode to string
|
|
22
|
+
* const urString = ur.string();
|
|
23
|
+
* console.log(urString); // "ur:test/..."
|
|
24
|
+
*
|
|
25
|
+
* // Decode from string
|
|
26
|
+
* const decodedUR = UR.fromURString(urString);
|
|
27
|
+
* console.log(decodedUR.urTypeStr()); // "test"
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export class UR {
|
|
31
|
+
private readonly _urType: URType;
|
|
32
|
+
private readonly _cbor: Cbor;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Creates a new UR from the provided type and CBOR data.
|
|
36
|
+
*
|
|
37
|
+
* @param urType - The UR type (will be validated)
|
|
38
|
+
* @param cbor - The CBOR data to encode
|
|
39
|
+
* @throws {InvalidTypeError} If the type is invalid
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* const ur = UR.new('bytes', CBOR.fromString('hello'));
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
static new(urType: string | URType, cbor: Cbor): UR {
|
|
47
|
+
const type = typeof urType === "string" ? new URType(urType) : urType;
|
|
48
|
+
return new UR(type, cbor);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Creates a new UR from a UR string.
|
|
53
|
+
*
|
|
54
|
+
* @param urString - A UR string like "ur:test/..."
|
|
55
|
+
* @throws {InvalidSchemeError} If the string doesn't start with "ur:"
|
|
56
|
+
* @throws {TypeUnspecifiedError} If no type is specified
|
|
57
|
+
* @throws {NotSinglePartError} If the UR is multi-part
|
|
58
|
+
* @throws {URError} If decoding fails
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```typescript
|
|
62
|
+
* const ur = UR.fromURString('ur:test/lsadaoaxjygonesw');
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
static fromURString(urString: string): UR {
|
|
66
|
+
// Decode the UR string to get the type and CBOR data
|
|
67
|
+
const decodedUR = URStringDecoder.decode(urString);
|
|
68
|
+
if (decodedUR === null || decodedUR === undefined) {
|
|
69
|
+
throw new URError("Failed to decode UR string");
|
|
70
|
+
}
|
|
71
|
+
const { urType, cbor } = decodedUR;
|
|
72
|
+
return new UR(new URType(urType), cbor);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private constructor(urType: URType, cbor: Cbor) {
|
|
76
|
+
this._urType = urType;
|
|
77
|
+
this._cbor = cbor;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Returns the UR type.
|
|
82
|
+
*/
|
|
83
|
+
urType(): URType {
|
|
84
|
+
return this._urType;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Returns the UR type as a string.
|
|
89
|
+
*/
|
|
90
|
+
urTypeStr(): string {
|
|
91
|
+
return this._urType.string();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Returns the CBOR data.
|
|
96
|
+
*/
|
|
97
|
+
cbor(): Cbor {
|
|
98
|
+
return this._cbor;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Returns the string representation of the UR (lowercase, suitable for display).
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```typescript
|
|
106
|
+
* const ur = UR.new('test', CBOR.fromArray([1, 2, 3]));
|
|
107
|
+
* console.log(ur.string()); // "ur:test/lsadaoaxjygonesw"
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
string(): string {
|
|
111
|
+
const cborData = this._cbor.toData();
|
|
112
|
+
return URStringEncoder.encode(this._urType.string(), cborData);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Returns the QR string representation (uppercase, most efficient for QR codes).
|
|
117
|
+
*/
|
|
118
|
+
qrString(): string {
|
|
119
|
+
return this.string().toUpperCase();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Returns the QR data as bytes (uppercase UR string as UTF-8).
|
|
124
|
+
*/
|
|
125
|
+
qrData(): Uint8Array {
|
|
126
|
+
// Use a helper to convert string to bytes across platforms
|
|
127
|
+
const str = this.qrString();
|
|
128
|
+
const bytes = new Uint8Array(str.length);
|
|
129
|
+
for (let i = 0; i < str.length; i++) {
|
|
130
|
+
bytes[i] = str.charCodeAt(i);
|
|
131
|
+
}
|
|
132
|
+
return bytes;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Checks if the UR type matches the expected type.
|
|
137
|
+
*
|
|
138
|
+
* @param expectedType - The expected type
|
|
139
|
+
* @throws {UnexpectedTypeError} If the types don't match
|
|
140
|
+
*/
|
|
141
|
+
checkType(expectedType: string | URType): void {
|
|
142
|
+
const expected = typeof expectedType === "string" ? new URType(expectedType) : expectedType;
|
|
143
|
+
if (!this._urType.equals(expected)) {
|
|
144
|
+
throw new UnexpectedTypeError(expected.string(), this._urType.string());
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Returns the string representation.
|
|
150
|
+
*/
|
|
151
|
+
toString(): string {
|
|
152
|
+
return this.string();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Checks equality with another UR.
|
|
157
|
+
*/
|
|
158
|
+
equals(other: UR): boolean {
|
|
159
|
+
return (
|
|
160
|
+
this._urType.equals(other._urType) &&
|
|
161
|
+
this._cbor.toData().toString() === other._cbor.toData().toString()
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Encodes a UR string using Bytewords minimal encoding.
|
|
168
|
+
* This handles single-part URs according to BCR-2020-005.
|
|
169
|
+
*/
|
|
170
|
+
class URStringEncoder {
|
|
171
|
+
static encode(urType: string, cborData: Uint8Array): string {
|
|
172
|
+
// Encode CBOR data using bytewords minimal style (with CRC32 checksum)
|
|
173
|
+
const encoded = encodeBytewords(cborData, BytewordsStyle.Minimal);
|
|
174
|
+
return `ur:${urType}/${encoded}`;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Decodes a UR string back to its components.
|
|
180
|
+
*/
|
|
181
|
+
class URStringDecoder {
|
|
182
|
+
static decode(urString: string): { urType: string; cbor: Cbor } | null {
|
|
183
|
+
const lowercased = urString.toLowerCase();
|
|
184
|
+
|
|
185
|
+
// Check scheme
|
|
186
|
+
if (!lowercased.startsWith("ur:")) {
|
|
187
|
+
throw new InvalidSchemeError();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Strip scheme
|
|
191
|
+
const afterScheme = lowercased.substring(3);
|
|
192
|
+
|
|
193
|
+
// Split into type and data
|
|
194
|
+
const [urType, ...dataParts] = afterScheme.split("/");
|
|
195
|
+
|
|
196
|
+
if (urType === "" || urType === undefined) {
|
|
197
|
+
throw new TypeUnspecifiedError();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const data = dataParts.join("/");
|
|
201
|
+
if (data === "" || data === undefined) {
|
|
202
|
+
throw new TypeUnspecifiedError();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
// Decode the bytewords-encoded data (validates CRC32 checksum)
|
|
207
|
+
const cborData = decodeBytewords(data, BytewordsStyle.Minimal);
|
|
208
|
+
const cbor = decodeCbor(cborData);
|
|
209
|
+
return { urType, cbor };
|
|
210
|
+
} catch (error) {
|
|
211
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
212
|
+
throw new URError(`Failed to decode UR: ${errorMessage}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|