glow.ts 1.1.2 → 1.1.3
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/dist/glow.cjs.js +4 -4
- package/dist/glow.cjs.js.map +1 -1
- package/dist/glow.es.js +721 -992
- package/dist/glow.es.js.map +1 -1
- package/package.json +11 -11
package/dist/glow.es.js
CHANGED
|
@@ -1,993 +1,722 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Mutex as
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
1
|
+
import { box as e, hash as t, randomBytes as n, secretbox as r } from "tweetnacl";
|
|
2
|
+
import { Mutex as i } from "async-mutex";
|
|
3
|
+
//#region src/utils/utils.ts
|
|
4
|
+
var a = class {
|
|
5
|
+
static toBase64(e) {
|
|
6
|
+
return typeof btoa < "u" ? btoa(this.decode_latin1(e)) : Buffer.from(this.decode_latin1(e), "latin1").toString("base64");
|
|
7
|
+
}
|
|
8
|
+
static fromBase64(e) {
|
|
9
|
+
return typeof btoa < "u" ? this.encode_latin1(atob(e)) : this.encode_latin1(Buffer.from(e, "base64").toString("latin1"));
|
|
10
|
+
}
|
|
11
|
+
static encode_latin1(e) {
|
|
12
|
+
let t = new Uint8Array(e.length);
|
|
13
|
+
for (let n = 0; n < e.length; n++) {
|
|
14
|
+
let r = e.charCodeAt(n);
|
|
15
|
+
if ((r & 255) !== r) throw {
|
|
16
|
+
message: "Cannot encode string in Latin1",
|
|
17
|
+
str: e
|
|
18
|
+
};
|
|
19
|
+
t[n] = r & 255;
|
|
20
|
+
}
|
|
21
|
+
return t;
|
|
22
|
+
}
|
|
23
|
+
static decode_latin1(e) {
|
|
24
|
+
let t = [];
|
|
25
|
+
for (let n = 0; n < e.length; n++) t.push(String.fromCharCode(e[n]));
|
|
26
|
+
return t.join("");
|
|
27
|
+
}
|
|
28
|
+
static toObject(e) {
|
|
29
|
+
return Array.from(e).reduce((e, [t, n]) => Object.assign(e, { [t]: n }), {});
|
|
30
|
+
}
|
|
31
|
+
}, o = class {
|
|
32
|
+
constructor() {
|
|
33
|
+
this.crypto_secretbox_KEYBYTES = r.keyLength, this.crypto_box_NONCEBYTES = e.nonceLength;
|
|
34
|
+
}
|
|
35
|
+
async crypto_secretbox_random_nonce() {
|
|
36
|
+
return n(r.nonceLength);
|
|
37
|
+
}
|
|
38
|
+
async crypto_secretbox(e, t, n) {
|
|
39
|
+
return r(e, t, n);
|
|
40
|
+
}
|
|
41
|
+
async crypto_secretbox_open(e, t, n) {
|
|
42
|
+
return r.open(e, t, n);
|
|
43
|
+
}
|
|
44
|
+
async crypto_box(t, n, r, i) {
|
|
45
|
+
return e(t, n, r, i);
|
|
46
|
+
}
|
|
47
|
+
async crypto_box_open(t, n, r, i) {
|
|
48
|
+
return e.open(t, n, r, i);
|
|
49
|
+
}
|
|
50
|
+
async crypto_box_random_nonce() {
|
|
51
|
+
return n(e.nonceLength);
|
|
52
|
+
}
|
|
53
|
+
async crypto_box_keypair() {
|
|
54
|
+
let t = e.keyPair();
|
|
55
|
+
return {
|
|
56
|
+
boxPk: t.publicKey,
|
|
57
|
+
boxSk: t.secretKey
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
async crypto_box_keypair_from_raw_sk(t) {
|
|
61
|
+
let n = e.keyPair.fromSecretKey(t);
|
|
62
|
+
return {
|
|
63
|
+
boxPk: n.publicKey,
|
|
64
|
+
boxSk: n.secretKey
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
async crypto_box_keypair_from_seed(n) {
|
|
68
|
+
return this.crypto_box_keypair_from_raw_sk(t(n).subarray(0, e.secretKeyLength));
|
|
69
|
+
}
|
|
70
|
+
async crypto_hash_sha256(e) {
|
|
71
|
+
let t = globalThis.crypto;
|
|
72
|
+
if (!t || !t.subtle) throw Error("Web Crypto API is not available. Please use a modern browser or Node.js 16+.");
|
|
73
|
+
let n = await t.subtle.digest("SHA-256", e);
|
|
74
|
+
return new Uint8Array(n);
|
|
75
|
+
}
|
|
76
|
+
async random_bytes(e) {
|
|
77
|
+
return n(e);
|
|
78
|
+
}
|
|
79
|
+
async encode_latin1(e) {
|
|
80
|
+
return a.encode_latin1(e);
|
|
81
|
+
}
|
|
82
|
+
async decode_latin1(e) {
|
|
83
|
+
return a.decode_latin1(e);
|
|
84
|
+
}
|
|
85
|
+
async encode_utf8(e) {
|
|
86
|
+
return this.encode_latin1(unescape(encodeURIComponent(e)));
|
|
87
|
+
}
|
|
88
|
+
async decode_utf8(e) {
|
|
89
|
+
return decodeURIComponent(escape(await this.decode_latin1(e)));
|
|
90
|
+
}
|
|
91
|
+
async to_hex(e) {
|
|
92
|
+
let t = [];
|
|
93
|
+
for (let n = 0; n < e.length; n++) {
|
|
94
|
+
let r = "0123456789abcdef";
|
|
95
|
+
t.push(r[e[n] >> 4 & 15]), t.push(r[e[n] & 15]);
|
|
96
|
+
}
|
|
97
|
+
return t.join("");
|
|
98
|
+
}
|
|
99
|
+
async from_hex(e) {
|
|
100
|
+
let t = new Uint8Array(e.length / 2);
|
|
101
|
+
for (let n = 0; n < e.length / 2; n++) t[n] = parseInt(e.substr(2 * n, 2), 16);
|
|
102
|
+
return t;
|
|
103
|
+
}
|
|
104
|
+
async h2(e) {
|
|
105
|
+
e instanceof Uint8Array || (e = await this.encode_latin1(e));
|
|
106
|
+
let t = new Uint8Array(64 + e.length);
|
|
107
|
+
return t.fill(0), t.set(e, 64), this.crypto_hash_sha256(await this.crypto_hash_sha256(t));
|
|
108
|
+
}
|
|
109
|
+
}, s = class {
|
|
110
|
+
constructor() {}
|
|
111
|
+
static setDefaultInstance() {
|
|
112
|
+
return this.setInstance(new o());
|
|
113
|
+
}
|
|
114
|
+
static setInstance(e) {
|
|
115
|
+
if (this.driverInstance) throw Error("[NaCl] NaCl driver has been already set, it is supposed to be set only once");
|
|
116
|
+
return this.driverInstance = e, !0;
|
|
117
|
+
}
|
|
118
|
+
static getInstance() {
|
|
119
|
+
if (!this.driverInstance) throw Error("[NaCl] NaCl instance is not yet set");
|
|
120
|
+
return this.driverInstance;
|
|
121
|
+
}
|
|
122
|
+
}, c = class {
|
|
123
|
+
constructor(e) {
|
|
124
|
+
if (this.keys = e, typeof e == "string") {
|
|
125
|
+
let { boxPk: t, boxSk: n } = JSON.parse(e);
|
|
126
|
+
this.keyPair = {
|
|
127
|
+
boxPk: a.fromBase64(t),
|
|
128
|
+
boxSk: a.fromBase64(n)
|
|
129
|
+
};
|
|
130
|
+
} else this.keyPair = e;
|
|
131
|
+
}
|
|
132
|
+
static isEqual(e, t) {
|
|
133
|
+
return e.toString() === t.toString();
|
|
134
|
+
}
|
|
135
|
+
toString() {
|
|
136
|
+
return JSON.stringify({
|
|
137
|
+
boxPk: this.publicKey,
|
|
138
|
+
boxSk: this.privateKey
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
get publicKey() {
|
|
142
|
+
return a.toBase64(this.keyPair.boxPk);
|
|
143
|
+
}
|
|
144
|
+
get privateKey() {
|
|
145
|
+
return a.toBase64(this.keyPair.boxSk);
|
|
146
|
+
}
|
|
147
|
+
}, l = 30 * 1e3, u = {
|
|
148
|
+
NONCE_TAG: "__nc",
|
|
149
|
+
SKEY_TAG: "storage_key",
|
|
150
|
+
STORAGE_ROOT: ".v1.stor.vlt12",
|
|
151
|
+
RELAY_TOKEN_LEN: 32,
|
|
152
|
+
RELAY_TOKEN_TIMEOUT: 300 * 1e3 - l,
|
|
153
|
+
RELAY_SESSION_TIMEOUT: 1200 * 1e3 - l,
|
|
154
|
+
RELAY_AJAX_TIMEOUT: 5 * 1e3
|
|
155
|
+
}, d = class {
|
|
156
|
+
constructor(e = "storage.") {
|
|
157
|
+
this.rootTag = `__glow.${e}`;
|
|
158
|
+
}
|
|
159
|
+
async get(e) {
|
|
160
|
+
return (await this.getMultiple([e]))[0];
|
|
161
|
+
}
|
|
162
|
+
async getMultiple(e) {
|
|
163
|
+
return e.map((e) => localStorage.getItem(this.tag(e)) || null);
|
|
164
|
+
}
|
|
165
|
+
async set(e, t) {
|
|
166
|
+
return this.setMultiple({ [e]: t });
|
|
167
|
+
}
|
|
168
|
+
async setMultiple(e) {
|
|
169
|
+
for (let t in e) {
|
|
170
|
+
let n = e[t];
|
|
171
|
+
n !== null && localStorage.setItem(this.tag(t), n);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async remove(e) {
|
|
175
|
+
this.removeMultiple([e]);
|
|
176
|
+
}
|
|
177
|
+
async removeMultiple(e) {
|
|
178
|
+
e.forEach((e) => localStorage.removeItem(this.tag(e)));
|
|
179
|
+
}
|
|
180
|
+
tag(e) {
|
|
181
|
+
return `${this.rootTag}.${e}`;
|
|
182
|
+
}
|
|
183
|
+
}, f = class e {
|
|
184
|
+
constructor(e, t) {
|
|
185
|
+
let n = s.getInstance();
|
|
186
|
+
this.nacl = n, this.driver = e, this.id = t ? `.${t}${u.STORAGE_ROOT}` : u.STORAGE_ROOT;
|
|
187
|
+
}
|
|
188
|
+
static async new(t) {
|
|
189
|
+
let n = this.getStorageDriver(), r = new e(n, t), i = r.addPrefix(u.SKEY_TAG), o = await n.get(i);
|
|
190
|
+
if (o) {
|
|
191
|
+
let { key: e } = JSON.parse(o);
|
|
192
|
+
r.storageKey = a.fromBase64(e);
|
|
193
|
+
} else r.storageKey = await r.nacl.random_bytes(r.nacl.crypto_secretbox_KEYBYTES), await n.set(i, JSON.stringify({ key: a.toBase64(r.storageKey) }));
|
|
194
|
+
return r;
|
|
195
|
+
}
|
|
196
|
+
static getStorageDriver() {
|
|
197
|
+
if (this.storageDriver) return this.storageDriver;
|
|
198
|
+
throw Error("[CryptoStorage] StorageDriver instance is not yet set");
|
|
199
|
+
}
|
|
200
|
+
static setDefaultStorageDriver() {
|
|
201
|
+
return this.setStorageDriver(new d());
|
|
202
|
+
}
|
|
203
|
+
static setStorageDriver(e) {
|
|
204
|
+
if (this.storageDriver) throw Error("[CryptoStorage] StorageDriver has been already set, it is supposed to be set only once");
|
|
205
|
+
return this.storageDriver = e, !0;
|
|
206
|
+
}
|
|
207
|
+
async save(e, t) {
|
|
208
|
+
if (!this.driver) throw Error("[CryptoStorage] Storage driver is not set");
|
|
209
|
+
if (!this.storageKey) throw Error("[CryptoStorage] Storage key is not set");
|
|
210
|
+
let n = JSON.stringify(t), r = await this.nacl.encode_utf8(n), i = await this.nacl.crypto_secretbox_random_nonce(), o = await this.nacl.crypto_secretbox(r, i, this.storageKey);
|
|
211
|
+
return await this.driver.setMultiple({
|
|
212
|
+
[this.addPrefix(e)]: a.toBase64(o),
|
|
213
|
+
[this.addNonceTag(e)]: a.toBase64(i)
|
|
214
|
+
}), !0;
|
|
215
|
+
}
|
|
216
|
+
async get(e) {
|
|
217
|
+
if (!this.driver) throw Error("[CryptoStorage] Storage driver is not set");
|
|
218
|
+
if (!this.storageKey) throw Error("[CryptoStorage] Storage key is not set");
|
|
219
|
+
let [t, n] = await this.driver.getMultiple([this.addPrefix(e), this.addNonceTag(e)]);
|
|
220
|
+
if (!t || !n) return null;
|
|
221
|
+
let r = a.fromBase64(t), i = a.fromBase64(n), o = await this.nacl.crypto_secretbox_open(r, i, this.storageKey);
|
|
222
|
+
if (o) {
|
|
223
|
+
let e = await this.nacl.decode_utf8(o);
|
|
224
|
+
return JSON.parse(e);
|
|
225
|
+
} else throw Error("[CryptoStorage] crypto_secretbox_open: decryption error");
|
|
226
|
+
}
|
|
227
|
+
async remove(e) {
|
|
228
|
+
if (!this.driver) throw Error("[CryptoStorage] Storage driver is not set");
|
|
229
|
+
return await this.driver.removeMultiple([this.addPrefix(e), this.addNonceTag(e)]), !0;
|
|
230
|
+
}
|
|
231
|
+
async selfDestruct() {
|
|
232
|
+
if (!this.driver) throw Error("[CryptoStorage] Storage driver is not set");
|
|
233
|
+
await this.driver.remove(this.addPrefix(u.SKEY_TAG));
|
|
234
|
+
}
|
|
235
|
+
addPrefix(e) {
|
|
236
|
+
return this.id ? e + this.id : e;
|
|
237
|
+
}
|
|
238
|
+
addNonceTag(e) {
|
|
239
|
+
return this.addPrefix(`${u.NONCE_TAG}.${e}`);
|
|
240
|
+
}
|
|
241
|
+
}, p = "__::commKey::__", m = class e {
|
|
242
|
+
static {
|
|
243
|
+
this.commKeyTag = "comm_key";
|
|
244
|
+
}
|
|
245
|
+
static {
|
|
246
|
+
this.guestRegistryTag = "guest_registry";
|
|
247
|
+
}
|
|
248
|
+
constructor(e, t, n) {
|
|
249
|
+
this.guestKeys = /* @__PURE__ */ new Map(), this.nacl = e, this.storage = t, this.commKey = n;
|
|
250
|
+
}
|
|
251
|
+
static async new(t) {
|
|
252
|
+
let n = s.getInstance(), r = await f.new(t), i = new e(n, r, await e.getCommKeyOrCreate(n, r));
|
|
253
|
+
return await i.loadGuestKeys(), i;
|
|
254
|
+
}
|
|
255
|
+
static async fromBackup(t, n) {
|
|
256
|
+
let r = JSON.parse(n), i = a.fromBase64(r[p]), o = await e.new(t);
|
|
257
|
+
await o.setCommFromSecKey(i);
|
|
258
|
+
for (let [e, t] of Object.entries(r)) e !== p && await o.addGuest(e, t);
|
|
259
|
+
return o;
|
|
260
|
+
}
|
|
261
|
+
getNumberOfGuests() {
|
|
262
|
+
return this.guestKeys.size;
|
|
263
|
+
}
|
|
264
|
+
get guests() {
|
|
265
|
+
return this.guestKeys;
|
|
266
|
+
}
|
|
267
|
+
getPubCommKey() {
|
|
268
|
+
return this.commKey.publicKey;
|
|
269
|
+
}
|
|
270
|
+
getPrivateCommKey() {
|
|
271
|
+
return this.commKey.privateKey;
|
|
272
|
+
}
|
|
273
|
+
async getHpk() {
|
|
274
|
+
let e = await this.nacl.h2(a.fromBase64(this.commKey.publicKey));
|
|
275
|
+
return a.toBase64(e);
|
|
276
|
+
}
|
|
277
|
+
getTagByHpk(e) {
|
|
278
|
+
for (let [t, n] of this.guestKeys) if (n.hpk === e) return t;
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
getGuestKey(e) {
|
|
282
|
+
let t = this.guestKeys.get(e);
|
|
283
|
+
return t ? t.pk : null;
|
|
284
|
+
}
|
|
285
|
+
async backup() {
|
|
286
|
+
let e = { [p]: this.commKey.privateKey };
|
|
287
|
+
if (this.getNumberOfGuests() > 0) for (let [t, n] of this.guestKeys) t && n && (e[t] = n.pk);
|
|
288
|
+
return JSON.stringify(e);
|
|
289
|
+
}
|
|
290
|
+
async setCommFromSeed(t) {
|
|
291
|
+
this.commKey = new c(await this.nacl.crypto_box_keypair_from_seed(t)), await this.storage.save(e.commKeyTag, this.commKey.toString());
|
|
292
|
+
}
|
|
293
|
+
async setCommFromSecKey(t) {
|
|
294
|
+
this.commKey = new c(await this.nacl.crypto_box_keypair_from_raw_sk(t)), await this.storage.save(e.commKeyTag, this.commKey.toString());
|
|
295
|
+
}
|
|
296
|
+
async addGuest(e, t) {
|
|
297
|
+
let n = a.toBase64(await this.nacl.h2(a.fromBase64(t)));
|
|
298
|
+
return this.guestKeys.set(e, {
|
|
299
|
+
pk: t,
|
|
300
|
+
hpk: n
|
|
301
|
+
}), await this.saveGuests(), n;
|
|
302
|
+
}
|
|
303
|
+
async removeGuest(e) {
|
|
304
|
+
return this.guestKeys.has(e) && (this.guestKeys.delete(e), await this.saveGuests()), !0;
|
|
305
|
+
}
|
|
306
|
+
async loadGuestKeys() {
|
|
307
|
+
let t = await this.storage.get(e.guestRegistryTag);
|
|
308
|
+
if (t) if (typeof t == "object") this.guestKeys = new Map(Object.entries(t));
|
|
309
|
+
else throw Error("[Keyring] Guest keys is not an object");
|
|
310
|
+
}
|
|
311
|
+
async saveGuests() {
|
|
312
|
+
await this.storage.save(e.guestRegistryTag, a.toObject(this.guestKeys.entries()));
|
|
313
|
+
}
|
|
314
|
+
static async getCommKeyOrCreate(t, n) {
|
|
315
|
+
let r = await n.get(e.commKeyTag);
|
|
316
|
+
if (r && typeof r == "string") return new c(r);
|
|
317
|
+
{
|
|
318
|
+
let r = new c(await t.crypto_box_keypair());
|
|
319
|
+
return await n.save(e.commKeyTag, r.toString()), r;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
async selfDestruct() {
|
|
323
|
+
await this.storage.remove(e.guestRegistryTag), await this.storage.remove(e.commKeyTag), await this.storage.selfDestruct();
|
|
324
|
+
}
|
|
325
|
+
}, h = class e {
|
|
326
|
+
constructor() {}
|
|
327
|
+
static async encodeMessage(e, t, n, r) {
|
|
328
|
+
let i = s.getInstance(), o = await this.makeNonce(r), c = await i.crypto_box(e, o, t, n);
|
|
329
|
+
return {
|
|
330
|
+
nonce: a.toBase64(o),
|
|
331
|
+
ctext: a.toBase64(c)
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
static async decodeMessage(e, t, n, r) {
|
|
335
|
+
let i = s.getInstance(), a = await i.crypto_box_open(t, e, n, r);
|
|
336
|
+
return a && await i.decode_utf8(a);
|
|
337
|
+
}
|
|
338
|
+
static async encodeMessageSymmetric(t, n) {
|
|
339
|
+
let r = s.getInstance(), i = await e.makeNonce(), o = await r.crypto_secretbox(t, i, n);
|
|
340
|
+
return {
|
|
341
|
+
nonce: a.toBase64(i),
|
|
342
|
+
ctext: a.toBase64(o)
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
static async decodeMessageSymmetric(e, t, n) {
|
|
346
|
+
return await s.getInstance().crypto_secretbox_open(a.fromBase64(t), a.fromBase64(e), n);
|
|
347
|
+
}
|
|
348
|
+
static async makeNonce(e) {
|
|
349
|
+
let t = s.getInstance(), n = await t.crypto_box_random_nonce(), r;
|
|
350
|
+
if (n.length !== t.crypto_box_NONCEBYTES) throw Error("[Mailbox] Wrong crypto_box nonce length");
|
|
351
|
+
r = 8;
|
|
352
|
+
let i = this.itoa(Math.floor(Date.now() / 1e3));
|
|
353
|
+
if (e && (r += 4), n.fill(0, 0, r), n.set(i, 8 - i.length), e) {
|
|
354
|
+
let t = this.itoa(e);
|
|
355
|
+
n.set(t, 12 - t.length);
|
|
356
|
+
}
|
|
357
|
+
return n;
|
|
358
|
+
}
|
|
359
|
+
static itoa(e) {
|
|
360
|
+
let t = e.toString(16);
|
|
361
|
+
t = t.length & 1 ? `0${t}` : t;
|
|
362
|
+
let n = t.length / 2, r = new Uint8Array(n);
|
|
363
|
+
for (let e = 0, n = 0; n < t.length; n += 2, e++) r[e] = parseInt(t[n] + t[n + 1], 16);
|
|
364
|
+
return r;
|
|
365
|
+
}
|
|
366
|
+
}, g = /* @__PURE__ */ function(e) {
|
|
367
|
+
return e.count = "count", e.upload = "upload", e.download = "download", e.messageStatus = "messageStatus", e.delete = "delete", e.startFileUpload = "startFileUpload", e.uploadFileChunk = "uploadFileChunk", e.downloadFileChunk = "downloadFileChunk", e.fileStatus = "fileStatus", e.deleteFile = "deleteFile", e;
|
|
368
|
+
}({}), _ = /* @__PURE__ */ function(e) {
|
|
369
|
+
return e.message = "message", e.file = "file", e.plain = "plain", e;
|
|
370
|
+
}({}), v = class extends Error {
|
|
371
|
+
constructor(e) {
|
|
372
|
+
super(`GlowNetworkError status: ${e}`), this.status = e, this.name = "GlowNetworkError";
|
|
373
|
+
}
|
|
374
|
+
}, y = class {
|
|
375
|
+
constructor(e) {
|
|
376
|
+
this.url = e, this.difficulty = 0, this.nacl = s.getInstance();
|
|
377
|
+
}
|
|
378
|
+
get isConnected() {
|
|
379
|
+
return !!(this.sessionKeys && this.publicKey);
|
|
380
|
+
}
|
|
381
|
+
async openConnection() {
|
|
382
|
+
this.sessionKeys = new c(await this.nacl.crypto_box_keypair()), this.clientToken = await this.nacl.random_bytes(u.RELAY_TOKEN_LEN);
|
|
383
|
+
let e = await this.fetchRelayToken();
|
|
384
|
+
return this.publicKey = await this.fetchRelayPublicKey(e), {
|
|
385
|
+
h2Signature: await this.getSignature(e, this.sessionKeys),
|
|
386
|
+
relayPublicKey: this.publicKey
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
async fetchRelayToken() {
|
|
390
|
+
if (!this.clientToken) throw Error("[Relay] clientToken is required please openConnection first");
|
|
391
|
+
let e = await this.httpCall("start_session", a.toBase64(this.clientToken));
|
|
392
|
+
this.scheduleTokenExpiration();
|
|
393
|
+
let [t, n] = this.parseResponse("start_session", e);
|
|
394
|
+
return this.difficulty = parseInt(n, 10), this.difficulty > 10 && console.log(`[Relay] ${this.url} requested difficulty ${this.difficulty}. Session handshake may take longer.`), a.fromBase64(t);
|
|
395
|
+
}
|
|
396
|
+
async fetchRelayPublicKey(e) {
|
|
397
|
+
if (!this.clientToken) throw Error("[Relay] clientToken is required please openConnection first");
|
|
398
|
+
let t = a.toBase64(await this.nacl.h2(this.clientToken)), n = new Uint8Array([...this.clientToken, ...e]), r;
|
|
399
|
+
r = this.difficulty === 0 ? await this.nacl.h2(n) : await this.ensureNonceDifficulty(n);
|
|
400
|
+
let i = await this.httpCall("verify_session", t, a.toBase64(r));
|
|
401
|
+
return a.fromBase64(i);
|
|
402
|
+
}
|
|
403
|
+
async prove(e) {
|
|
404
|
+
if (!this.clientToken) throw Error("[Relay] clientToken is required please openConnection first");
|
|
405
|
+
if (!this.sessionKeys) throw Error("[Relay] No session key found, open the connection first");
|
|
406
|
+
let t = a.toBase64(await this.nacl.h2(this.clientToken)), n = await this.httpCall("prove", t, this.sessionKeys.publicKey, e.nonce, e.ctext);
|
|
407
|
+
return this.scheduleSessionExpiration(), n;
|
|
408
|
+
}
|
|
409
|
+
async encodeMessage(e) {
|
|
410
|
+
if (!this.publicKey) throw Error("[Relay] No relay public key found, open the connection first");
|
|
411
|
+
if (!this.sessionKeys) throw Error("[Relay] No session key found, open the connection first");
|
|
412
|
+
return await h.encodeMessage(await this.nacl.encode_utf8(e), this.publicKey, a.fromBase64(this.sessionKeys?.privateKey));
|
|
413
|
+
}
|
|
414
|
+
async decodeMessage(e, t) {
|
|
415
|
+
let n = this.publicKey;
|
|
416
|
+
if (!n) throw Error("[Relay] No relay public key found, open the connection first");
|
|
417
|
+
if (!this.sessionKeys) throw Error("[Relay] No session key found, open the connection first");
|
|
418
|
+
let r = await h.decodeMessage(a.fromBase64(e), a.fromBase64(t), n, a.fromBase64(this.sessionKeys.privateKey));
|
|
419
|
+
if (r === null) throw Error("[Relay] failed to decode message");
|
|
420
|
+
return JSON.parse(r);
|
|
421
|
+
}
|
|
422
|
+
async runCmd(e, t, n, r) {
|
|
423
|
+
if (!Object.keys(g).includes(e)) throw Error(`[Relay] ${this.url} doesn't support command ${e}`);
|
|
424
|
+
let i = [
|
|
425
|
+
t,
|
|
426
|
+
n.nonce,
|
|
427
|
+
n.ctext
|
|
428
|
+
];
|
|
429
|
+
r && i.push(r);
|
|
430
|
+
let a = await this.httpCall("command", ...i);
|
|
431
|
+
return this.parseResponse(e, a);
|
|
432
|
+
}
|
|
433
|
+
async getSignature(e, t) {
|
|
434
|
+
if (!this.clientToken) throw Error("[Relay] clientToken is required please openConnection first");
|
|
435
|
+
let n = a.fromBase64(t.publicKey), r = new Uint8Array([
|
|
436
|
+
...n,
|
|
437
|
+
...e,
|
|
438
|
+
...this.clientToken
|
|
439
|
+
]);
|
|
440
|
+
return await this.nacl.h2(r);
|
|
441
|
+
}
|
|
442
|
+
async httpCall(e, ...t) {
|
|
443
|
+
let n = new AbortController(), r = setTimeout(() => n.abort(), u.RELAY_AJAX_TIMEOUT), i = {
|
|
444
|
+
method: "POST",
|
|
445
|
+
headers: {
|
|
446
|
+
Accept: "text/plain",
|
|
447
|
+
"Content-Type": "text/plain"
|
|
448
|
+
},
|
|
449
|
+
body: t.join("\r\n"),
|
|
450
|
+
signal: n.signal
|
|
451
|
+
};
|
|
452
|
+
try {
|
|
453
|
+
let t = await fetch(`${this.url}/${e}`, i);
|
|
454
|
+
if (!t.ok) throw t.status === 401 && (this.clearSession(), this.clearToken()), new v(t.status);
|
|
455
|
+
return await t.text();
|
|
456
|
+
} catch (e) {
|
|
457
|
+
throw e instanceof Error && e.name === "AbortError" ? new v(408) : e instanceof v ? e : e instanceof TypeError ? new v(0) : e;
|
|
458
|
+
} finally {
|
|
459
|
+
clearTimeout(r);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
parseResponse(e, t) {
|
|
463
|
+
let n = t.split("\r\n");
|
|
464
|
+
if (!t || !this.validateResponse(e, n.length)) throw console.log(n), Error(`[Relay] ${this.url} - ${e}: Bad response`);
|
|
465
|
+
return n;
|
|
466
|
+
}
|
|
467
|
+
validateResponse(e, t) {
|
|
468
|
+
switch (e) {
|
|
469
|
+
case g.upload:
|
|
470
|
+
case g.messageStatus:
|
|
471
|
+
case g.delete: return t === 1;
|
|
472
|
+
case g.downloadFileChunk: return t === 3;
|
|
473
|
+
default: return t === 2;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
scheduleTokenExpiration() {
|
|
477
|
+
this.tokenExpirationTimeoutHandle && clearTimeout(this.tokenExpirationTimeoutHandle), this.tokenExpirationTimeoutHandle = setTimeout(() => this.clearToken(), u.RELAY_TOKEN_TIMEOUT);
|
|
478
|
+
}
|
|
479
|
+
clearToken() {
|
|
480
|
+
this.tokenExpirationTimeoutHandle && clearTimeout(this.tokenExpirationTimeoutHandle), this.clientToken && delete this.clientToken;
|
|
481
|
+
}
|
|
482
|
+
scheduleSessionExpiration() {
|
|
483
|
+
this.sessionExpirationTimeoutHandle && clearTimeout(this.sessionExpirationTimeoutHandle), this.sessionExpirationTimeoutHandle = setTimeout(() => {
|
|
484
|
+
this.clearSession();
|
|
485
|
+
}, u.RELAY_SESSION_TIMEOUT);
|
|
486
|
+
}
|
|
487
|
+
clearSession() {
|
|
488
|
+
this.sessionExpirationTimeoutHandle && clearTimeout(this.sessionExpirationTimeoutHandle), this.sessionKeys && delete this.sessionKeys;
|
|
489
|
+
}
|
|
490
|
+
async ensureNonceDifficulty(e) {
|
|
491
|
+
let t, n;
|
|
492
|
+
do
|
|
493
|
+
t = await this.nacl.random_bytes(32), n = await this.nacl.h2(new Uint8Array([...e, ...t]));
|
|
494
|
+
while (!this.arrayZeroBits(n));
|
|
495
|
+
return t;
|
|
496
|
+
}
|
|
497
|
+
arrayZeroBits(e) {
|
|
498
|
+
let t, n = this.difficulty;
|
|
499
|
+
for (let r = 0; r <= 1 + this.difficulty / 8; r++) {
|
|
500
|
+
if (t = e[r], n <= 0) return !0;
|
|
501
|
+
if (n > 8) {
|
|
502
|
+
if (n -= 8, t > 0) return !1;
|
|
503
|
+
} else return this.firstZeroBits(t, n);
|
|
504
|
+
}
|
|
505
|
+
return !1;
|
|
506
|
+
}
|
|
507
|
+
firstZeroBits(e, t) {
|
|
508
|
+
return e === e >> t << t;
|
|
509
|
+
}
|
|
510
|
+
}, b = class {
|
|
511
|
+
constructor() {
|
|
512
|
+
this.relays = {};
|
|
513
|
+
}
|
|
514
|
+
getInstance(e) {
|
|
515
|
+
let t = this.relays[e];
|
|
516
|
+
return t ||= this.relays[e] = new y(e), t;
|
|
517
|
+
}
|
|
518
|
+
}, x = class e {
|
|
519
|
+
constructor(e, t, n) {
|
|
520
|
+
this.relayFactory = new b(), this.relayConnectionMutexes = /* @__PURE__ */ new Map(), this.nacl = e, this.keyRing = t, this.identity = n;
|
|
521
|
+
}
|
|
522
|
+
static async new(t) {
|
|
523
|
+
return new e(s.getInstance(), await m.new(t), t);
|
|
524
|
+
}
|
|
525
|
+
static async fromSeed(e, t) {
|
|
526
|
+
let n = await this.new(e);
|
|
527
|
+
return t instanceof Uint8Array || (t = a.encode_latin1(t)), await n.keyRing.setCommFromSeed(t), n;
|
|
528
|
+
}
|
|
529
|
+
static async fromSecKey(e, t) {
|
|
530
|
+
let n = await this.new(e);
|
|
531
|
+
return t instanceof Uint8Array || (t = a.fromBase64(t)), await n.keyRing.setCommFromSecKey(t), n;
|
|
532
|
+
}
|
|
533
|
+
static async fromBackup(t, n) {
|
|
534
|
+
return new e(s.getInstance(), await m.fromBackup(t, n), t);
|
|
535
|
+
}
|
|
536
|
+
async upload(e, t, n, r = !0) {
|
|
537
|
+
let i = await this.prepareRelay(e), o = this.getGuestKey(t), s = r ? await this.encodeMessage(t, n) : n, c = a.toBase64(await this.nacl.h2(a.fromBase64(o))), [l] = await this.runRelayCommand(i, g.upload, {
|
|
538
|
+
to: c,
|
|
539
|
+
payload: s
|
|
540
|
+
});
|
|
541
|
+
return l;
|
|
542
|
+
}
|
|
543
|
+
async download(e) {
|
|
544
|
+
let t = await this.prepareRelay(e), n = await this.runRelayCommand(t, g.download), r = await this.decryptResponse(t, n), i = [];
|
|
545
|
+
for (let e of r) {
|
|
546
|
+
let t = this.keyRing.getTagByHpk(e.from);
|
|
547
|
+
if (!t) i.push(await this.parsePlainMessage(e));
|
|
548
|
+
else if (e.kind === "message") i.push(await this.parseTextMessage(e, t));
|
|
549
|
+
else if (e.kind === "file") i.push(await this.parseFileMessage(e, t));
|
|
550
|
+
else throw Error("[Mailbox] download - Unknown message type");
|
|
551
|
+
}
|
|
552
|
+
return i;
|
|
553
|
+
}
|
|
554
|
+
async parsePlainMessage({ data: e, time: t, from: n, nonce: r }) {
|
|
555
|
+
return {
|
|
556
|
+
data: e,
|
|
557
|
+
time: t,
|
|
558
|
+
from: n,
|
|
559
|
+
nonce: r,
|
|
560
|
+
kind: _.plain
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
async parseFileMessage(e, t) {
|
|
564
|
+
let { nonce: n, ctext: r, uploadID: i } = JSON.parse(e.data), a = await this.decodeMessage(t, n, r);
|
|
565
|
+
if (a === null) throw Error("[Mailbox] Failed to decode file message");
|
|
566
|
+
return {
|
|
567
|
+
data: JSON.parse(a),
|
|
568
|
+
time: e.time,
|
|
569
|
+
senderTag: t,
|
|
570
|
+
uploadID: i,
|
|
571
|
+
nonce: n,
|
|
572
|
+
kind: _.file
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
async parseTextMessage(e, t) {
|
|
576
|
+
let n = await this.decodeMessage(t, e.nonce, e.data);
|
|
577
|
+
return n ||= e.data, {
|
|
578
|
+
data: n,
|
|
579
|
+
time: e.time,
|
|
580
|
+
senderTag: t,
|
|
581
|
+
nonce: e.nonce,
|
|
582
|
+
kind: _.message
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
async count(e) {
|
|
586
|
+
let t = await this.prepareRelay(e), n = await this.runRelayCommand(t, g.count);
|
|
587
|
+
return await this.decryptResponse(t, n);
|
|
588
|
+
}
|
|
589
|
+
async delete(e, t) {
|
|
590
|
+
let n = await this.prepareRelay(e), [r] = await this.runRelayCommand(n, g.delete, { payload: t });
|
|
591
|
+
return parseInt(r, 10);
|
|
592
|
+
}
|
|
593
|
+
async messageStatus(e, t) {
|
|
594
|
+
let n = await this.prepareRelay(e), [r] = await this.runRelayCommand(n, g.messageStatus, { token: t });
|
|
595
|
+
return parseInt(r, 10);
|
|
596
|
+
}
|
|
597
|
+
async startFileUpload(e, t, n) {
|
|
598
|
+
let r = await this.prepareRelay(e), i = this.getGuestKey(t), o = a.toBase64(await this.nacl.h2(a.fromBase64(i))), s = await this.nacl.random_bytes(this.nacl.crypto_secretbox_KEYBYTES);
|
|
599
|
+
n.skey = a.toBase64(s);
|
|
600
|
+
let c = await this.encodeMessage(t, JSON.stringify(n)), l = await this.runRelayCommand(r, g.startFileUpload, {
|
|
601
|
+
to: o,
|
|
602
|
+
file_size: n.orig_size,
|
|
603
|
+
metadata: c
|
|
604
|
+
}), u = await this.decryptResponse(r, l);
|
|
605
|
+
return u.skey = s, u;
|
|
606
|
+
}
|
|
607
|
+
async uploadFileChunk(e, t, n, r, i, a) {
|
|
608
|
+
let o = await this.prepareRelay(e), s = await h.encodeMessageSymmetric(n, a), c = await this.runRelayCommand(o, g.uploadFileChunk, {
|
|
609
|
+
uploadID: t,
|
|
610
|
+
part: r,
|
|
611
|
+
last_chunk: i - 1 === r,
|
|
612
|
+
nonce: s.nonce
|
|
613
|
+
}, s.ctext);
|
|
614
|
+
return await this.decryptResponse(o, c);
|
|
615
|
+
}
|
|
616
|
+
async getFileStatus(e, t) {
|
|
617
|
+
let n = await this.prepareRelay(e), r = await this.runRelayCommand(n, g.fileStatus, { uploadID: t });
|
|
618
|
+
return await this.decryptResponse(n, r);
|
|
619
|
+
}
|
|
620
|
+
async getFileMetadata(e, t) {
|
|
621
|
+
return (await this.download(e)).filter((e) => e.kind === "file").find((e) => e.uploadID === t)?.data;
|
|
622
|
+
}
|
|
623
|
+
async downloadFileChunk(e, t, n, r) {
|
|
624
|
+
r instanceof Uint8Array || (r = a.fromBase64(r));
|
|
625
|
+
let i = await this.prepareRelay(e), [o, s, c] = await this.runRelayCommand(i, g.downloadFileChunk, {
|
|
626
|
+
uploadID: t,
|
|
627
|
+
part: n
|
|
628
|
+
}), l = await i.decodeMessage(o, s);
|
|
629
|
+
return await h.decodeMessageSymmetric(l.nonce, c, r);
|
|
630
|
+
}
|
|
631
|
+
async deleteFile(e, t) {
|
|
632
|
+
let n = await this.prepareRelay(e), r = await this.runRelayCommand(n, g.deleteFile, { uploadID: t });
|
|
633
|
+
return await this.decryptResponse(n, r);
|
|
634
|
+
}
|
|
635
|
+
async connectToRelay(e) {
|
|
636
|
+
let t = this.relayFactory.getInstance(e), n = await t.openConnection(), r = await this.encryptSignature(n), i = await t.prove(await t.encodeMessage(JSON.stringify({
|
|
637
|
+
pub_key: this.keyRing.getPubCommKey(),
|
|
638
|
+
nonce: r.nonce,
|
|
639
|
+
ctext: r.ctext
|
|
640
|
+
})));
|
|
641
|
+
return parseInt(i, 10);
|
|
642
|
+
}
|
|
643
|
+
clearSession(e) {
|
|
644
|
+
let t = this.relayFactory.getInstance(e);
|
|
645
|
+
t.clearToken(), t.clearSession();
|
|
646
|
+
}
|
|
647
|
+
async encryptSignature(e) {
|
|
648
|
+
let t = a.fromBase64(this.keyRing.getPrivateCommKey());
|
|
649
|
+
return await h.encodeMessage(e.h2Signature, e.relayPublicKey, t);
|
|
650
|
+
}
|
|
651
|
+
async prepareRelay(e) {
|
|
652
|
+
let t = this.relayFactory.getInstance(e);
|
|
653
|
+
return await this.getRelayConnectionMutex(e).runExclusive(async () => {
|
|
654
|
+
t.isConnected || await this.connectToRelay(e);
|
|
655
|
+
}), t;
|
|
656
|
+
}
|
|
657
|
+
async runRelayCommand(e, t, n, r) {
|
|
658
|
+
n = {
|
|
659
|
+
cmd: t,
|
|
660
|
+
...n
|
|
661
|
+
};
|
|
662
|
+
let i = await this.keyRing.getHpk(), a = await e.encodeMessage(JSON.stringify(n));
|
|
663
|
+
return await e.runCmd(t, i, a, r);
|
|
664
|
+
}
|
|
665
|
+
async decryptResponse(e, t) {
|
|
666
|
+
let [n, r] = t;
|
|
667
|
+
return await e.decodeMessage(n, r);
|
|
668
|
+
}
|
|
669
|
+
async encodeMessage(e, t) {
|
|
670
|
+
let n = this.getGuestKey(e), r = this.keyRing.getPrivateCommKey();
|
|
671
|
+
return await h.encodeMessage(await this.nacl.encode_utf8(t), a.fromBase64(n), a.fromBase64(r));
|
|
672
|
+
}
|
|
673
|
+
async decodeMessage(e, t, n) {
|
|
674
|
+
let r = this.getGuestKey(e), i = this.keyRing.getPrivateCommKey(), o;
|
|
675
|
+
try {
|
|
676
|
+
o = a.fromBase64(n);
|
|
677
|
+
} catch {
|
|
678
|
+
return null;
|
|
679
|
+
}
|
|
680
|
+
return await h.decodeMessage(a.fromBase64(t), o, a.fromBase64(r), a.fromBase64(i));
|
|
681
|
+
}
|
|
682
|
+
getGuestKey(e) {
|
|
683
|
+
let t = this.keyRing.getGuestKey(e);
|
|
684
|
+
if (!t) throw Error(`[Mailbox] Unknown guest ${e}`);
|
|
685
|
+
return t;
|
|
686
|
+
}
|
|
687
|
+
async selfDestruct() {
|
|
688
|
+
await this.keyRing.selfDestruct();
|
|
689
|
+
}
|
|
690
|
+
getRelayConnectionMutex(e) {
|
|
691
|
+
return this.relayConnectionMutexes.has(e) || this.relayConnectionMutexes.set(e, new i()), this.relayConnectionMutexes.get(e);
|
|
692
|
+
}
|
|
693
|
+
}, S = class {
|
|
694
|
+
constructor() {
|
|
695
|
+
this.storage = {};
|
|
696
|
+
}
|
|
697
|
+
async get(e) {
|
|
698
|
+
return (await this.getMultiple([e]))[0];
|
|
699
|
+
}
|
|
700
|
+
async getMultiple(e) {
|
|
701
|
+
return e.map((e) => this.storage[e]);
|
|
702
|
+
}
|
|
703
|
+
set(e, t) {
|
|
704
|
+
return this.setMultiple({ [e]: t });
|
|
705
|
+
}
|
|
706
|
+
async setMultiple(e) {
|
|
707
|
+
for (let t in e) this.storage[t] = e[t];
|
|
708
|
+
}
|
|
709
|
+
remove(e) {
|
|
710
|
+
return this.removeMultiple([e]);
|
|
711
|
+
}
|
|
712
|
+
async removeMultiple(e) {
|
|
713
|
+
e.forEach((e) => delete this.storage[e]);
|
|
714
|
+
}
|
|
715
|
+
reset() {
|
|
716
|
+
this.storage = {};
|
|
717
|
+
}
|
|
176
718
|
};
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
async get(e) {
|
|
182
|
-
return (await this.getMultiple([e]))[0];
|
|
183
|
-
}
|
|
184
|
-
async getMultiple(e) {
|
|
185
|
-
return e.map((t) => localStorage.getItem(this.tag(t)) || null);
|
|
186
|
-
}
|
|
187
|
-
async set(e, t) {
|
|
188
|
-
return this.setMultiple({ [e]: t });
|
|
189
|
-
}
|
|
190
|
-
async setMultiple(e) {
|
|
191
|
-
for (const t in e) {
|
|
192
|
-
const s = e[t];
|
|
193
|
-
s !== null && localStorage.setItem(this.tag(t), s);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
async remove(e) {
|
|
197
|
-
this.removeMultiple([e]);
|
|
198
|
-
}
|
|
199
|
-
async removeMultiple(e) {
|
|
200
|
-
e.forEach((t) => localStorage.removeItem(this.tag(t)));
|
|
201
|
-
}
|
|
202
|
-
tag(e) {
|
|
203
|
-
return `${this.rootTag}.${e}`;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
class E {
|
|
207
|
-
constructor(e, t) {
|
|
208
|
-
const s = h.getInstance();
|
|
209
|
-
this.nacl = s, this.driver = e, this.id = t ? `.${t}${u.STORAGE_ROOT}` : u.STORAGE_ROOT;
|
|
210
|
-
}
|
|
211
|
-
static async new(e) {
|
|
212
|
-
const t = this.getStorageDriver(), s = new E(t, e), n = s.addPrefix(u.SKEY_TAG), r = await t.get(n);
|
|
213
|
-
if (r) {
|
|
214
|
-
const { key: a } = JSON.parse(r);
|
|
215
|
-
s.storageKey = o.fromBase64(a);
|
|
216
|
-
} else
|
|
217
|
-
s.storageKey = await s.nacl.random_bytes(s.nacl.crypto_secretbox_KEYBYTES), await t.set(n, JSON.stringify({ key: o.toBase64(s.storageKey) }));
|
|
218
|
-
return s;
|
|
219
|
-
}
|
|
220
|
-
static getStorageDriver() {
|
|
221
|
-
if (this.storageDriver)
|
|
222
|
-
return this.storageDriver;
|
|
223
|
-
throw new Error("[CryptoStorage] StorageDriver instance is not yet set");
|
|
224
|
-
}
|
|
225
|
-
static setDefaultStorageDriver() {
|
|
226
|
-
return this.setStorageDriver(new M());
|
|
227
|
-
}
|
|
228
|
-
static setStorageDriver(e) {
|
|
229
|
-
if (this.storageDriver)
|
|
230
|
-
throw new Error("[CryptoStorage] StorageDriver has been already set, it is supposed to be set only once");
|
|
231
|
-
return this.storageDriver = e, !0;
|
|
232
|
-
}
|
|
233
|
-
async save(e, t) {
|
|
234
|
-
if (!this.driver)
|
|
235
|
-
throw new Error("[CryptoStorage] Storage driver is not set");
|
|
236
|
-
if (!this.storageKey)
|
|
237
|
-
throw new Error("[CryptoStorage] Storage key is not set");
|
|
238
|
-
const s = JSON.stringify(t), n = await this.nacl.encode_utf8(s), r = await this.nacl.crypto_secretbox_random_nonce(), a = await this.nacl.crypto_secretbox(n, r, this.storageKey);
|
|
239
|
-
return await this.driver.setMultiple({
|
|
240
|
-
[this.addPrefix(e)]: o.toBase64(a),
|
|
241
|
-
[this.addNonceTag(e)]: o.toBase64(r)
|
|
242
|
-
}), !0;
|
|
243
|
-
}
|
|
244
|
-
async get(e) {
|
|
245
|
-
if (!this.driver)
|
|
246
|
-
throw new Error("[CryptoStorage] Storage driver is not set");
|
|
247
|
-
if (!this.storageKey)
|
|
248
|
-
throw new Error("[CryptoStorage] Storage key is not set");
|
|
249
|
-
const [t, s] = await this.driver.getMultiple([this.addPrefix(e), this.addNonceTag(e)]);
|
|
250
|
-
if (!t || !s)
|
|
251
|
-
return null;
|
|
252
|
-
const n = o.fromBase64(t), r = o.fromBase64(s), a = await this.nacl.crypto_secretbox_open(n, r, this.storageKey);
|
|
253
|
-
if (a) {
|
|
254
|
-
const c = await this.nacl.decode_utf8(a);
|
|
255
|
-
return JSON.parse(c);
|
|
256
|
-
} else
|
|
257
|
-
throw new Error("[CryptoStorage] crypto_secretbox_open: decryption error");
|
|
258
|
-
}
|
|
259
|
-
async remove(e) {
|
|
260
|
-
if (!this.driver)
|
|
261
|
-
throw new Error("[CryptoStorage] Storage driver is not set");
|
|
262
|
-
return await this.driver.removeMultiple([this.addPrefix(e), this.addNonceTag(e)]), !0;
|
|
263
|
-
}
|
|
264
|
-
async selfDestruct() {
|
|
265
|
-
if (!this.driver)
|
|
266
|
-
throw new Error("[CryptoStorage] Storage driver is not set");
|
|
267
|
-
await this.driver.remove(this.addPrefix(u.SKEY_TAG));
|
|
268
|
-
}
|
|
269
|
-
// Keys are tagged in the storage with a versioned prefix
|
|
270
|
-
addPrefix(e) {
|
|
271
|
-
return this.id ? e + this.id : e;
|
|
272
|
-
}
|
|
273
|
-
addNonceTag(e) {
|
|
274
|
-
return this.addPrefix(`${u.NONCE_TAG}.${e}`);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
const T = "__::commKey::__", y = class y {
|
|
278
|
-
constructor(e, t, s) {
|
|
279
|
-
this.guestKeys = /* @__PURE__ */ new Map(), this.nacl = e, this.storage = t, this.commKey = s;
|
|
280
|
-
}
|
|
281
|
-
static async new(e) {
|
|
282
|
-
const t = h.getInstance(), s = await E.new(e), n = await y.getCommKeyOrCreate(t, s), r = new y(t, s, n);
|
|
283
|
-
return await r.loadGuestKeys(), r;
|
|
284
|
-
}
|
|
285
|
-
static async fromBackup(e, t) {
|
|
286
|
-
const s = JSON.parse(t), n = o.fromBase64(s[T]), r = await y.new(e);
|
|
287
|
-
await r.setCommFromSecKey(n);
|
|
288
|
-
for (const [a, c] of Object.entries(s))
|
|
289
|
-
a !== T && await r.addGuest(a, c);
|
|
290
|
-
return r;
|
|
291
|
-
}
|
|
292
|
-
getNumberOfGuests() {
|
|
293
|
-
return this.guestKeys.size;
|
|
294
|
-
}
|
|
295
|
-
get guests() {
|
|
296
|
-
return this.guestKeys;
|
|
297
|
-
}
|
|
298
|
-
/**
|
|
299
|
-
* Returns public identity, which is the default communication key.
|
|
300
|
-
* Correspondents can know it, whereas Relays do not need it (other than
|
|
301
|
-
* temporarily for internal use during the ownership proof)
|
|
302
|
-
*/
|
|
303
|
-
getPubCommKey() {
|
|
304
|
-
return this.commKey.publicKey;
|
|
305
|
-
}
|
|
306
|
-
getPrivateCommKey() {
|
|
307
|
-
return this.commKey.privateKey;
|
|
308
|
-
}
|
|
309
|
-
/**
|
|
310
|
-
* Returns HPK (hash of the public key) of the mailbox. This is what Zax relays
|
|
311
|
-
* uses as the universal address of the mailbox
|
|
312
|
-
*/
|
|
313
|
-
async getHpk() {
|
|
314
|
-
const e = await this.nacl.h2(o.fromBase64(this.commKey.publicKey));
|
|
315
|
-
return o.toBase64(e);
|
|
316
|
-
}
|
|
317
|
-
getTagByHpk(e) {
|
|
318
|
-
for (const [t, s] of this.guestKeys)
|
|
319
|
-
if (s.hpk === e)
|
|
320
|
-
return t;
|
|
321
|
-
return null;
|
|
322
|
-
}
|
|
323
|
-
getGuestKey(e) {
|
|
324
|
-
const t = this.guestKeys.get(e);
|
|
325
|
-
return t ? t.pk : null;
|
|
326
|
-
}
|
|
327
|
-
// Backups
|
|
328
|
-
async backup() {
|
|
329
|
-
const e = {
|
|
330
|
-
[T]: this.commKey.privateKey
|
|
331
|
-
};
|
|
332
|
-
if (this.getNumberOfGuests() > 0)
|
|
333
|
-
for (const [t, s] of this.guestKeys)
|
|
334
|
-
t && s && (e[t] = s.pk);
|
|
335
|
-
return JSON.stringify(e);
|
|
336
|
-
}
|
|
337
|
-
async setCommFromSeed(e) {
|
|
338
|
-
this.commKey = new w(await this.nacl.crypto_box_keypair_from_seed(e)), await this.storage.save(y.commKeyTag, this.commKey.toString());
|
|
339
|
-
}
|
|
340
|
-
async setCommFromSecKey(e) {
|
|
341
|
-
this.commKey = new w(await this.nacl.crypto_box_keypair_from_raw_sk(e)), await this.storage.save(y.commKeyTag, this.commKey.toString());
|
|
342
|
-
}
|
|
343
|
-
async addGuest(e, t) {
|
|
344
|
-
const s = o.toBase64(await this.nacl.h2(o.fromBase64(t)));
|
|
345
|
-
return this.guestKeys.set(e, {
|
|
346
|
-
pk: t,
|
|
347
|
-
hpk: s
|
|
348
|
-
}), await this.saveGuests(), s;
|
|
349
|
-
}
|
|
350
|
-
async removeGuest(e) {
|
|
351
|
-
return this.guestKeys.has(e) && (this.guestKeys.delete(e), await this.saveGuests()), !0;
|
|
352
|
-
}
|
|
353
|
-
async loadGuestKeys() {
|
|
354
|
-
const e = await this.storage.get(y.guestRegistryTag);
|
|
355
|
-
if (e)
|
|
356
|
-
if (typeof e == "object")
|
|
357
|
-
this.guestKeys = new Map(Object.entries(e));
|
|
358
|
-
else
|
|
359
|
-
throw new Error("[Keyring] Guest keys is not an object");
|
|
360
|
-
else return;
|
|
361
|
-
}
|
|
362
|
-
async saveGuests() {
|
|
363
|
-
await this.storage.save(y.guestRegistryTag, o.toObject(this.guestKeys.entries()));
|
|
364
|
-
}
|
|
365
|
-
static async getCommKeyOrCreate(e, t) {
|
|
366
|
-
const s = await t.get(y.commKeyTag);
|
|
367
|
-
if (s && typeof s == "string")
|
|
368
|
-
return new w(s);
|
|
369
|
-
{
|
|
370
|
-
const n = await e.crypto_box_keypair(), r = new w(n);
|
|
371
|
-
return await t.save(y.commKeyTag, r.toString()), r;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
async selfDestruct() {
|
|
375
|
-
await this.storage.remove(y.guestRegistryTag), await this.storage.remove(y.commKeyTag), await this.storage.selfDestruct();
|
|
376
|
-
}
|
|
377
|
-
};
|
|
378
|
-
y.commKeyTag = "comm_key", y.guestRegistryTag = "guest_registry";
|
|
379
|
-
let S = y;
|
|
380
|
-
class p {
|
|
381
|
-
constructor() {
|
|
382
|
-
}
|
|
383
|
-
/**
|
|
384
|
-
* Encodes a binary message with `cryptobox`
|
|
385
|
-
*/
|
|
386
|
-
static async encodeMessage(e, t, s, n) {
|
|
387
|
-
const r = h.getInstance(), a = await this.makeNonce(n), c = await r.crypto_box(e, a, t, s);
|
|
388
|
-
return {
|
|
389
|
-
nonce: o.toBase64(a),
|
|
390
|
-
ctext: o.toBase64(c)
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
|
-
/**
|
|
394
|
-
* Decodes a binary message with `cryptobox_open`
|
|
395
|
-
* @returns null if failed to decode
|
|
396
|
-
*/
|
|
397
|
-
static async decodeMessage(e, t, s, n) {
|
|
398
|
-
const r = h.getInstance(), a = await r.crypto_box_open(t, e, s, n);
|
|
399
|
-
return a && await r.decode_utf8(a);
|
|
400
|
-
}
|
|
401
|
-
/**
|
|
402
|
-
* Encodes a binary message with `secretbox` (used for file chunk encryption)
|
|
403
|
-
*/
|
|
404
|
-
static async encodeMessageSymmetric(e, t) {
|
|
405
|
-
const s = h.getInstance(), n = await p.makeNonce(), r = await s.crypto_secretbox(e, n, t);
|
|
406
|
-
return {
|
|
407
|
-
nonce: o.toBase64(n),
|
|
408
|
-
ctext: o.toBase64(r)
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
/**
|
|
412
|
-
* Decodes a binary message with `secretbox_open` (used for file chunk decryption)
|
|
413
|
-
*/
|
|
414
|
-
static async decodeMessageSymmetric(e, t, s) {
|
|
415
|
-
return await h.getInstance().crypto_secretbox_open(o.fromBase64(t), o.fromBase64(e), s);
|
|
416
|
-
}
|
|
417
|
-
// ---------- Nonce helper ----------
|
|
418
|
-
/**
|
|
419
|
-
* Makes a timestamp nonce that a relay expects for any crypto operations.
|
|
420
|
-
* Timestamp is the first 8 bytes, the rest is random, unless custom `data`
|
|
421
|
-
* is specified. `data` will be packed as next 4 bytes after timestamp.
|
|
422
|
-
*/
|
|
423
|
-
static async makeNonce(e) {
|
|
424
|
-
const t = h.getInstance(), s = await t.crypto_box_random_nonce();
|
|
425
|
-
let n;
|
|
426
|
-
if (s.length !== t.crypto_box_NONCEBYTES)
|
|
427
|
-
throw new Error("[Mailbox] Wrong crypto_box nonce length");
|
|
428
|
-
n = 8;
|
|
429
|
-
const r = this.itoa(Math.floor(Date.now() / 1e3));
|
|
430
|
-
if (e && (n += 4), s.fill(0, 0, n), s.set(r, 8 - r.length), e) {
|
|
431
|
-
const a = this.itoa(e);
|
|
432
|
-
s.set(a, 12 - a.length);
|
|
433
|
-
}
|
|
434
|
-
return s;
|
|
435
|
-
}
|
|
436
|
-
/**
|
|
437
|
-
* Splits an integer into an array of bytes
|
|
438
|
-
*/
|
|
439
|
-
static itoa(e) {
|
|
440
|
-
let t = e.toString(16);
|
|
441
|
-
t = t.length & 1 ? `0${t}` : t;
|
|
442
|
-
const s = t.length / 2, n = new Uint8Array(s);
|
|
443
|
-
for (let r = 0, a = 0; a < t.length; a += 2, r++)
|
|
444
|
-
n[r] = parseInt(t[a] + t[a + 1], 16);
|
|
445
|
-
return n;
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
var l = /* @__PURE__ */ ((i) => (i.count = "count", i.upload = "upload", i.download = "download", i.messageStatus = "messageStatus", i.delete = "delete", i.startFileUpload = "startFileUpload", i.uploadFileChunk = "uploadFileChunk", i.downloadFileChunk = "downloadFileChunk", i.fileStatus = "fileStatus", i.deleteFile = "deleteFile", i))(l || {}), b = /* @__PURE__ */ ((i) => (i.message = "message", i.file = "file", i.plain = "plain", i))(b || {});
|
|
449
|
-
class _ extends Error {
|
|
450
|
-
constructor(e) {
|
|
451
|
-
super(`GlowNetworkError status: ${e}`), this.status = e, this.name = "GlowNetworkError";
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
class N {
|
|
455
|
-
constructor(e) {
|
|
456
|
-
this.url = e, this.difficulty = 0, this.nacl = h.getInstance();
|
|
457
|
-
}
|
|
458
|
-
get isConnected() {
|
|
459
|
-
return !!(this.sessionKeys && this.publicKey);
|
|
460
|
-
}
|
|
461
|
-
// ---------- Connection initialization ----------
|
|
462
|
-
/**
|
|
463
|
-
* Exchanges tokens with a relay and gets a temp session key for this relay.
|
|
464
|
-
* Returns h₂(signature) and a relay public key
|
|
465
|
-
*/
|
|
466
|
-
async openConnection() {
|
|
467
|
-
this.sessionKeys = new w(await this.nacl.crypto_box_keypair()), this.clientToken = await this.nacl.random_bytes(u.RELAY_TOKEN_LEN);
|
|
468
|
-
const e = await this.fetchRelayToken();
|
|
469
|
-
return this.publicKey = await this.fetchRelayPublicKey(e), {
|
|
470
|
-
h2Signature: await this.getSignature(e, this.sessionKeys),
|
|
471
|
-
relayPublicKey: this.publicKey
|
|
472
|
-
};
|
|
473
|
-
}
|
|
474
|
-
/**
|
|
475
|
-
* Sends a client token to a relay and saves a relay token
|
|
476
|
-
*/
|
|
477
|
-
async fetchRelayToken() {
|
|
478
|
-
if (!this.clientToken)
|
|
479
|
-
throw new Error("[Relay] clientToken is required please openConnection first");
|
|
480
|
-
const e = await this.httpCall("start_session", o.toBase64(this.clientToken));
|
|
481
|
-
this.scheduleTokenExpiration();
|
|
482
|
-
const [t, s] = this.parseResponse("start_session", e);
|
|
483
|
-
return this.difficulty = parseInt(s, 10), this.difficulty > 10 && console.log(`[Relay] ${this.url} requested difficulty ${this.difficulty}. Session handshake may take longer.`), o.fromBase64(t);
|
|
484
|
-
}
|
|
485
|
-
/**
|
|
486
|
-
* Completes the handshake and saves a relay pubic key
|
|
487
|
-
*/
|
|
488
|
-
async fetchRelayPublicKey(e) {
|
|
489
|
-
if (!this.clientToken)
|
|
490
|
-
throw new Error("[Relay] clientToken is required please openConnection first");
|
|
491
|
-
const t = o.toBase64(await this.nacl.h2(this.clientToken)), s = new Uint8Array([...this.clientToken, ...e]);
|
|
492
|
-
let n;
|
|
493
|
-
this.difficulty === 0 ? n = await this.nacl.h2(s) : n = await this.ensureNonceDifficulty(s);
|
|
494
|
-
const r = await this.httpCall("verify_session", t, o.toBase64(n));
|
|
495
|
-
return o.fromBase64(r);
|
|
496
|
-
}
|
|
497
|
-
/**
|
|
498
|
-
* Attaches a mailbox and fetches number of messages
|
|
499
|
-
*/
|
|
500
|
-
async prove(e) {
|
|
501
|
-
if (!this.clientToken)
|
|
502
|
-
throw new Error("[Relay] clientToken is required please openConnection first");
|
|
503
|
-
if (!this.sessionKeys)
|
|
504
|
-
throw new Error("[Relay] No session key found, open the connection first");
|
|
505
|
-
const t = o.toBase64(await this.nacl.h2(this.clientToken)), s = await this.httpCall(
|
|
506
|
-
"prove",
|
|
507
|
-
t,
|
|
508
|
-
this.sessionKeys.publicKey,
|
|
509
|
-
e.nonce,
|
|
510
|
-
e.ctext
|
|
511
|
-
);
|
|
512
|
-
return this.scheduleSessionExpiration(), s;
|
|
513
|
-
}
|
|
514
|
-
async encodeMessage(e) {
|
|
515
|
-
if (!this.publicKey)
|
|
516
|
-
throw new Error("[Relay] No relay public key found, open the connection first");
|
|
517
|
-
if (!this.sessionKeys)
|
|
518
|
-
throw new Error("[Relay] No session key found, open the connection first");
|
|
519
|
-
return await p.encodeMessage(
|
|
520
|
-
await this.nacl.encode_utf8(e),
|
|
521
|
-
this.publicKey,
|
|
522
|
-
o.fromBase64(this.sessionKeys?.privateKey)
|
|
523
|
-
);
|
|
524
|
-
}
|
|
525
|
-
async decodeMessage(e, t) {
|
|
526
|
-
const s = this.publicKey;
|
|
527
|
-
if (!s)
|
|
528
|
-
throw new Error("[Relay] No relay public key found, open the connection first");
|
|
529
|
-
if (!this.sessionKeys)
|
|
530
|
-
throw new Error("[Relay] No session key found, open the connection first");
|
|
531
|
-
const n = await p.decodeMessage(
|
|
532
|
-
o.fromBase64(e),
|
|
533
|
-
o.fromBase64(t),
|
|
534
|
-
s,
|
|
535
|
-
o.fromBase64(this.sessionKeys.privateKey)
|
|
536
|
-
);
|
|
537
|
-
if (n === null)
|
|
538
|
-
throw new Error("[Relay] failed to decode message");
|
|
539
|
-
return JSON.parse(n);
|
|
540
|
-
}
|
|
541
|
-
// ---------- Low-level server request handling ----------
|
|
542
|
-
/**
|
|
543
|
-
* Executes a message/file command on a relay, parses and validates the response
|
|
544
|
-
*/
|
|
545
|
-
async runCmd(e, t, s, n) {
|
|
546
|
-
if (!Object.keys(l).includes(e))
|
|
547
|
-
throw new Error(`[Relay] ${this.url} doesn't support command ${e}`);
|
|
548
|
-
const r = [t, s.nonce, s.ctext];
|
|
549
|
-
n && r.push(n);
|
|
550
|
-
const a = await this.httpCall("command", ...r);
|
|
551
|
-
return this.parseResponse(e, a);
|
|
552
|
-
}
|
|
553
|
-
async getSignature(e, t) {
|
|
554
|
-
if (!this.clientToken)
|
|
555
|
-
throw new Error("[Relay] clientToken is required please openConnection first");
|
|
556
|
-
const s = o.fromBase64(t.publicKey), n = new Uint8Array([...s, ...e, ...this.clientToken]);
|
|
557
|
-
return await this.nacl.h2(n);
|
|
558
|
-
}
|
|
559
|
-
/**
|
|
560
|
-
* Executes a call to a relay and return raw string response
|
|
561
|
-
*/
|
|
562
|
-
// TODO: extract fetching into separate function
|
|
563
|
-
async httpCall(e, ...t) {
|
|
564
|
-
const s = new AbortController(), n = setTimeout(() => s.abort(), u.RELAY_AJAX_TIMEOUT), r = {
|
|
565
|
-
method: "POST",
|
|
566
|
-
headers: {
|
|
567
|
-
Accept: "text/plain",
|
|
568
|
-
"Content-Type": "text/plain"
|
|
569
|
-
},
|
|
570
|
-
body: t.join(`\r
|
|
571
|
-
`),
|
|
572
|
-
signal: s.signal
|
|
573
|
-
};
|
|
574
|
-
try {
|
|
575
|
-
const a = await fetch(`${this.url}/${e}`, r);
|
|
576
|
-
if (!a.ok)
|
|
577
|
-
throw a.status === 401 && (this.clearSession(), this.clearToken()), new _(a.status);
|
|
578
|
-
return await a.text();
|
|
579
|
-
} catch (a) {
|
|
580
|
-
throw a instanceof Error && a.name === "AbortError" ? new _(408) : a instanceof _ ? a : a instanceof TypeError ? new _(0) : a;
|
|
581
|
-
} finally {
|
|
582
|
-
clearTimeout(n);
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
/**
|
|
586
|
-
* Parses relay response and throws an error if its format is unexpected
|
|
587
|
-
*/
|
|
588
|
-
parseResponse(e, t) {
|
|
589
|
-
const s = t.split(`\r
|
|
590
|
-
`);
|
|
591
|
-
if (!t || !this.validateResponse(e, s.length))
|
|
592
|
-
throw console.log(s), new Error(`[Relay] ${this.url} - ${e}: Bad response`);
|
|
593
|
-
return s;
|
|
594
|
-
}
|
|
595
|
-
/**
|
|
596
|
-
* Compares the expected number of lines in response with what was actually received from a relay
|
|
597
|
-
*/
|
|
598
|
-
validateResponse(e, t) {
|
|
599
|
-
switch (e) {
|
|
600
|
-
case l.upload:
|
|
601
|
-
case l.messageStatus:
|
|
602
|
-
case l.delete:
|
|
603
|
-
return t === 1;
|
|
604
|
-
case l.downloadFileChunk:
|
|
605
|
-
return t === 3;
|
|
606
|
-
default:
|
|
607
|
-
return t === 2;
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
/**
|
|
611
|
-
* delete token locally before it was removed on server so will reconnect without getting unauthorized error
|
|
612
|
-
*/
|
|
613
|
-
scheduleTokenExpiration() {
|
|
614
|
-
this.tokenExpirationTimeoutHandle && clearTimeout(this.tokenExpirationTimeoutHandle), this.tokenExpirationTimeoutHandle = setTimeout(() => this.clearToken(), u.RELAY_TOKEN_TIMEOUT);
|
|
615
|
-
}
|
|
616
|
-
clearToken() {
|
|
617
|
-
this.tokenExpirationTimeoutHandle && clearTimeout(this.tokenExpirationTimeoutHandle), this.clientToken && delete this.clientToken;
|
|
618
|
-
}
|
|
619
|
-
/**
|
|
620
|
-
* established session is valid only for some period need to remove it after it's ttl
|
|
621
|
-
* so relay will reconnect without receiving error from server
|
|
622
|
-
*/
|
|
623
|
-
scheduleSessionExpiration() {
|
|
624
|
-
this.sessionExpirationTimeoutHandle && clearTimeout(this.sessionExpirationTimeoutHandle), this.sessionExpirationTimeoutHandle = setTimeout(() => {
|
|
625
|
-
this.clearSession();
|
|
626
|
-
}, u.RELAY_SESSION_TIMEOUT);
|
|
627
|
-
}
|
|
628
|
-
clearSession() {
|
|
629
|
-
this.sessionExpirationTimeoutHandle && clearTimeout(this.sessionExpirationTimeoutHandle), this.sessionKeys && delete this.sessionKeys;
|
|
630
|
-
}
|
|
631
|
-
// ---------- Difficulty adjustment ----------
|
|
632
|
-
/**
|
|
633
|
-
* Continuously calculates the nonce until one requested by a relay is found
|
|
634
|
-
*/
|
|
635
|
-
async ensureNonceDifficulty(e) {
|
|
636
|
-
let t, s;
|
|
637
|
-
do
|
|
638
|
-
t = await this.nacl.random_bytes(32), s = await this.nacl.h2(new Uint8Array([...e, ...t]));
|
|
639
|
-
while (!this.arrayZeroBits(s));
|
|
640
|
-
return t;
|
|
641
|
-
}
|
|
642
|
-
/**
|
|
643
|
-
* Returns `true` if the rightmost n bits of a byte are 0.
|
|
644
|
-
* Check whether the rightmost difficulty bits of an Uint8Array are 0, where
|
|
645
|
-
* the lowest indexes of the array represent those rightmost bits. Thus if
|
|
646
|
-
* the difficulty is 17, then array[0] and array[1] should be 0, as should the
|
|
647
|
-
* rightmost bit of array[2]. This is used for our difficulty settings in Zax to
|
|
648
|
-
* reduce burden on a busy server by ensuring clients have to do some
|
|
649
|
-
* additional work during the session handshake
|
|
650
|
-
*/
|
|
651
|
-
arrayZeroBits(e) {
|
|
652
|
-
let t, s = this.difficulty;
|
|
653
|
-
for (let n = 0; n <= 1 + this.difficulty / 8; n++) {
|
|
654
|
-
if (t = e[n], s <= 0)
|
|
655
|
-
return !0;
|
|
656
|
-
if (s > 8) {
|
|
657
|
-
if (s -= 8, t > 0)
|
|
658
|
-
return !1;
|
|
659
|
-
} else
|
|
660
|
-
return this.firstZeroBits(t, s);
|
|
661
|
-
}
|
|
662
|
-
return !1;
|
|
663
|
-
}
|
|
664
|
-
/**
|
|
665
|
-
* Returns `true` if the rightmost n bits of a byte are 0
|
|
666
|
-
*/
|
|
667
|
-
firstZeroBits(e, t) {
|
|
668
|
-
return e === e >> t << t;
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
class O {
|
|
672
|
-
constructor() {
|
|
673
|
-
this.relays = {};
|
|
674
|
-
}
|
|
675
|
-
/**
|
|
676
|
-
* Relay factory, that returns a Relay instance for a given URL,
|
|
677
|
-
* or creates a new one if it hasn't yet been initialized.
|
|
678
|
-
*/
|
|
679
|
-
getInstance(e) {
|
|
680
|
-
let t = this.relays[e];
|
|
681
|
-
return t || (t = this.relays[e] = new N(e)), t;
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
class x {
|
|
685
|
-
constructor(e, t, s) {
|
|
686
|
-
this.relayFactory = new O(), this.relayConnectionMutexes = /* @__PURE__ */ new Map(), this.nacl = e, this.keyRing = t, this.identity = s;
|
|
687
|
-
}
|
|
688
|
-
static async new(e) {
|
|
689
|
-
return new x(h.getInstance(), await S.new(e), e);
|
|
690
|
-
}
|
|
691
|
-
// ---------- Alternative initializers ----------
|
|
692
|
-
/**
|
|
693
|
-
* Create a Mailbox where the secret identity key is derived from a well-known seed
|
|
694
|
-
*/
|
|
695
|
-
static async fromSeed(e, t) {
|
|
696
|
-
const s = await this.new(e);
|
|
697
|
-
return t instanceof Uint8Array || (t = o.encode_latin1(t)), await s.keyRing.setCommFromSeed(t), s;
|
|
698
|
-
}
|
|
699
|
-
/**
|
|
700
|
-
* Create a Mailbox from the known secret identity key
|
|
701
|
-
*/
|
|
702
|
-
static async fromSecKey(e, t) {
|
|
703
|
-
const s = await this.new(e);
|
|
704
|
-
return t instanceof Uint8Array || (t = o.fromBase64(t)), await s.keyRing.setCommFromSecKey(t), s;
|
|
705
|
-
}
|
|
706
|
-
/**
|
|
707
|
-
* Create a Mailbox from the backup string
|
|
708
|
-
*/
|
|
709
|
-
static async fromBackup(e, t) {
|
|
710
|
-
return new x(h.getInstance(), await S.fromBackup(e, t), e);
|
|
711
|
-
}
|
|
712
|
-
// ---------- Relay message commands (public API) ----------
|
|
713
|
-
/**
|
|
714
|
-
* Sends a free-form object to a guest we already have in our keyring. Set `encrypt` to `false` to
|
|
715
|
-
* send a plaintext message. Returns a token that can be used with `messageStatus` command to check
|
|
716
|
-
* the status of the message
|
|
717
|
-
*/
|
|
718
|
-
async upload(e, t, s, n = !0) {
|
|
719
|
-
const r = await this.prepareRelay(e), a = this.getGuestKey(t), c = n ? await this.encodeMessage(t, s) : s, d = o.toBase64(await this.nacl.h2(o.fromBase64(a))), [g] = await this.runRelayCommand(r, l.upload, { to: d, payload: c });
|
|
720
|
-
return g;
|
|
721
|
-
}
|
|
722
|
-
/**
|
|
723
|
-
* Downloads all messages from a relay, decrypts them with a relay key,
|
|
724
|
-
* and then parses each message to find out if it's a text message, file message,
|
|
725
|
-
* or if it can't be decrypted because HPK is missing in the keyring.
|
|
726
|
-
* Returns an array of mixed messages
|
|
727
|
-
*/
|
|
728
|
-
async download(e) {
|
|
729
|
-
const t = await this.prepareRelay(e), s = await this.runRelayCommand(t, l.download), n = await this.decryptResponse(t, s), r = [];
|
|
730
|
-
for (const a of n) {
|
|
731
|
-
const c = this.keyRing.getTagByHpk(a.from);
|
|
732
|
-
if (!c)
|
|
733
|
-
r.push(await this.parsePlainMessage(a));
|
|
734
|
-
else if (a.kind === "message")
|
|
735
|
-
r.push(await this.parseTextMessage(a, c));
|
|
736
|
-
else if (a.kind === "file")
|
|
737
|
-
r.push(await this.parseFileMessage(a, c));
|
|
738
|
-
else
|
|
739
|
-
throw new Error("[Mailbox] download - Unknown message type");
|
|
740
|
-
}
|
|
741
|
-
return r;
|
|
742
|
-
}
|
|
743
|
-
/**
|
|
744
|
-
* Marks a raw Zax message as one that can't be decrypted,
|
|
745
|
-
* because sender's HPK is not found in the keyring
|
|
746
|
-
*/
|
|
747
|
-
async parsePlainMessage({ data: e, time: t, from: s, nonce: n }) {
|
|
748
|
-
return { data: e, time: t, from: s, nonce: n, kind: b.plain };
|
|
749
|
-
}
|
|
750
|
-
/**
|
|
751
|
-
* Decrypts a message that represents uploaded file metadata
|
|
752
|
-
*/
|
|
753
|
-
async parseFileMessage(e, t) {
|
|
754
|
-
const { nonce: s, ctext: n, uploadID: r } = JSON.parse(e.data), a = await this.decodeMessage(t, s, n);
|
|
755
|
-
if (a === null)
|
|
756
|
-
throw new Error("[Mailbox] Failed to decode file message");
|
|
757
|
-
return { data: JSON.parse(a), time: e.time, senderTag: t, uploadID: r, nonce: s, kind: b.file };
|
|
758
|
-
}
|
|
759
|
-
/**
|
|
760
|
-
* Attempts to decrypt a regular encrypted Zax message. Returns plain message if it was sent encrypted
|
|
761
|
-
*/
|
|
762
|
-
async parseTextMessage(e, t) {
|
|
763
|
-
let s = await this.decodeMessage(t, e.nonce, e.data);
|
|
764
|
-
return s || (s = e.data), { data: s, time: e.time, senderTag: t, nonce: e.nonce, kind: b.message };
|
|
765
|
-
}
|
|
766
|
-
/**
|
|
767
|
-
* Returns the number of messages in the mailbox on a given relay
|
|
768
|
-
*/
|
|
769
|
-
async count(e) {
|
|
770
|
-
const t = await this.prepareRelay(e), s = await this.runRelayCommand(t, l.count);
|
|
771
|
-
return await this.decryptResponse(t, s);
|
|
772
|
-
}
|
|
773
|
-
/**
|
|
774
|
-
* Deletes messages from a relay given a list of base64 message nonces,
|
|
775
|
-
* and returns the number of remaining messages
|
|
776
|
-
*/
|
|
777
|
-
async delete(e, t) {
|
|
778
|
-
const s = await this.prepareRelay(e), [n] = await this.runRelayCommand(s, l.delete, { payload: t });
|
|
779
|
-
return parseInt(n, 10);
|
|
780
|
-
}
|
|
781
|
-
/**
|
|
782
|
-
* Gets the status of a previously sent Zax message by a storage token.
|
|
783
|
-
* Returns "time to live" in seconds or a negative value if it's not applicable.
|
|
784
|
-
* See `MessageStatusResponse` values for reference
|
|
785
|
-
*/
|
|
786
|
-
async messageStatus(e, t) {
|
|
787
|
-
const s = await this.prepareRelay(e), [n] = await this.runRelayCommand(s, l.messageStatus, { token: t });
|
|
788
|
-
return parseInt(n, 10);
|
|
789
|
-
}
|
|
790
|
-
// ---------- Relay file commands (public API) ----------
|
|
791
|
-
/**
|
|
792
|
-
* Asks Zax to start a new upload session, and returns a unique file identifier
|
|
793
|
-
* required to upload file chunks.
|
|
794
|
-
*/
|
|
795
|
-
async startFileUpload(e, t, s) {
|
|
796
|
-
const n = await this.prepareRelay(e), r = this.getGuestKey(t), a = o.toBase64(await this.nacl.h2(o.fromBase64(r))), c = await this.nacl.random_bytes(this.nacl.crypto_secretbox_KEYBYTES);
|
|
797
|
-
s.skey = o.toBase64(c);
|
|
798
|
-
const d = await this.encodeMessage(t, JSON.stringify(s)), g = await this.runRelayCommand(n, l.startFileUpload, {
|
|
799
|
-
to: a,
|
|
800
|
-
file_size: s.orig_size,
|
|
801
|
-
metadata: d
|
|
802
|
-
}), m = await this.decryptResponse(n, g);
|
|
803
|
-
return m.skey = c, m;
|
|
804
|
-
}
|
|
805
|
-
/**
|
|
806
|
-
* Encrypts the file chunk symmetrically and transfers it to a relay
|
|
807
|
-
*/
|
|
808
|
-
async uploadFileChunk(e, t, s, n, r, a) {
|
|
809
|
-
const c = await this.prepareRelay(e), d = await p.encodeMessageSymmetric(s, a), g = await this.runRelayCommand(c, l.uploadFileChunk, {
|
|
810
|
-
uploadID: t,
|
|
811
|
-
part: n,
|
|
812
|
-
last_chunk: r - 1 === n,
|
|
813
|
-
// marker of the last chunk, sent only once
|
|
814
|
-
nonce: d.nonce
|
|
815
|
-
}, d.ctext);
|
|
816
|
-
return await this.decryptResponse(c, g);
|
|
817
|
-
}
|
|
818
|
-
/**
|
|
819
|
-
* Returns the status of a file upload by its relay-specific uploadID. Uploader can call it
|
|
820
|
-
* to verify the correct transfer, and downloader can check if the file exists and retrieve
|
|
821
|
-
* the number of chunks
|
|
822
|
-
*/
|
|
823
|
-
async getFileStatus(e, t) {
|
|
824
|
-
const s = await this.prepareRelay(e), n = await this.runRelayCommand(s, l.fileStatus, { uploadID: t });
|
|
825
|
-
return await this.decryptResponse(s, n);
|
|
826
|
-
}
|
|
827
|
-
/**
|
|
828
|
-
* Fetches the file metadata by uploadID, which was declared by the uploader
|
|
829
|
-
*/
|
|
830
|
-
async getFileMetadata(e, t) {
|
|
831
|
-
return (await this.download(e)).filter((r) => r.kind === "file").find((r) => r.uploadID === t)?.data;
|
|
832
|
-
}
|
|
833
|
-
/**
|
|
834
|
-
* Downloads a binary chunk of a file from a relay by a given uploadID. The total number of chunks
|
|
835
|
-
* can be retrieved via a `getFileStatus` request
|
|
836
|
-
*/
|
|
837
|
-
async downloadFileChunk(e, t, s, n) {
|
|
838
|
-
n instanceof Uint8Array || (n = o.fromBase64(n));
|
|
839
|
-
const r = await this.prepareRelay(e), a = await this.runRelayCommand(r, l.downloadFileChunk, { uploadID: t, part: s }), [c, d, g] = a, m = await r.decodeMessage(c, d);
|
|
840
|
-
return await p.decodeMessageSymmetric(m.nonce, g, n);
|
|
841
|
-
}
|
|
842
|
-
/**
|
|
843
|
-
* Deletes a file from the relay (or all chunks uploaded so far, if the upload was not completed).
|
|
844
|
-
* Can be called by either the sender or recipient
|
|
845
|
-
*/
|
|
846
|
-
async deleteFile(e, t) {
|
|
847
|
-
const s = await this.prepareRelay(e), n = await this.runRelayCommand(s, l.deleteFile, { uploadID: t });
|
|
848
|
-
return await this.decryptResponse(s, n);
|
|
849
|
-
}
|
|
850
|
-
// ---------- Dealing with Relay ----------
|
|
851
|
-
/**
|
|
852
|
-
* Establishes a session, exchanges temp keys and proves our ownership of this
|
|
853
|
-
* Mailbox to this specific relay. This is the first function to start
|
|
854
|
-
* communications with any relay. Returns the number of messages in the mailbox
|
|
855
|
-
*/
|
|
856
|
-
async connectToRelay(e) {
|
|
857
|
-
const t = this.relayFactory.getInstance(e), s = await t.openConnection(), n = await this.encryptSignature(s), r = await t.prove(await t.encodeMessage(JSON.stringify({
|
|
858
|
-
pub_key: this.keyRing.getPubCommKey(),
|
|
859
|
-
nonce: n.nonce,
|
|
860
|
-
ctext: n.ctext
|
|
861
|
-
})));
|
|
862
|
-
return parseInt(r, 10);
|
|
863
|
-
}
|
|
864
|
-
clearSession(e) {
|
|
865
|
-
const t = this.relayFactory.getInstance(e);
|
|
866
|
-
t.clearToken(), t.clearSession();
|
|
867
|
-
}
|
|
868
|
-
async encryptSignature(e) {
|
|
869
|
-
const t = o.fromBase64(this.keyRing.getPrivateCommKey());
|
|
870
|
-
return await p.encodeMessage(e.h2Signature, e.relayPublicKey, t);
|
|
871
|
-
}
|
|
872
|
-
/**
|
|
873
|
-
* Gets a singleton Relay instance, and reconnects to a relay if a previous token has expired
|
|
874
|
-
*/
|
|
875
|
-
async prepareRelay(e) {
|
|
876
|
-
const t = this.relayFactory.getInstance(e);
|
|
877
|
-
return await this.getRelayConnectionMutex(e).runExclusive(async () => {
|
|
878
|
-
t.isConnected || await this.connectToRelay(e);
|
|
879
|
-
}), t;
|
|
880
|
-
}
|
|
881
|
-
/**
|
|
882
|
-
* Encrypts the payload of the command and sends it to a relay
|
|
883
|
-
*/
|
|
884
|
-
async runRelayCommand(e, t, s, n) {
|
|
885
|
-
s = { cmd: t, ...s };
|
|
886
|
-
const r = await this.keyRing.getHpk(), a = await e.encodeMessage(JSON.stringify(s));
|
|
887
|
-
return await e.runCmd(t, r, a, n);
|
|
888
|
-
}
|
|
889
|
-
/**
|
|
890
|
-
* Parses relay's response to a command, for those commands that expect an encrypted message in return.
|
|
891
|
-
* Two lines of POST response will be nonce and ctext
|
|
892
|
-
*/
|
|
893
|
-
async decryptResponse(e, t) {
|
|
894
|
-
const [s, n] = t;
|
|
895
|
-
return await e.decodeMessage(s, n);
|
|
896
|
-
}
|
|
897
|
-
// ---------- Message encoding / decoding ----------
|
|
898
|
-
/**
|
|
899
|
-
* Encodes `message` to the guest key of a guest already
|
|
900
|
-
* added to the keyring
|
|
901
|
-
*/
|
|
902
|
-
async encodeMessage(e, t) {
|
|
903
|
-
const s = this.getGuestKey(e), n = this.keyRing.getPrivateCommKey();
|
|
904
|
-
return await p.encodeMessage(
|
|
905
|
-
await this.nacl.encode_utf8(t),
|
|
906
|
-
o.fromBase64(s),
|
|
907
|
-
o.fromBase64(n)
|
|
908
|
-
);
|
|
909
|
-
}
|
|
910
|
-
/**
|
|
911
|
-
* Decodes a ciphertext from a guest key already in our keyring with this nonce
|
|
912
|
-
* @returns null if failed to decode
|
|
913
|
-
*/
|
|
914
|
-
async decodeMessage(e, t, s) {
|
|
915
|
-
const n = this.getGuestKey(e), r = this.keyRing.getPrivateCommKey();
|
|
916
|
-
let a;
|
|
917
|
-
try {
|
|
918
|
-
a = o.fromBase64(s);
|
|
919
|
-
} catch {
|
|
920
|
-
return null;
|
|
921
|
-
}
|
|
922
|
-
return await p.decodeMessage(
|
|
923
|
-
o.fromBase64(t),
|
|
924
|
-
a,
|
|
925
|
-
o.fromBase64(n),
|
|
926
|
-
o.fromBase64(r)
|
|
927
|
-
);
|
|
928
|
-
}
|
|
929
|
-
/**
|
|
930
|
-
* Wrapper around `keyring.getGuestKey` that handles unknown guests
|
|
931
|
-
*/
|
|
932
|
-
getGuestKey(e) {
|
|
933
|
-
const t = this.keyRing.getGuestKey(e);
|
|
934
|
-
if (!t)
|
|
935
|
-
throw new Error(`[Mailbox] Unknown guest ${e}`);
|
|
936
|
-
return t;
|
|
937
|
-
}
|
|
938
|
-
// ---------- Destroying Mailbox ----------
|
|
939
|
-
/**
|
|
940
|
-
* Deletes a Mailbox and all its data from local CryptoStorage. This is a very
|
|
941
|
-
* destructive operation, use with caution - it will delete the Mailbox
|
|
942
|
-
* keyring along with all stored public keys. To restore that information, you
|
|
943
|
-
* will need to do another key exchange with all the guests on your keyring.
|
|
944
|
-
*/
|
|
945
|
-
async selfDestruct() {
|
|
946
|
-
await this.keyRing.selfDestruct();
|
|
947
|
-
}
|
|
948
|
-
getRelayConnectionMutex(e) {
|
|
949
|
-
return this.relayConnectionMutexes.has(e) || this.relayConnectionMutexes.set(e, new R()), this.relayConnectionMutexes.get(e);
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
class A {
|
|
953
|
-
constructor() {
|
|
954
|
-
this.storage = {};
|
|
955
|
-
}
|
|
956
|
-
async get(e) {
|
|
957
|
-
return (await this.getMultiple([e]))[0];
|
|
958
|
-
}
|
|
959
|
-
async getMultiple(e) {
|
|
960
|
-
return e.map((t) => this.storage[t]);
|
|
961
|
-
}
|
|
962
|
-
set(e, t) {
|
|
963
|
-
return this.setMultiple({ [e]: t });
|
|
964
|
-
}
|
|
965
|
-
async setMultiple(e) {
|
|
966
|
-
for (const t in e)
|
|
967
|
-
this.storage[t] = e[t];
|
|
968
|
-
}
|
|
969
|
-
remove(e) {
|
|
970
|
-
return this.removeMultiple([e]);
|
|
971
|
-
}
|
|
972
|
-
async removeMultiple(e) {
|
|
973
|
-
e.forEach((t) => delete this.storage[t]);
|
|
974
|
-
}
|
|
975
|
-
reset() {
|
|
976
|
-
this.storage = {};
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
export {
|
|
980
|
-
E as CryptoStorage,
|
|
981
|
-
_ as GlowNetworkError,
|
|
982
|
-
A as InMemoryStorage,
|
|
983
|
-
B as JsNaClDriver,
|
|
984
|
-
S as KeyRing,
|
|
985
|
-
w as Keys,
|
|
986
|
-
M as LocalStorageDriver,
|
|
987
|
-
x as Mailbox,
|
|
988
|
-
h as NaCl,
|
|
989
|
-
N as Relay,
|
|
990
|
-
o as Utils,
|
|
991
|
-
b as ZaxMessageKind
|
|
992
|
-
};
|
|
993
|
-
//# sourceMappingURL=glow.es.js.map
|
|
719
|
+
//#endregion
|
|
720
|
+
export { f as CryptoStorage, v as GlowNetworkError, S as InMemoryStorage, o as JsNaClDriver, m as KeyRing, c as Keys, d as LocalStorageDriver, x as Mailbox, s as NaCl, y as Relay, a as Utils, _ as ZaxMessageKind };
|
|
721
|
+
|
|
722
|
+
//# sourceMappingURL=glow.es.js.map
|