@bcts/uniform-resources 1.0.0-alpha.21 → 1.0.0-alpha.23
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/README.md +1 -1
- package/dist/index.cjs +136 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +49 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +49 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.iife.js +194 -102
- package/dist/index.iife.js.map +1 -1
- package/dist/index.mjs +131 -43
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -10
- package/src/index.ts +5 -0
- package/src/utils.ts +135 -17
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { MajorType, cbor, decodeCbor } from "@bcts/dcbor";
|
|
2
2
|
import { sha256 } from "@bcts/crypto";
|
|
3
|
-
|
|
4
3
|
//#region src/error.ts
|
|
5
4
|
/**
|
|
6
5
|
* Copyright © 2023-2026 Blockchain Commons, LLC
|
|
@@ -94,7 +93,6 @@ var URDecodeError = class extends URError {
|
|
|
94
93
|
function isError(result) {
|
|
95
94
|
return result instanceof Error;
|
|
96
95
|
}
|
|
97
|
-
|
|
98
96
|
//#endregion
|
|
99
97
|
//#region src/utils.ts
|
|
100
98
|
/**
|
|
@@ -652,36 +650,102 @@ const BYTEMOJIS = [
|
|
|
652
650
|
"🐳"
|
|
653
651
|
];
|
|
654
652
|
/**
|
|
655
|
-
* Encodes
|
|
653
|
+
* Encodes an arbitrary byte slice as a string of space-separated bytewords.
|
|
654
|
+
*
|
|
655
|
+
* Mirrors `bytewords::encode_to_words` in `bc-ur-rust` (≥ v0.19.1). Does not
|
|
656
|
+
* add a CRC32 checksum — use {@link encodeBytewords} for UR-style encoding.
|
|
656
657
|
*/
|
|
657
|
-
function
|
|
658
|
-
if (data.length !== 4) throw new Error("Identifier data must be exactly 4 bytes");
|
|
658
|
+
function encodeToWords(data) {
|
|
659
659
|
const words = [];
|
|
660
|
-
for (
|
|
661
|
-
const byte = data[i];
|
|
662
|
-
if (byte === void 0) throw new Error("Invalid byte");
|
|
660
|
+
for (const byte of data) {
|
|
663
661
|
const word = BYTEWORDS[byte];
|
|
664
|
-
if (word ===
|
|
662
|
+
if (word === void 0) throw new Error(`Invalid byte value: ${byte}`);
|
|
665
663
|
words.push(word);
|
|
666
664
|
}
|
|
667
665
|
return words.join(" ");
|
|
668
666
|
}
|
|
669
667
|
/**
|
|
670
|
-
* Encodes
|
|
668
|
+
* Encodes an arbitrary byte slice as a string of space-separated bytemojis.
|
|
669
|
+
*
|
|
670
|
+
* Mirrors `bytewords::encode_to_bytemojis` in `bc-ur-rust` (≥ v0.19.1).
|
|
671
671
|
*/
|
|
672
|
-
function
|
|
673
|
-
if (data.length !== 4) throw new Error("Identifier data must be exactly 4 bytes");
|
|
672
|
+
function encodeToBytemojis(data) {
|
|
674
673
|
const emojis = [];
|
|
675
|
-
for (
|
|
676
|
-
const byte = data[i];
|
|
677
|
-
if (byte === void 0) throw new Error("Invalid byte");
|
|
674
|
+
for (const byte of data) {
|
|
678
675
|
const emoji = BYTEMOJIS[byte];
|
|
679
|
-
if (emoji ===
|
|
676
|
+
if (emoji === void 0) throw new Error(`Invalid byte value: ${byte}`);
|
|
680
677
|
emojis.push(emoji);
|
|
681
678
|
}
|
|
682
679
|
return emojis.join(" ");
|
|
683
680
|
}
|
|
684
681
|
/**
|
|
682
|
+
* Encodes an arbitrary byte slice as minimal bytewords (first + last letter of
|
|
683
|
+
* each word, concatenated with no separator).
|
|
684
|
+
*
|
|
685
|
+
* Mirrors `bytewords::encode_to_minimal_bytewords` in `bc-ur-rust`
|
|
686
|
+
* (≥ v0.19.1). Does not add a CRC32 checksum.
|
|
687
|
+
*/
|
|
688
|
+
function encodeToMinimalBytewords(data) {
|
|
689
|
+
let out = "";
|
|
690
|
+
for (const byte of data) {
|
|
691
|
+
const word = BYTEWORDS[byte];
|
|
692
|
+
if (word === void 0) throw new Error(`Invalid byte value: ${byte}`);
|
|
693
|
+
out += word[0] + word[word.length - 1];
|
|
694
|
+
}
|
|
695
|
+
return out;
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Encodes a 4-byte slice as a string of bytewords for identification.
|
|
699
|
+
*
|
|
700
|
+
* Thin wrapper over {@link encodeToWords} that enforces the 4-byte length
|
|
701
|
+
* contract historically used by `bc-ur-rust`'s `bytewords::identifier`.
|
|
702
|
+
*/
|
|
703
|
+
function encodeBytewordsIdentifier(data) {
|
|
704
|
+
if (data.length !== 4) throw new Error("Identifier data must be exactly 4 bytes");
|
|
705
|
+
return encodeToWords(data);
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* Encodes a 4-byte slice as a string of bytemojis for identification.
|
|
709
|
+
*
|
|
710
|
+
* Thin wrapper over {@link encodeToBytemojis} that enforces the 4-byte length
|
|
711
|
+
* contract historically used by `bc-ur-rust`'s `bytewords::bytemoji_identifier`.
|
|
712
|
+
*/
|
|
713
|
+
function encodeBytemojisIdentifier(data) {
|
|
714
|
+
if (data.length !== 4) throw new Error("Identifier data must be exactly 4 bytes");
|
|
715
|
+
return encodeToBytemojis(data);
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Returns `true` if `emoji` is one of the 256 bytemojis.
|
|
719
|
+
*
|
|
720
|
+
* Mirrors `bytewords::is_valid_bytemoji` in `bc-ur-rust` (≥ v0.19.1).
|
|
721
|
+
*/
|
|
722
|
+
function isValidBytemoji(emoji) {
|
|
723
|
+
return BYTEMOJI_SET.has(emoji);
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Canonicalises a byteword token (2–4 ASCII letters, case-insensitive) to its
|
|
727
|
+
* full 4-letter lowercase form. Returns `undefined` if the token is not a
|
|
728
|
+
* valid byteword or any of its short forms.
|
|
729
|
+
*
|
|
730
|
+
* Mirrors `bytewords::canonicalize_byteword` in `bc-ur-rust` (≥ v0.19.1).
|
|
731
|
+
*
|
|
732
|
+
* - 2-letter tokens are matched against the first + last letter of each
|
|
733
|
+
* byteword (identical to the minimal bytewords encoding).
|
|
734
|
+
* - 3-letter tokens are matched against the first 3 and the last 3 letters of
|
|
735
|
+
* each byteword; if both match different entries, the first-3 match wins
|
|
736
|
+
* (matching rust's `or_else` priority).
|
|
737
|
+
* - 4-letter tokens must exactly match a full byteword (after lower-casing).
|
|
738
|
+
*/
|
|
739
|
+
function canonicalizeByteword(token) {
|
|
740
|
+
const lower = token.toLowerCase();
|
|
741
|
+
switch (lower.length) {
|
|
742
|
+
case 4: return BYTEWORDS_MAP.has(lower) ? lower : void 0;
|
|
743
|
+
case 2: return BYTEWORD_FIRST_LAST_MAP.get(lower);
|
|
744
|
+
case 3: return BYTEWORD_FIRST_THREE_MAP.get(lower) ?? BYTEWORD_LAST_THREE_MAP.get(lower);
|
|
745
|
+
default: return;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
685
749
|
* Bytewords encoding style.
|
|
686
750
|
*/
|
|
687
751
|
let BytewordsStyle = /* @__PURE__ */ function(BytewordsStyle) {
|
|
@@ -706,6 +770,38 @@ function createMinimalBytewordsMap() {
|
|
|
706
770
|
}
|
|
707
771
|
const MINIMAL_BYTEWORDS_MAP = createMinimalBytewordsMap();
|
|
708
772
|
/**
|
|
773
|
+
* Set of all 256 bytemojis for fast membership testing. Backs
|
|
774
|
+
* {@link isValidBytemoji}.
|
|
775
|
+
*/
|
|
776
|
+
const BYTEMOJI_SET = new Set(BYTEMOJIS);
|
|
777
|
+
/**
|
|
778
|
+
* Lookup from a 2-letter (first+last) byteword short-form to its full
|
|
779
|
+
* lowercase 4-letter form. Backs {@link canonicalizeByteword}.
|
|
780
|
+
*/
|
|
781
|
+
const BYTEWORD_FIRST_LAST_MAP = (() => {
|
|
782
|
+
const map = /* @__PURE__ */ new Map();
|
|
783
|
+
for (const word of BYTEWORDS) map.set(word[0] + word[word.length - 1], word);
|
|
784
|
+
return map;
|
|
785
|
+
})();
|
|
786
|
+
/**
|
|
787
|
+
* Lookup from the first 3 letters of a byteword to its full lowercase 4-letter
|
|
788
|
+
* form. Backs {@link canonicalizeByteword}.
|
|
789
|
+
*/
|
|
790
|
+
const BYTEWORD_FIRST_THREE_MAP = (() => {
|
|
791
|
+
const map = /* @__PURE__ */ new Map();
|
|
792
|
+
for (const word of BYTEWORDS) map.set(word.slice(0, 3), word);
|
|
793
|
+
return map;
|
|
794
|
+
})();
|
|
795
|
+
/**
|
|
796
|
+
* Lookup from the last 3 letters of a byteword to its full lowercase 4-letter
|
|
797
|
+
* form. Backs {@link canonicalizeByteword}.
|
|
798
|
+
*/
|
|
799
|
+
const BYTEWORD_LAST_THREE_MAP = (() => {
|
|
800
|
+
const map = /* @__PURE__ */ new Map();
|
|
801
|
+
for (const word of BYTEWORDS) map.set(word.slice(1), word);
|
|
802
|
+
return map;
|
|
803
|
+
})();
|
|
804
|
+
/**
|
|
709
805
|
* CRC32 lookup table (IEEE polynomial).
|
|
710
806
|
*/
|
|
711
807
|
const CRC32_TABLE = (() => {
|
|
@@ -740,7 +836,7 @@ function uint32ToBytes(value) {
|
|
|
740
836
|
* Encode data as bytewords with the specified style.
|
|
741
837
|
* Includes CRC32 checksum.
|
|
742
838
|
*/
|
|
743
|
-
function encodeBytewords(data, style =
|
|
839
|
+
function encodeBytewords(data, style = "minimal") {
|
|
744
840
|
const checksumBytes = uint32ToBytes(crc32(data));
|
|
745
841
|
const dataWithChecksum = new Uint8Array(data.length + 4);
|
|
746
842
|
dataWithChecksum.set(data);
|
|
@@ -750,46 +846,46 @@ function encodeBytewords(data, style = BytewordsStyle.Minimal) {
|
|
|
750
846
|
const word = BYTEWORDS[byte];
|
|
751
847
|
if (word === void 0) throw new Error(`Invalid byte value: ${byte}`);
|
|
752
848
|
switch (style) {
|
|
753
|
-
case
|
|
849
|
+
case "standard":
|
|
754
850
|
words.push(word);
|
|
755
851
|
break;
|
|
756
|
-
case
|
|
852
|
+
case "uri":
|
|
757
853
|
words.push(word);
|
|
758
854
|
break;
|
|
759
|
-
case
|
|
855
|
+
case "minimal":
|
|
760
856
|
words.push(word[0] + word[3]);
|
|
761
857
|
break;
|
|
762
858
|
}
|
|
763
859
|
}
|
|
764
860
|
switch (style) {
|
|
765
|
-
case
|
|
766
|
-
case
|
|
767
|
-
case
|
|
861
|
+
case "standard": return words.join(" ");
|
|
862
|
+
case "uri": return words.join("-");
|
|
863
|
+
case "minimal": return words.join("");
|
|
768
864
|
}
|
|
769
865
|
}
|
|
770
866
|
/**
|
|
771
867
|
* Decode bytewords string back to data.
|
|
772
868
|
* Validates and removes CRC32 checksum.
|
|
773
869
|
*/
|
|
774
|
-
function decodeBytewords(encoded, style =
|
|
870
|
+
function decodeBytewords(encoded, style = "minimal") {
|
|
775
871
|
const lowercased = encoded.toLowerCase();
|
|
776
872
|
let bytes;
|
|
777
873
|
switch (style) {
|
|
778
|
-
case
|
|
874
|
+
case "standard":
|
|
779
875
|
bytes = lowercased.split(" ").map((word) => {
|
|
780
876
|
const index = BYTEWORDS_MAP.get(word);
|
|
781
877
|
if (index === void 0) throw new Error(`Invalid byteword: ${word}`);
|
|
782
878
|
return index;
|
|
783
879
|
});
|
|
784
880
|
break;
|
|
785
|
-
case
|
|
881
|
+
case "uri":
|
|
786
882
|
bytes = lowercased.split("-").map((word) => {
|
|
787
883
|
const index = BYTEWORDS_MAP.get(word);
|
|
788
884
|
if (index === void 0) throw new Error(`Invalid byteword: ${word}`);
|
|
789
885
|
return index;
|
|
790
886
|
});
|
|
791
887
|
break;
|
|
792
|
-
case
|
|
888
|
+
case "minimal":
|
|
793
889
|
if (lowercased.length % 2 !== 0) throw new Error("Invalid minimal bytewords length");
|
|
794
890
|
bytes = [];
|
|
795
891
|
for (let i = 0; i < lowercased.length; i += 2) {
|
|
@@ -809,7 +905,6 @@ function decodeBytewords(encoded, style = BytewordsStyle.Minimal) {
|
|
|
809
905
|
if (expectedChecksum !== actualChecksum) throw new Error(`Bytewords checksum mismatch: expected ${expectedChecksum.toString(16)}, got ${actualChecksum.toString(16)}`);
|
|
810
906
|
return data;
|
|
811
907
|
}
|
|
812
|
-
|
|
813
908
|
//#endregion
|
|
814
909
|
//#region src/ur-type.ts
|
|
815
910
|
/**
|
|
@@ -893,7 +988,6 @@ var URType = class URType {
|
|
|
893
988
|
}
|
|
894
989
|
}
|
|
895
990
|
};
|
|
896
|
-
|
|
897
991
|
//#endregion
|
|
898
992
|
//#region src/ur.ts
|
|
899
993
|
/**
|
|
@@ -1036,7 +1130,7 @@ var UR = class UR {
|
|
|
1036
1130
|
*/
|
|
1037
1131
|
var URStringEncoder = class {
|
|
1038
1132
|
static encode(urType, cborData) {
|
|
1039
|
-
return `ur:${urType}/${encodeBytewords(cborData,
|
|
1133
|
+
return `ur:${urType}/${encodeBytewords(cborData, "minimal")}`;
|
|
1040
1134
|
}
|
|
1041
1135
|
};
|
|
1042
1136
|
/**
|
|
@@ -1053,14 +1147,13 @@ var URStringDecoder = class {
|
|
|
1053
1147
|
try {
|
|
1054
1148
|
return {
|
|
1055
1149
|
urType,
|
|
1056
|
-
cbor: decodeCbor(decodeBytewords(data,
|
|
1150
|
+
cbor: decodeCbor(decodeBytewords(data, "minimal"))
|
|
1057
1151
|
};
|
|
1058
1152
|
} catch (error) {
|
|
1059
1153
|
throw new URError(`Failed to decode UR: ${error instanceof Error ? error.message : String(error)}`);
|
|
1060
1154
|
}
|
|
1061
1155
|
}
|
|
1062
1156
|
};
|
|
1063
|
-
|
|
1064
1157
|
//#endregion
|
|
1065
1158
|
//#region src/ur-encodable.ts
|
|
1066
1159
|
/**
|
|
@@ -1069,7 +1162,6 @@ var URStringDecoder = class {
|
|
|
1069
1162
|
function isUREncodable(obj) {
|
|
1070
1163
|
return typeof obj === "object" && obj !== null && "ur" in obj && "urString" in obj && typeof obj["ur"] === "function" && typeof obj["urString"] === "function";
|
|
1071
1164
|
}
|
|
1072
|
-
|
|
1073
1165
|
//#endregion
|
|
1074
1166
|
//#region src/ur-decodable.ts
|
|
1075
1167
|
/**
|
|
@@ -1078,7 +1170,6 @@ function isUREncodable(obj) {
|
|
|
1078
1170
|
function isURDecodable(obj) {
|
|
1079
1171
|
return typeof obj === "object" && obj !== null && "fromUR" in obj && typeof obj["fromUR"] === "function";
|
|
1080
1172
|
}
|
|
1081
|
-
|
|
1082
1173
|
//#endregion
|
|
1083
1174
|
//#region src/ur-codable.ts
|
|
1084
1175
|
/**
|
|
@@ -1087,7 +1178,6 @@ function isURDecodable(obj) {
|
|
|
1087
1178
|
function isURCodable(obj) {
|
|
1088
1179
|
return typeof obj === "object" && obj !== null && "ur" in obj && "urString" in obj && "fromUR" in obj && typeof obj["ur"] === "function" && typeof obj["urString"] === "function" && typeof obj["fromUR"] === "function";
|
|
1089
1180
|
}
|
|
1090
|
-
|
|
1091
1181
|
//#endregion
|
|
1092
1182
|
//#region src/xoshiro.ts
|
|
1093
1183
|
/**
|
|
@@ -1299,7 +1389,6 @@ function createSeed(checksum, seqNum) {
|
|
|
1299
1389
|
seed8[7] = checksum & 255;
|
|
1300
1390
|
return sha256(seed8);
|
|
1301
1391
|
}
|
|
1302
|
-
|
|
1303
1392
|
//#endregion
|
|
1304
1393
|
//#region src/fountain.ts
|
|
1305
1394
|
/**
|
|
@@ -1552,7 +1641,6 @@ var FountainDecoder = class {
|
|
|
1552
1641
|
this.mixedParts.clear();
|
|
1553
1642
|
}
|
|
1554
1643
|
};
|
|
1555
|
-
|
|
1556
1644
|
//#endregion
|
|
1557
1645
|
//#region src/multipart-encoder.ts
|
|
1558
1646
|
/**
|
|
@@ -1601,7 +1689,8 @@ var MultipartEncoder = class {
|
|
|
1601
1689
|
constructor(ur, maxFragmentLen) {
|
|
1602
1690
|
if (maxFragmentLen < 1) throw new URError("Max fragment length must be at least 1");
|
|
1603
1691
|
this._ur = ur;
|
|
1604
|
-
|
|
1692
|
+
const cborData = ur.cbor().toData();
|
|
1693
|
+
this._fountainEncoder = new FountainEncoder(cborData, maxFragmentLen);
|
|
1605
1694
|
}
|
|
1606
1695
|
/**
|
|
1607
1696
|
* Gets the next part of the encoding.
|
|
@@ -1627,7 +1716,7 @@ var MultipartEncoder = class {
|
|
|
1627
1716
|
*/
|
|
1628
1717
|
_encodePart(part) {
|
|
1629
1718
|
if (part.seqLen === 1) return this._ur.string();
|
|
1630
|
-
const encoded = encodeBytewords(this._encodePartData(part),
|
|
1719
|
+
const encoded = encodeBytewords(this._encodePartData(part), "minimal");
|
|
1631
1720
|
return `ur:${this._ur.urTypeStr()}/${part.seqNum}-${part.seqLen}/${encoded}`;
|
|
1632
1721
|
}
|
|
1633
1722
|
/**
|
|
@@ -1659,7 +1748,6 @@ var MultipartEncoder = class {
|
|
|
1659
1748
|
return this._fountainEncoder.seqLen;
|
|
1660
1749
|
}
|
|
1661
1750
|
};
|
|
1662
|
-
|
|
1663
1751
|
//#endregion
|
|
1664
1752
|
//#region src/multipart-decoder.ts
|
|
1665
1753
|
/**
|
|
@@ -1748,7 +1836,7 @@ var MultipartDecoder = class {
|
|
|
1748
1836
|
* The multipart body is a CBOR array: [seqNum, seqLen, messageLen, checksum, data]
|
|
1749
1837
|
*/
|
|
1750
1838
|
_decodeFountainPart(partInfo) {
|
|
1751
|
-
const decoded = decodeCbor(decodeBytewords(partInfo.encodedData,
|
|
1839
|
+
const decoded = decodeCbor(decodeBytewords(partInfo.encodedData, "minimal"));
|
|
1752
1840
|
if (decoded.type !== MajorType.Array) throw new URError("Invalid multipart data: expected CBOR array");
|
|
1753
1841
|
const items = decoded.value;
|
|
1754
1842
|
if (items.length !== 5) throw new URError(`Invalid multipart data: expected 5 elements, got ${items.length}`);
|
|
@@ -1781,7 +1869,7 @@ var MultipartDecoder = class {
|
|
|
1781
1869
|
return this._decodedMessage;
|
|
1782
1870
|
}
|
|
1783
1871
|
};
|
|
1784
|
-
|
|
1785
1872
|
//#endregion
|
|
1786
|
-
export { BYTEMOJIS, BYTEWORDS, BytewordsError, BytewordsStyle, CBORError, InvalidSchemeError, InvalidTypeError, MultipartDecoder, MultipartEncoder, NotSinglePartError, TypeUnspecifiedError, UR, URDecodeError, URError, URType, UnexpectedTypeError, decodeBytewords, encodeBytemojisIdentifier, encodeBytewords, encodeBytewordsIdentifier, isError, isURCodable, isURDecodable, isUREncodable };
|
|
1873
|
+
export { BYTEMOJIS, BYTEWORDS, BytewordsError, BytewordsStyle, CBORError, InvalidSchemeError, InvalidTypeError, MultipartDecoder, MultipartEncoder, NotSinglePartError, TypeUnspecifiedError, UR, URDecodeError, URError, URType, UnexpectedTypeError, canonicalizeByteword, decodeBytewords, encodeBytemojisIdentifier, encodeBytewords, encodeBytewordsIdentifier, encodeToBytemojis, encodeToMinimalBytewords, encodeToWords, isError, isURCodable, isURDecodable, isUREncodable, isValidBytemoji };
|
|
1874
|
+
|
|
1787
1875
|
//# sourceMappingURL=index.mjs.map
|