-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.ts
151 lines (130 loc) · 4.89 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import {
bufferSourceToUint8Array, bytesToHexArray, concatUint8Arrays, hexStringToBytes,
} from "https://ghuc.cc/qwtel/typed-array-utils/index.ts";
function _bytesToUUIDString(uint8Array: Uint8Array) {
const hexArray = bytesToHexArray(uint8Array);
hexArray.splice(4, 0, '-');
hexArray.splice(7, 0, '-');
hexArray.splice(10, 0, '-');
hexArray.splice(13, 0, '-');
return hexArray.join('');
}
function _v4() {
const uuid = crypto.getRandomValues(new Uint8Array(16));
// Per 4.4, set bits for version and `clock_seq_hi_and_reserved`
uuid[6] = (uuid[6] & 0x0f) | 0x40;
uuid[8] = (uuid[8] & 0x3f) | 0x80;
return uuid.buffer;
}
function _fromString(str: string) {
const hex = str.replace(/[^0-9a-f]/gi, '').slice(0, 32);
if (hex.length < 32) throw Error('UUID too short');
return hexStringToBytes(hex).buffer;
}
function stringToBytes(str: string) {
str = unescape(encodeURIComponent(str)); // UTF8 escape
return new TextEncoder().encode(str);
}
async function _v5(value: string | BufferSource, namespace: string | UUID) {
const valueBytes = typeof value === 'string'
? stringToBytes(value)
: bufferSourceToUint8Array(value);
const namespaceUUID = typeof namespace === 'string'
? new UUID(namespace)
: namespace
const hashBytes = new Uint8Array(
await crypto.subtle.digest('SHA-1', concatUint8Arrays(namespaceUUID, valueBytes))
);
hashBytes[6] = (hashBytes[6] & 0x0f) | 0x50; // version
hashBytes[8] = (hashBytes[8] & 0x3f) | 0x80;
return hashBytes.subarray(0, 16);
}
/**
* A better UUID class for JavaScript.
*
* UUID are represented as bytes (`Uint8Array`) and converted to strings on-demand.
*
* This class implements `toString` and `toJSON` for better language integration,
* as well as inspection for node and Deno for a better development experience.
*
* For the most part, `UUID` can be used where UUID strings are used,
* except for equality checks. For those cases, `UUID` provides quick access
* to the string representations via the `id` field.
*/
export class UUID extends Uint8Array {
/**
* Generate a new UUID version 4 (random).
*
* __Note that `crypto.getRandomValues` needs to be available in the global JS object!__
*/
static v4(): UUID {
return new UUID(_v4());
}
/**
* Generated a new UUID version 5 (hashed)
*
* __Note that `crypto.subtle` needs to be available in the global JS object (Not the case on non-HTTPS sites)!__
*
* @param value
* @param namespace
*/
static async v5(value: string | BufferSource, namespace: string | UUID): Promise<UUID> {
return new UUID(await _v5(value, namespace));
}
/**
* Generate a new UUID version 4 (random).
* __Note that `crypto.getRandomValues` needs to be available in the global JS object!__
*/
constructor();
/** Creates a new UUID object from the provided string, which must be a valid UUID string. */
constructor(value: string);
/** Creates a copy of the provided UUID */
constructor(value: UUID);
/** Create a UUID from the provided iterable, where every value will be interpreted as a unsigned 8 bit integer. */
constructor(value: Iterable<number>);
/** Create a new UUID from the provided array-like structure. */
constructor(value: ArrayLike<number> | ArrayBufferLike);
/** Creates a UUID from the array buffer using 16 bytes started from the provided offset. */
constructor(value: ArrayBufferLike, byteOffset: number);
// deno-lint-ignore constructor-super
constructor(value?: string | UUID | Iterable<number> | ArrayLike<number> | ArrayBufferLike, byteOffset?: number) {
if (value == null) {
super(_v4());
} else if (typeof value === 'string') {
super(_fromString(value));
} else if (value instanceof UUID) {
super(value.buffer);
} else {
const u8 = value instanceof ArrayBuffer || value instanceof SharedArrayBuffer
? new Uint8Array(value, byteOffset ?? 0, 16)
: 'length' in value ? new Uint8Array(value) : new Uint8Array(value);
if (u8.length < 16) throw Error('UUID too short');
super(u8.subarray(0, 16));
}
}
/**
* Quick access to the string representation for easier comparison.
* @example if (myUUID.id === otherUUID.id) { ... }
*/
get id(): string {
return _bytesToUUIDString(this);
}
/**
* Quick access to the UUID string representation for easier comparison.
* @example if (myUUID.uuid === otherUUID.uuid) { ... }
*/
get uuid(): string {
return _bytesToUUIDString(this);
}
toString(): string {
return _bytesToUUIDString(this);
}
toJSON(): string {
return _bytesToUUIDString(this);
}
// We don't operations like `map`, `subarray`, etc. to preserve the UUID class status
static get [Symbol.species]() { return Uint8Array }
// Custom inspects..
[Symbol.for('nodejs.util.inspect.custom')]() { return `UUID [ ${this.uuid} ]` }
[Symbol.for('Deno.customInspect')]() { return `UUID [ ${this.uuid} ]` }
}