webdaemon 11.4.2 → 11.4.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/index.js +1627 -0
- package/dist/index.js.map +7 -0
- package/package.json +16 -3
- package/index.js +0 -17
- package/js/Alert.js +0 -33
- package/js/BrowserApp.js +0 -334
- package/js/Digest.js +0 -45
- package/js/KeyPair.js +0 -38
- package/js/ParentHelper.js +0 -302
- package/js/QrCode.js +0 -37
- package/js/README.html +0 -13
- package/js/README.md +0 -4
- package/js/README.pdf +0 -0
- package/js/Storage.js +0 -155
- package/js/Token.js +0 -529
- package/package.json.template +0 -13
- package/ts/Assertions.ts +0 -41
- package/ts/Lifecycle.ts +0 -307
- package/ts/README.html +0 -13
- package/ts/README.md +0 -4
- package/ts/README.pdf +0 -0
- package/ts/Requests.ts +0 -131
- package/ts/Responses.ts +0 -132
package/dist/index.js
ADDED
|
@@ -0,0 +1,1627 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defProps = Object.defineProperties;
|
|
3
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
4
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
7
|
+
var __typeError = (msg) => {
|
|
8
|
+
throw TypeError(msg);
|
|
9
|
+
};
|
|
10
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
11
|
+
var __spreadValues = (a, b) => {
|
|
12
|
+
for (var prop in b || (b = {}))
|
|
13
|
+
if (__hasOwnProp.call(b, prop))
|
|
14
|
+
__defNormalProp(a, prop, b[prop]);
|
|
15
|
+
if (__getOwnPropSymbols)
|
|
16
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
17
|
+
if (__propIsEnum.call(b, prop))
|
|
18
|
+
__defNormalProp(a, prop, b[prop]);
|
|
19
|
+
}
|
|
20
|
+
return a;
|
|
21
|
+
};
|
|
22
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
23
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
24
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
25
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
26
|
+
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
27
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
28
|
+
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
29
|
+
var __async = (__this, __arguments, generator) => {
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
var fulfilled = (value) => {
|
|
32
|
+
try {
|
|
33
|
+
step(generator.next(value));
|
|
34
|
+
} catch (e) {
|
|
35
|
+
reject(e);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var rejected = (value) => {
|
|
39
|
+
try {
|
|
40
|
+
step(generator.throw(value));
|
|
41
|
+
} catch (e) {
|
|
42
|
+
reject(e);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
46
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// js/Digest.js
|
|
51
|
+
function shortSafeDigest(message, length = 0) {
|
|
52
|
+
return __async(this, null, function* () {
|
|
53
|
+
const msgUint8 = new TextEncoder().encode(message);
|
|
54
|
+
if (!crypto.subtle) {
|
|
55
|
+
throw "Requires secure origin (localhost or https:)";
|
|
56
|
+
}
|
|
57
|
+
const hashBuffer = yield crypto.subtle.digest("SHA-1", msgUint8);
|
|
58
|
+
const hashArray = new Uint8Array(hashBuffer);
|
|
59
|
+
const byteString = String.fromCodePoint(...hashArray);
|
|
60
|
+
const base64 = btoa(byteString);
|
|
61
|
+
const base64Safe = base64.replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
|
|
62
|
+
return length ? base64Safe.substring(0, length) : base64Safe;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function shortHexDigest(message, length = 0) {
|
|
66
|
+
return __async(this, null, function* () {
|
|
67
|
+
const msgUint8 = new TextEncoder().encode(message);
|
|
68
|
+
if (!crypto.subtle) {
|
|
69
|
+
throw "Requires secure origin (localhost or https:)";
|
|
70
|
+
}
|
|
71
|
+
const hashBuffer = yield crypto.subtle.digest("SHA-1", msgUint8);
|
|
72
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
73
|
+
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
74
|
+
return length ? hashHex.substring(0, length) : hashHex;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// js/KeyPair.js
|
|
79
|
+
var DEFAULT_ALGORITHM = {
|
|
80
|
+
name: "RSASSA-PKCS1-v1_5",
|
|
81
|
+
modulusLength: 2048,
|
|
82
|
+
publicExponent: new Uint8Array([1, 0, 1]),
|
|
83
|
+
hash: "SHA-256"
|
|
84
|
+
};
|
|
85
|
+
var _algorithm, _keypair;
|
|
86
|
+
var KeyPair = class {
|
|
87
|
+
constructor(algorithm = DEFAULT_ALGORITHM) {
|
|
88
|
+
__privateAdd(this, _algorithm);
|
|
89
|
+
__privateAdd(this, _keypair);
|
|
90
|
+
__privateSet(this, _algorithm, algorithm);
|
|
91
|
+
if (!crypto.subtle) {
|
|
92
|
+
throw "Crypto.subtle requires secure environment";
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
generate() {
|
|
96
|
+
return __async(this, null, function* () {
|
|
97
|
+
__privateSet(this, _keypair, yield crypto.subtle.generateKey(
|
|
98
|
+
__privateGet(this, _algorithm),
|
|
99
|
+
true,
|
|
100
|
+
["sign", "verify"]
|
|
101
|
+
));
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
publicJwk() {
|
|
105
|
+
return __async(this, null, function* () {
|
|
106
|
+
const publicKey = __privateGet(this, _keypair).publicKey;
|
|
107
|
+
const publicJwk = yield crypto.subtle.exportKey("jwk", publicKey);
|
|
108
|
+
return publicJwk;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
privateJwk() {
|
|
112
|
+
return __async(this, null, function* () {
|
|
113
|
+
const privateKey = __privateGet(this, _keypair).privateKey;
|
|
114
|
+
const privateJwk = yield crypto.subtle.exportKey("jwk", privateKey);
|
|
115
|
+
return privateJwk;
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
_algorithm = new WeakMap();
|
|
120
|
+
_keypair = new WeakMap();
|
|
121
|
+
|
|
122
|
+
// js/Token.js
|
|
123
|
+
var ALGORITHM = {
|
|
124
|
+
name: "RSASSA-PKCS1-v1_5",
|
|
125
|
+
hash: "SHA-256"
|
|
126
|
+
};
|
|
127
|
+
var MATCH_ORIGIN = /https?:\/\/[^\/]+$/;
|
|
128
|
+
var TOKEN_IAT_LEEWAY_MILLIS = 1e3 * 30;
|
|
129
|
+
var _payload, _signatureBase64, _audUrl, _subUrl, _srcUrl, _signatory;
|
|
130
|
+
var _Token = class _Token {
|
|
131
|
+
// Set upon successful verification of signature.
|
|
132
|
+
/**
|
|
133
|
+
* Constructor takes either a signed base64 token, or a payload object.
|
|
134
|
+
*
|
|
135
|
+
* If successful, the payload and optionally the signature fields are
|
|
136
|
+
* populated.
|
|
137
|
+
*
|
|
138
|
+
* @param {object | string} source a payload, or a base64 token
|
|
139
|
+
*/
|
|
140
|
+
constructor(source) {
|
|
141
|
+
// Specification per String.split() function.
|
|
142
|
+
/** @type{Token} */
|
|
143
|
+
__privateAdd(this, _payload, null);
|
|
144
|
+
/** @type{string} */
|
|
145
|
+
__privateAdd(this, _signatureBase64, null);
|
|
146
|
+
/** @type{URL} */
|
|
147
|
+
__privateAdd(this, _audUrl);
|
|
148
|
+
/** @type{URL} */
|
|
149
|
+
__privateAdd(this, _subUrl);
|
|
150
|
+
/** @type{URL} */
|
|
151
|
+
__privateAdd(this, _srcUrl);
|
|
152
|
+
/** @type {Signatory} */
|
|
153
|
+
__privateAdd(this, _signatory);
|
|
154
|
+
let payload = null;
|
|
155
|
+
if (typeof source == "object") {
|
|
156
|
+
payload = source;
|
|
157
|
+
} else if (typeof source == "string") {
|
|
158
|
+
try {
|
|
159
|
+
payload = JSON.parse(atob(source));
|
|
160
|
+
} catch (_e) {
|
|
161
|
+
throw "Invalid token format";
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
throw `Cannot construct token from ${typeof source}`;
|
|
165
|
+
}
|
|
166
|
+
__privateSet(this, _signatureBase64, payload.sig);
|
|
167
|
+
delete payload.sig;
|
|
168
|
+
__privateSet(this, _payload, payload);
|
|
169
|
+
const {
|
|
170
|
+
iss,
|
|
171
|
+
aud,
|
|
172
|
+
sub,
|
|
173
|
+
src,
|
|
174
|
+
scope,
|
|
175
|
+
iat,
|
|
176
|
+
exp
|
|
177
|
+
} = payload;
|
|
178
|
+
if (!iss || !aud || !sub || !src || !scope || !iat || !exp) {
|
|
179
|
+
throw "Token must include iss, aud, sub, src, scope, iat and exp";
|
|
180
|
+
}
|
|
181
|
+
if (!aud.match(MATCH_ORIGIN) || !sub.match(MATCH_ORIGIN)) {
|
|
182
|
+
throw "The aud and sub attributes must be origins";
|
|
183
|
+
}
|
|
184
|
+
__privateSet(this, _audUrl, new URL(aud));
|
|
185
|
+
__privateSet(this, _subUrl, new URL(sub));
|
|
186
|
+
if (src.includes("?") || src.includes("#")) {
|
|
187
|
+
throw "The src attribute must have no query or fragment component.";
|
|
188
|
+
}
|
|
189
|
+
__privateSet(this, _srcUrl, new URL(src));
|
|
190
|
+
__privateSet(this, _payload, payload);
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Generates the signature for the object using the private key provided by the
|
|
194
|
+
* supplied callback and stores the result.
|
|
195
|
+
*
|
|
196
|
+
* @param {Promise<JsonWebKey>} privateJwkPromise the private key used for signing.
|
|
197
|
+
* @return {Promise<string>} resolved with the base64 signature if signed successfully.
|
|
198
|
+
*/
|
|
199
|
+
signWith(privateJwkPromise) {
|
|
200
|
+
return __async(this, null, function* () {
|
|
201
|
+
if (__privateGet(this, _payload) == null) {
|
|
202
|
+
throw "No payload to sign";
|
|
203
|
+
}
|
|
204
|
+
const privateJwk = yield privateJwkPromise;
|
|
205
|
+
const privateKey = yield crypto.subtle.importKey("jwk", privateJwk, ALGORITHM, true, ["sign"]);
|
|
206
|
+
const toSign = JSON.stringify(__privateGet(this, _payload));
|
|
207
|
+
const messageBuffer = new TextEncoder().encode(toSign).buffer;
|
|
208
|
+
const signature = yield crypto.subtle.sign(ALGORITHM, privateKey, messageBuffer);
|
|
209
|
+
const signatureBase64 = _Token.bytesToBase64(new Uint8Array(signature));
|
|
210
|
+
__privateSet(this, _signatureBase64, signatureBase64);
|
|
211
|
+
return signatureBase64;
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Retrieves the signatory using the `iss` field, checks the signature
|
|
216
|
+
* and if valid sets the signatory property in this instance.
|
|
217
|
+
*
|
|
218
|
+
* @throws {string} if signatory cannot be retrieved or signature doesn't match.
|
|
219
|
+
*/
|
|
220
|
+
verifySignatory() {
|
|
221
|
+
return __async(this, null, function* () {
|
|
222
|
+
yield this.fetchSignatory();
|
|
223
|
+
yield this.checkSignature(Promise.resolve(__privateGet(this, _signatory).jwk));
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Fetches the signatory from the `iss` URL, setting the instance
|
|
228
|
+
* property.
|
|
229
|
+
*
|
|
230
|
+
* The signatory comprises the `role` label and the public `jwk`.
|
|
231
|
+
*
|
|
232
|
+
* Caching may be introduced to reduce latency and bandwidth use.
|
|
233
|
+
*
|
|
234
|
+
* @throws {string} error if fetch fails or returns an error response.
|
|
235
|
+
*/
|
|
236
|
+
fetchSignatory() {
|
|
237
|
+
return __async(this, null, function* () {
|
|
238
|
+
const issuer = this.getIssuer();
|
|
239
|
+
try {
|
|
240
|
+
const response2 = yield fetch(issuer, {
|
|
241
|
+
headers: {
|
|
242
|
+
"content-type": "application/json"
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
const json = yield response2.json();
|
|
246
|
+
if ("error" in json) {
|
|
247
|
+
throw json.error;
|
|
248
|
+
}
|
|
249
|
+
__privateSet(this, _signatory, json.ok);
|
|
250
|
+
} catch (_e) {
|
|
251
|
+
throw `Failed to read signatory at ${issuer}`;
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Checks the signature using the public key provided by the supplied callback,
|
|
257
|
+
* and stores the result.
|
|
258
|
+
*
|
|
259
|
+
* @param {Promise<JsonWebKey>} publicJwkPromise the public key used to verify the signature.
|
|
260
|
+
* @throws {string} exception if signature cannot be verified or the public key is null or error.
|
|
261
|
+
*/
|
|
262
|
+
checkSignature(publicJwkPromise) {
|
|
263
|
+
return __async(this, null, function* () {
|
|
264
|
+
const publicJwk = yield publicJwkPromise;
|
|
265
|
+
if (!publicJwk || "error" in publicJwk) {
|
|
266
|
+
throw publicJwk.error;
|
|
267
|
+
}
|
|
268
|
+
const publicKey = yield crypto.subtle.importKey("jwk", publicJwk, ALGORITHM, true, ["verify"]);
|
|
269
|
+
const toCheck = JSON.stringify(__privateGet(this, _payload));
|
|
270
|
+
const messageBuffer = new TextEncoder().encode(toCheck).buffer;
|
|
271
|
+
const sigBuffer = _Token.base64ToBytes(__privateGet(this, _signatureBase64)).buffer;
|
|
272
|
+
const isVerified = yield crypto.subtle.verify(ALGORITHM, publicKey, sigBuffer, messageBuffer);
|
|
273
|
+
if (!isVerified) {
|
|
274
|
+
throw "Signature cannot be verified";
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Returns the payload with the `sig` property added.
|
|
280
|
+
*
|
|
281
|
+
* @return {SignedTokenPayload} the payload with additional `sig` property.
|
|
282
|
+
*/
|
|
283
|
+
getSignedPayload() {
|
|
284
|
+
if (__privateGet(this, _signatureBase64) === null) {
|
|
285
|
+
throw "Not yet signed";
|
|
286
|
+
}
|
|
287
|
+
return __spreadProps(__spreadValues({}, __privateGet(this, _payload)), {
|
|
288
|
+
sig: __privateGet(this, _signatureBase64)
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Returns the payload whether signed or not.
|
|
293
|
+
*
|
|
294
|
+
* @return {TokenPayload} the payload object without signature.
|
|
295
|
+
*/
|
|
296
|
+
getPayload() {
|
|
297
|
+
return __privateGet(this, _payload);
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Returns the `aud` field as a string.
|
|
301
|
+
*
|
|
302
|
+
* @return {string} the `aud` field value.
|
|
303
|
+
*/
|
|
304
|
+
getAud() {
|
|
305
|
+
return __privateGet(this, _audUrl).origin;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Returns the `aud` host which is the party name handling the request.
|
|
309
|
+
*
|
|
310
|
+
* @return {string} the party name.
|
|
311
|
+
*/
|
|
312
|
+
getParty() {
|
|
313
|
+
return __privateGet(this, _audUrl).host;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Returns the `sub` field as a string.
|
|
317
|
+
*
|
|
318
|
+
* @return {string} the `sub` field value.
|
|
319
|
+
*/
|
|
320
|
+
getSub() {
|
|
321
|
+
return __privateGet(this, _subUrl).origin;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Returns the `sub` host, which is the counterparty name making the
|
|
325
|
+
* request.
|
|
326
|
+
*
|
|
327
|
+
* @return {string} the counterparty name.
|
|
328
|
+
*/
|
|
329
|
+
getCounterparty() {
|
|
330
|
+
return __privateGet(this, _subUrl).host;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Returns the role of the signatory who signed on behalf of the
|
|
334
|
+
* counterparty.
|
|
335
|
+
*
|
|
336
|
+
* @returns {string} role of the verified signatory.
|
|
337
|
+
*/
|
|
338
|
+
getSignatoryRole() {
|
|
339
|
+
return __privateGet(this, _signatory).role;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Returns the `src` URL.
|
|
343
|
+
*
|
|
344
|
+
* @return {URL} the source URL.
|
|
345
|
+
*/
|
|
346
|
+
getSourceUrl() {
|
|
347
|
+
return __privateGet(this, _srcUrl);
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Returns the `issuer` field as a URL. The counterparty is inferred from
|
|
351
|
+
* the host name which generally matches the sub field.
|
|
352
|
+
*
|
|
353
|
+
* @return {URL} the issuer URL.
|
|
354
|
+
*/
|
|
355
|
+
getIssuer() {
|
|
356
|
+
return new URL(this.getPayload().iss);
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Returns the list of zero or more sources for which scope is requested.
|
|
360
|
+
*
|
|
361
|
+
* @return {string[]} a list of source URL strings.
|
|
362
|
+
*/
|
|
363
|
+
getSources() {
|
|
364
|
+
const sources = [];
|
|
365
|
+
for (const source in __privateGet(this, _payload).scope) {
|
|
366
|
+
sources.push(source);
|
|
367
|
+
}
|
|
368
|
+
return sources;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Returns the scope for the source as an array of capability tokens.
|
|
372
|
+
*
|
|
373
|
+
* These can be separated by any mix of space, comma, semicolon
|
|
374
|
+
* or vertical bar.
|
|
375
|
+
*
|
|
376
|
+
* @param {string} source whose capabilities are returned.
|
|
377
|
+
* @return {string[]} zero or more scope tokens.
|
|
378
|
+
*/
|
|
379
|
+
getCapabilities(source) {
|
|
380
|
+
const scope = __privateGet(this, _payload).scope;
|
|
381
|
+
if (!(source in scope)) {
|
|
382
|
+
throw `Source '${source}' not in scope`;
|
|
383
|
+
}
|
|
384
|
+
return scope[source].split(_Token.SCOPE_SEPARATOR);
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Returns true if the supplied capability is present in the token
|
|
388
|
+
* scope under the specified source, otherwise false.
|
|
389
|
+
*
|
|
390
|
+
* @param {string} source the source under which the capability is expected.
|
|
391
|
+
* @param {string} capability the capability being tested.
|
|
392
|
+
* @return {boolean} true if capability is present in the scope, otherwise false.
|
|
393
|
+
*/
|
|
394
|
+
hasCapability(source, capability) {
|
|
395
|
+
return this.getCapabilities(source).includes(capability);
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Checks the token is within the period defined by the `iat` and `exp`
|
|
399
|
+
* date fields.
|
|
400
|
+
*
|
|
401
|
+
* @throws {string} exception if not within valid period.
|
|
402
|
+
*/
|
|
403
|
+
checkPeriod() {
|
|
404
|
+
const {
|
|
405
|
+
iat,
|
|
406
|
+
exp
|
|
407
|
+
} = __privateGet(this, _payload);
|
|
408
|
+
const now = Date.now();
|
|
409
|
+
if (now < iat - TOKEN_IAT_LEEWAY_MILLIS) {
|
|
410
|
+
throw "Token is not yet valid";
|
|
411
|
+
}
|
|
412
|
+
if (now > exp) {
|
|
413
|
+
throw "Token has expired";
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Returns the signed object including the `sig` property as a base64 string.
|
|
418
|
+
*
|
|
419
|
+
* @return {string} the signed object as a base64 string.
|
|
420
|
+
*/
|
|
421
|
+
asSignedBase64() {
|
|
422
|
+
return btoa(JSON.stringify(this.getSignedPayload()));
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Returns a base64 JSON string corresponding to the provided byte array.
|
|
426
|
+
*
|
|
427
|
+
* @param {Uint8Array} bytes to be encoded as a base64 string.
|
|
428
|
+
* @returns {string} the encoded bytes.
|
|
429
|
+
*/
|
|
430
|
+
static bytesToBase64(bytes) {
|
|
431
|
+
const binString = Array.from(bytes, (x) => String.fromCodePoint(x)).join("");
|
|
432
|
+
return btoa(binString);
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Returns the byte array decoded from the base64 string.
|
|
436
|
+
*
|
|
437
|
+
* @param {string} base64
|
|
438
|
+
* @returns {Uint8Array} the decoded bytes.
|
|
439
|
+
*/
|
|
440
|
+
static base64ToBytes(base64) {
|
|
441
|
+
const binString = atob(base64);
|
|
442
|
+
return Uint8Array.from(binString, (m) => m.codePointAt(0));
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Returns a new token using the x-tabserver-token header on the
|
|
446
|
+
* request.
|
|
447
|
+
*
|
|
448
|
+
* @param {Request} the request.
|
|
449
|
+
* @return {Token | null} the token, or null if no header is present.
|
|
450
|
+
* @throws {string} exception if token cannot be constructed.
|
|
451
|
+
*/
|
|
452
|
+
static from(request) {
|
|
453
|
+
if (request.headers.has(_Token.TOKEN_HEADER)) {
|
|
454
|
+
return new _Token(request.headers.get(_Token.TOKEN_HEADER));
|
|
455
|
+
}
|
|
456
|
+
return null;
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Converts an object whose string keys map to token objects
|
|
460
|
+
* into URL-friendly search params suitable for use in a
|
|
461
|
+
* query string or fragment.
|
|
462
|
+
*
|
|
463
|
+
* The string keys are normally the audience hostname, or the
|
|
464
|
+
* special key 'party' which holds the identity token whose
|
|
465
|
+
* `aud` and `sub` are the same.
|
|
466
|
+
*
|
|
467
|
+
* @param {TokenSet} tokenSet the set of tokens keyed by string.
|
|
468
|
+
* @returns {string}
|
|
469
|
+
*/
|
|
470
|
+
static toSearchString(tokenSet) {
|
|
471
|
+
const searchParams = new URLSearchParams();
|
|
472
|
+
for (const key in tokenSet) {
|
|
473
|
+
const token = tokenSet[key];
|
|
474
|
+
const tokenBase64 = token.asSignedBase64();
|
|
475
|
+
searchParams.append(key, tokenBase64);
|
|
476
|
+
}
|
|
477
|
+
return searchParams.toString();
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
_payload = new WeakMap();
|
|
481
|
+
_signatureBase64 = new WeakMap();
|
|
482
|
+
_audUrl = new WeakMap();
|
|
483
|
+
_subUrl = new WeakMap();
|
|
484
|
+
_srcUrl = new WeakMap();
|
|
485
|
+
_signatory = new WeakMap();
|
|
486
|
+
// Standard sources.
|
|
487
|
+
__publicField(_Token, "Source", {
|
|
488
|
+
PARTY_CONTROL: "party:control"
|
|
489
|
+
// Party control subject, a pseudo-source with scope granted to counterparties.
|
|
490
|
+
});
|
|
491
|
+
// Standard counterparty pattern.
|
|
492
|
+
__publicField(_Token, "Counterparty", {
|
|
493
|
+
PUBLIC: "public"
|
|
494
|
+
// Public counterparty, matching requests with no host specified in sub.
|
|
495
|
+
});
|
|
496
|
+
// Standard signatory roles.
|
|
497
|
+
__publicField(_Token, "SignatoryRole", {
|
|
498
|
+
PARTY: "party",
|
|
499
|
+
PARENT: "parent"
|
|
500
|
+
});
|
|
501
|
+
// Standard scope labels.
|
|
502
|
+
__publicField(_Token, "Capability", {
|
|
503
|
+
OPEN: "open",
|
|
504
|
+
// Capability to open source files and start tab runners.
|
|
505
|
+
CLOSE: "close",
|
|
506
|
+
// Capability to close source files and stop tab runners.
|
|
507
|
+
GRANT: "grant",
|
|
508
|
+
// Capability to create relation records.
|
|
509
|
+
REVOKE: "revoke",
|
|
510
|
+
// Capability to delete relation records.
|
|
511
|
+
OFFER: "offer",
|
|
512
|
+
// Capability to create a party offer for a device to claim.
|
|
513
|
+
RETRACT: "retract",
|
|
514
|
+
// Capability to delete a party offer before it is claimed.
|
|
515
|
+
DELETE: "delete",
|
|
516
|
+
// Capability to delete devices.
|
|
517
|
+
CATALOG: "catalog",
|
|
518
|
+
// Capability to get sources, devices, alerts etc.
|
|
519
|
+
ALIAS: "alias",
|
|
520
|
+
// Capability to set the alias for a counterparty host.
|
|
521
|
+
UNALIAS: "unalias",
|
|
522
|
+
// Capability to unset the alias for a counterparty host.
|
|
523
|
+
GET_ITEM: "getitem",
|
|
524
|
+
// Capability to get a storage item.
|
|
525
|
+
SET_ITEM: "setitem",
|
|
526
|
+
// Capability to set (and remove) a storage item.
|
|
527
|
+
ALERT: "alert",
|
|
528
|
+
// Capability to post and delete alerts.
|
|
529
|
+
LOG: "log",
|
|
530
|
+
// Capability to view logs.
|
|
531
|
+
READ: "read",
|
|
532
|
+
// Capability to read from drive.
|
|
533
|
+
WRITE: "write"
|
|
534
|
+
// Capability to write to drive.
|
|
535
|
+
});
|
|
536
|
+
__publicField(_Token, "DEFAULT_EXPIRY_MILLIS", 1e3 * 60 * 60 * 24);
|
|
537
|
+
__publicField(_Token, "TOKEN_HEADER", "x-tabserver-token");
|
|
538
|
+
__publicField(_Token, "SCOPE_SEPARATOR", /[\s,;\|]+/);
|
|
539
|
+
var Token = _Token;
|
|
540
|
+
|
|
541
|
+
// js/BrowserApp.js
|
|
542
|
+
var PARTY_PARAM = "party";
|
|
543
|
+
var _instance, _appName, _appUrlKey, _partyKey, _tokensKey;
|
|
544
|
+
var _BrowserApp = class _BrowserApp {
|
|
545
|
+
/**
|
|
546
|
+
* Constructs a BrowserApp instance with the app name. This should be
|
|
547
|
+
* unique per origin. Spaces are _NOT_ allowed in this name.
|
|
548
|
+
*
|
|
549
|
+
* @param {string} appName a unique name for this app per origin.
|
|
550
|
+
*/
|
|
551
|
+
constructor(appName) {
|
|
552
|
+
__privateAdd(this, _appName);
|
|
553
|
+
__privateAdd(this, _appUrlKey);
|
|
554
|
+
__privateAdd(this, _partyKey);
|
|
555
|
+
__privateAdd(this, _tokensKey);
|
|
556
|
+
__privateSet(this, _appName, appName);
|
|
557
|
+
__privateSet(this, _partyKey, `${appName}Party`);
|
|
558
|
+
__privateSet(this, _tokensKey, `${appName}Tokens`);
|
|
559
|
+
__privateSet(this, _appUrlKey, `${appName}Url`);
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Gets the singleton instance of BrowserApp. On invocations
|
|
563
|
+
* after the first, the app name must be omitted or match
|
|
564
|
+
* the first-used app name.
|
|
565
|
+
*
|
|
566
|
+
* @param {string=} appName
|
|
567
|
+
* @return {Promise<BrowserApp>} instance of browser app.
|
|
568
|
+
*/
|
|
569
|
+
static getInstance(appName = null) {
|
|
570
|
+
return __async(this, null, function* () {
|
|
571
|
+
if (!appName && !__privateGet(_BrowserApp, _instance)) {
|
|
572
|
+
throw `Must provide app name`;
|
|
573
|
+
}
|
|
574
|
+
if (appName && __privateGet(_BrowserApp, _instance) && appName !== __privateGet(__privateGet(_BrowserApp, _instance), _appName)) {
|
|
575
|
+
throw `Cannot change app name to ${appName}`;
|
|
576
|
+
}
|
|
577
|
+
if (!__privateGet(_BrowserApp, _instance)) {
|
|
578
|
+
__privateSet(_BrowserApp, _instance, new _BrowserApp(appName));
|
|
579
|
+
yield __privateGet(_BrowserApp, _instance).init();
|
|
580
|
+
}
|
|
581
|
+
return __privateGet(_BrowserApp, _instance);
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Initialises the instance with the party name and tokens passed
|
|
586
|
+
* in the #fragment and ?query string search parameters.
|
|
587
|
+
*
|
|
588
|
+
* - party is passed in the special parameter 'party'.
|
|
589
|
+
* - tokens are passed in the remaining parameters keyed by host name.
|
|
590
|
+
*
|
|
591
|
+
* The entire #fragment is parsed into search params, if present.
|
|
592
|
+
* Query params whose name starts with the special character 'æ' are
|
|
593
|
+
* extracted, if present.
|
|
594
|
+
*
|
|
595
|
+
* The window location is replaced with the 'clean' version that
|
|
596
|
+
* no longer includes tokens.
|
|
597
|
+
*
|
|
598
|
+
* @return {Promise<BrowserApp>} instance promise.
|
|
599
|
+
* @throws {string} error if party token `src` does not match our window location.
|
|
600
|
+
*/
|
|
601
|
+
init() {
|
|
602
|
+
return __async(this, null, function* () {
|
|
603
|
+
const ourUrl = new URL(globalThis.location);
|
|
604
|
+
const tokenParams = new URLSearchParams(ourUrl.hash.substring(1));
|
|
605
|
+
ourUrl.hash = "";
|
|
606
|
+
for (const [key, value] of ourUrl.searchParams) {
|
|
607
|
+
if (key.startsWith("\xE6")) {
|
|
608
|
+
tokenParams.set(key.substring(1), value);
|
|
609
|
+
ourUrl.searchParams.delete(key);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
sessionStorage[__privateGet(this, _appUrlKey)] = ourUrl.toString();
|
|
613
|
+
if (tokenParams.has(PARTY_PARAM)) {
|
|
614
|
+
sessionStorage[__privateGet(this, _tokensKey)] = tokenParams.toString();
|
|
615
|
+
const identityToken = this.getToken();
|
|
616
|
+
yield identityToken.verifySignatory();
|
|
617
|
+
sessionStorage[__privateGet(this, _partyKey)] = identityToken.getParty();
|
|
618
|
+
const srcUrl = identityToken.getSourceUrl();
|
|
619
|
+
if (srcUrl.origin !== ourUrl.origin || srcUrl.pathname !== ourUrl.pathname) {
|
|
620
|
+
throw `Party token is for different app: ${srcUrl}`;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
globalThis.history.replaceState(null, "", ourUrl.toString());
|
|
624
|
+
return this;
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Returns true if this app is an orphan, i.e. has not been launched from
|
|
629
|
+
* a Dæmon shell.
|
|
630
|
+
*
|
|
631
|
+
* Otherwise returns false.
|
|
632
|
+
*
|
|
633
|
+
* @return {boolean} true if an orphan without Dæmon, otherwise false.
|
|
634
|
+
*/
|
|
635
|
+
isOrphan() {
|
|
636
|
+
return sessionStorage[__privateGet(this, _partyKey)] == void 0 || sessionStorage[__privateGet(this, _tokensKey)] == void 0;
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Returns the app name. This is normally used to disambiguate properties
|
|
640
|
+
* of this app from others, such as those sharing a web origin and therefore
|
|
641
|
+
* sharing localStorage or sessionStorage.
|
|
642
|
+
*
|
|
643
|
+
* Note that this is a client-side name not known to the tabserver.
|
|
644
|
+
*
|
|
645
|
+
* @return {string} the app name.
|
|
646
|
+
*/
|
|
647
|
+
getAppName() {
|
|
648
|
+
return __privateGet(this, _appName);
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Returns the app url, obtained from window.location at time of app init,
|
|
652
|
+
* or the empty string if not initialised.
|
|
653
|
+
*
|
|
654
|
+
* @return {string} the app url, or the empty string.
|
|
655
|
+
*/
|
|
656
|
+
getAppUrl() {
|
|
657
|
+
return sessionStorage[__privateGet(this, _appUrlKey)] || "";
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Returns the party hostname, being the Dæmon that launched this app.
|
|
661
|
+
*
|
|
662
|
+
* @return {string} the party (Dæmon) hostname.
|
|
663
|
+
*/
|
|
664
|
+
getParty() {
|
|
665
|
+
return sessionStorage[__privateGet(this, _partyKey)];
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Returns the party origin, being the protocol (http: or https:)
|
|
669
|
+
* and the party hostname.
|
|
670
|
+
*
|
|
671
|
+
* @return {string} the party origin.
|
|
672
|
+
*/
|
|
673
|
+
getPartyOrigin() {
|
|
674
|
+
return `${globalThis.location.protocol}//${this.getParty()}`;
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Returns the base64-encoded token for the supplied party. If no
|
|
678
|
+
* party is specified, returns the token for the launching party.
|
|
679
|
+
*
|
|
680
|
+
* @param {string} party the party, or the launching party if not specified.
|
|
681
|
+
* @return {string} the base64-encoded token for the party.
|
|
682
|
+
*/
|
|
683
|
+
getTokenBase64(party = PARTY_PARAM) {
|
|
684
|
+
const tokens = sessionStorage[__privateGet(this, _tokensKey)];
|
|
685
|
+
const searchParams = new URLSearchParams(tokens);
|
|
686
|
+
const tokenBase64 = searchParams.get(party);
|
|
687
|
+
if (!tokenBase64) {
|
|
688
|
+
throw `No token for ${party}, check 'audience' in YML`;
|
|
689
|
+
}
|
|
690
|
+
return tokenBase64;
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Gets the token for a party either as a base64-encoded string, or as a
|
|
694
|
+
* Token object depending on the asBase64 argument.
|
|
695
|
+
*
|
|
696
|
+
* If the party is not passed or is null, the default is the launching party.
|
|
697
|
+
* If the asBase64 is not passed, the default is true.
|
|
698
|
+
*
|
|
699
|
+
* @param {string=} party the audience for the token. Defaults to the identity token 'party'.
|
|
700
|
+
* @return {Token} the pre-generated token for the given party.
|
|
701
|
+
*/
|
|
702
|
+
getToken(party = PARTY_PARAM) {
|
|
703
|
+
const tokenBase64 = this.getTokenBase64(party);
|
|
704
|
+
return new Token(tokenBase64);
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Gets the value of the named parameter, or null if no value is supplied.
|
|
708
|
+
*
|
|
709
|
+
* The parameter name is case insensitive.
|
|
710
|
+
*
|
|
711
|
+
* The parameter is specified as:
|
|
712
|
+
* - an attribute on the webdaemon meta tag, or
|
|
713
|
+
* - a search parameter in the query string of the URL
|
|
714
|
+
*
|
|
715
|
+
* where the query string parameter takes priority if present.
|
|
716
|
+
*
|
|
717
|
+
* @param {string} name
|
|
718
|
+
* @return {string | null} parameter value or null if not present.
|
|
719
|
+
*/
|
|
720
|
+
getParam(name) {
|
|
721
|
+
const searchParams = new URL(globalThis.location).searchParams;
|
|
722
|
+
for (const [paramName, value] of searchParams) {
|
|
723
|
+
if (name.toLowerCase() == paramName.toLowerCase()) {
|
|
724
|
+
return value;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
const metaElement = document.querySelector("link[rel=webdaemon]");
|
|
728
|
+
return metaElement.getAttribute(name);
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Returns true if the named parameter is present, even if with empty
|
|
732
|
+
* or null value.
|
|
733
|
+
*
|
|
734
|
+
* @param {string} name
|
|
735
|
+
* @return {boolean} true if the parameter exists, otherwise false.
|
|
736
|
+
*/
|
|
737
|
+
hasParam(name) {
|
|
738
|
+
const searchParams = new URL(globalThis.location).searchParams;
|
|
739
|
+
if (searchParams.has(name)) {
|
|
740
|
+
return true;
|
|
741
|
+
}
|
|
742
|
+
const metaElement = document.querySelector("link[rel=webdaemon]");
|
|
743
|
+
return metaElement.hasAttribute(name);
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Returns the currently raised launch alert for this app,
|
|
747
|
+
* or null if none.
|
|
748
|
+
*
|
|
749
|
+
* @return { Promise<AlertType | null> } raised launch alert, or null if none.
|
|
750
|
+
* @throws {string} exception if alert catalog cannot be retrieved.
|
|
751
|
+
*/
|
|
752
|
+
getLaunchAlert() {
|
|
753
|
+
return __async(this, null, function* () {
|
|
754
|
+
const origin = this.getPartyOrigin();
|
|
755
|
+
const url = new URL(`${origin}/catalog`);
|
|
756
|
+
url.searchParams.append("alert", "");
|
|
757
|
+
const response2 = yield fetch(url, {
|
|
758
|
+
headers: {
|
|
759
|
+
"X-Tabserver-Token": this.getTokenBase64(),
|
|
760
|
+
"Accept": "application/json"
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
const json = yield response2.json();
|
|
764
|
+
if ("error" in json) {
|
|
765
|
+
throw json.error;
|
|
766
|
+
}
|
|
767
|
+
const alert = json.ok.alert.find(
|
|
768
|
+
(alert2) => alert2.type == "LAUNCH" && alert2.sourceUrl == this.getAppUrl()
|
|
769
|
+
);
|
|
770
|
+
return alert || null;
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Resolves the currently raised launch alert for this app, if any.
|
|
775
|
+
*
|
|
776
|
+
* @throws {string} exception if alert cannot be resolved.
|
|
777
|
+
*/
|
|
778
|
+
resolveLaunchAlert() {
|
|
779
|
+
return __async(this, null, function* () {
|
|
780
|
+
const origin = this.getPartyOrigin();
|
|
781
|
+
const url = new URL(`${origin}/resolve`);
|
|
782
|
+
const response2 = yield fetch(url, {
|
|
783
|
+
method: "POST",
|
|
784
|
+
headers: {
|
|
785
|
+
"X-Tabserver-Token": this.getTokenBase64(),
|
|
786
|
+
"Content-Type": "application/json"
|
|
787
|
+
},
|
|
788
|
+
body: JSON.stringify({
|
|
789
|
+
type: "LAUNCH",
|
|
790
|
+
source: this.getAppUrl()
|
|
791
|
+
})
|
|
792
|
+
});
|
|
793
|
+
const json = yield response2.json();
|
|
794
|
+
if ("error" in json) {
|
|
795
|
+
throw json.error;
|
|
796
|
+
}
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Resolves the currently raised launch alert for this app, if any.
|
|
801
|
+
*
|
|
802
|
+
* @throws {string} exception if alert cannot be resolved.
|
|
803
|
+
*/
|
|
804
|
+
rejectLaunchAlert() {
|
|
805
|
+
return __async(this, null, function* () {
|
|
806
|
+
const origin = this.getPartyOrigin();
|
|
807
|
+
const url = new URL(`${origin}/reject`);
|
|
808
|
+
const response2 = yield fetch(url, {
|
|
809
|
+
method: "POST",
|
|
810
|
+
headers: {
|
|
811
|
+
"X-Tabserver-Token": this.getTokenBase64(),
|
|
812
|
+
"Content-Type": "application/json"
|
|
813
|
+
},
|
|
814
|
+
body: JSON.stringify({
|
|
815
|
+
type: "LAUNCH",
|
|
816
|
+
source: this.getAppUrl()
|
|
817
|
+
})
|
|
818
|
+
});
|
|
819
|
+
const json = yield response2.json();
|
|
820
|
+
if ("error" in json) {
|
|
821
|
+
throw json.error;
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
_instance = new WeakMap();
|
|
827
|
+
_appName = new WeakMap();
|
|
828
|
+
_appUrlKey = new WeakMap();
|
|
829
|
+
_partyKey = new WeakMap();
|
|
830
|
+
_tokensKey = new WeakMap();
|
|
831
|
+
__privateAdd(_BrowserApp, _instance);
|
|
832
|
+
var BrowserApp = _BrowserApp;
|
|
833
|
+
|
|
834
|
+
// js/Alert.js
|
|
835
|
+
function getLaunchAlert() {
|
|
836
|
+
return __async(this, null, function* () {
|
|
837
|
+
const app = yield BrowserApp.getInstance();
|
|
838
|
+
const origin = app.getPartyOrigin();
|
|
839
|
+
const url = new URL(`${origin}/catalog`);
|
|
840
|
+
url.searchParams.append("alert", "");
|
|
841
|
+
const response2 = yield fetch(url, {
|
|
842
|
+
headers: {
|
|
843
|
+
"X-Tabserver-Token": this.getTokenBase64(),
|
|
844
|
+
"Accept": "application/json"
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
const json = yield response2.json();
|
|
848
|
+
if ("error" in json) {
|
|
849
|
+
throw json.error;
|
|
850
|
+
}
|
|
851
|
+
const alert = json.ok.alert.find(
|
|
852
|
+
(alert2) => alert2.type == "LAUNCH" && alert2.sourceUrl == app.getAppUrl()
|
|
853
|
+
);
|
|
854
|
+
return alert || null;
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// js/ParentHelper.js
|
|
859
|
+
var _privateJwk, _publicJwk, _daemon, _issUrl, _source, _token, _claimCode, _ParentHelper_instances, generateKeyPair_fn, buildToken_fn;
|
|
860
|
+
var ParentHelper = class {
|
|
861
|
+
constructor() {
|
|
862
|
+
__privateAdd(this, _ParentHelper_instances);
|
|
863
|
+
__privateAdd(this, _privateJwk);
|
|
864
|
+
// Signs the token used for activate and offer.
|
|
865
|
+
__privateAdd(this, _publicJwk);
|
|
866
|
+
// Verifies the signed token, must be made publicly visible.
|
|
867
|
+
__privateAdd(this, _daemon);
|
|
868
|
+
// The host name of the daemon being parented.
|
|
869
|
+
__privateAdd(this, _issUrl);
|
|
870
|
+
// Callback-supplied URL for the pubicly visible issuer object.
|
|
871
|
+
__privateAdd(this, _source);
|
|
872
|
+
// The HTML source URL, which must match origin: header iff present in daemon request.
|
|
873
|
+
__privateAdd(this, _token);
|
|
874
|
+
// The token used for check activate and offer calls to the daemon.
|
|
875
|
+
__privateAdd(this, _claimCode);
|
|
876
|
+
}
|
|
877
|
+
// The claim code returned by fetchOffer.
|
|
878
|
+
/**
|
|
879
|
+
* Generates the keypair for this instance and invokes callback for daemon
|
|
880
|
+
* name and signatory path.
|
|
881
|
+
*
|
|
882
|
+
* The callback should:
|
|
883
|
+
* 1. Save the generated public key such that it is publically visible.
|
|
884
|
+
* 2. Return the externally addressable signatory path.
|
|
885
|
+
*
|
|
886
|
+
* @param {SignatoryCallback} signatoryCallback
|
|
887
|
+
* @return {Promise<TokenBase64>}
|
|
888
|
+
*/
|
|
889
|
+
init(signatoryCallback) {
|
|
890
|
+
return __async(this, null, function* () {
|
|
891
|
+
yield __privateMethod(this, _ParentHelper_instances, generateKeyPair_fn).call(this);
|
|
892
|
+
const {
|
|
893
|
+
daemon,
|
|
894
|
+
source,
|
|
895
|
+
// Only needed if offer request is made from a browser.
|
|
896
|
+
issUrl
|
|
897
|
+
} = yield signatoryCallback({
|
|
898
|
+
role: "parent",
|
|
899
|
+
publicJwk: __privateGet(this, _publicJwk)
|
|
900
|
+
});
|
|
901
|
+
__privateSet(this, _daemon, daemon);
|
|
902
|
+
__privateSet(this, _source, source);
|
|
903
|
+
__privateSet(this, _issUrl, issUrl);
|
|
904
|
+
yield __privateMethod(this, _ParentHelper_instances, buildToken_fn).call(this);
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* Uses the token to check activate the daemon. An already
|
|
909
|
+
* activated daemon with this parent is retained, or a new
|
|
910
|
+
* daemon is created so long as the sub of the pre-generated
|
|
911
|
+
* token matches a parent record in the provider.
|
|
912
|
+
*
|
|
913
|
+
* @throws {string} exception if daemon cannot be activated.
|
|
914
|
+
*/
|
|
915
|
+
checkActivate() {
|
|
916
|
+
return __async(this, null, function* () {
|
|
917
|
+
const url = new URL(`${__privateGet(this, _token).getAud()}/activate`);
|
|
918
|
+
const token = yield __privateGet(this, _token).asSignedBase64();
|
|
919
|
+
const body = {};
|
|
920
|
+
let response2;
|
|
921
|
+
try {
|
|
922
|
+
response2 = yield fetch(url, {
|
|
923
|
+
method: "POST",
|
|
924
|
+
headers: {
|
|
925
|
+
"x-tabserver-token": token,
|
|
926
|
+
"content-type": "application/json"
|
|
927
|
+
},
|
|
928
|
+
body: JSON.stringify(body)
|
|
929
|
+
});
|
|
930
|
+
} catch (e) {
|
|
931
|
+
console.error(e);
|
|
932
|
+
throw `Cannot activate ${__privateGet(this, _token).getParty()}`;
|
|
933
|
+
}
|
|
934
|
+
const json = yield response2.json();
|
|
935
|
+
if ("error" in json) {
|
|
936
|
+
throw json.error;
|
|
937
|
+
}
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Uses the token to create a device offer on the daemon with default
|
|
942
|
+
* type 'TRANSIENT' and ttl of 300s.
|
|
943
|
+
*
|
|
944
|
+
* @param {'NO_CHECK' | 'DO_CHECK'} flow to use. For QR offers, use checked flow.
|
|
945
|
+
* @param {OfferOptions} options to use when making the offer, if any.
|
|
946
|
+
* @return {Promise<Offer>} offer claimCode and checkState ('NO_CHECK' or 'AWAIT_CHECK').
|
|
947
|
+
*/
|
|
948
|
+
makeOffer(_0) {
|
|
949
|
+
return __async(this, arguments, function* (flow, options = {}) {
|
|
950
|
+
const {
|
|
951
|
+
type = "TRANSIENT",
|
|
952
|
+
ttl = 300,
|
|
953
|
+
expiry = 30
|
|
954
|
+
} = options;
|
|
955
|
+
const url = new URL(`${__privateGet(this, _token).getAud()}/offer`);
|
|
956
|
+
const token = yield __privateGet(this, _token).asSignedBase64();
|
|
957
|
+
const body = {
|
|
958
|
+
role: "party",
|
|
959
|
+
type,
|
|
960
|
+
ttl,
|
|
961
|
+
description: `Offered by ${__privateGet(this, _token).getCounterparty()}`,
|
|
962
|
+
expiry: new Date(Date.now() + 1e3 * expiry),
|
|
963
|
+
flow
|
|
964
|
+
};
|
|
965
|
+
const response2 = yield fetch(url, {
|
|
966
|
+
method: "POST",
|
|
967
|
+
headers: {
|
|
968
|
+
"x-tabserver-token": token,
|
|
969
|
+
"content-type": "application/json"
|
|
970
|
+
},
|
|
971
|
+
body: JSON.stringify(body)
|
|
972
|
+
});
|
|
973
|
+
const json = yield response2.json();
|
|
974
|
+
if ("error" in json) {
|
|
975
|
+
throw json.error;
|
|
976
|
+
}
|
|
977
|
+
const {
|
|
978
|
+
claimCode,
|
|
979
|
+
_checkState
|
|
980
|
+
} = json.ok;
|
|
981
|
+
__privateSet(this, _claimCode, claimCode);
|
|
982
|
+
return json.ok;
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
/**
|
|
986
|
+
* Returns the check state for the offer. This is used when
|
|
987
|
+
* awaiting claim.
|
|
988
|
+
*
|
|
989
|
+
* @return {Promise<void>}
|
|
990
|
+
*/
|
|
991
|
+
checkState() {
|
|
992
|
+
return __async(this, null, function* () {
|
|
993
|
+
const url = new URL(`${__privateGet(this, _token).getAud()}/offer/check`);
|
|
994
|
+
url.searchParams.append("claimCode", __privateGet(this, _claimCode));
|
|
995
|
+
const token = yield __privateGet(this, _token).asSignedBase64();
|
|
996
|
+
const response2 = yield fetch(url, {
|
|
997
|
+
headers: {
|
|
998
|
+
"x-tabserver-token": token
|
|
999
|
+
}
|
|
1000
|
+
});
|
|
1001
|
+
const json = yield response2.json();
|
|
1002
|
+
if ("error" in json) {
|
|
1003
|
+
throw json.error;
|
|
1004
|
+
}
|
|
1005
|
+
return json.ok.checkState;
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* Posts the check code that confirms the offering device has
|
|
1010
|
+
* got the expected claiming device, and returns the resulting check state.
|
|
1011
|
+
*
|
|
1012
|
+
* @param {string} checkCode the check code obtained from the claiming device.
|
|
1013
|
+
* @return {Promise<ConfirmResult>} the check state following confirmation.
|
|
1014
|
+
*/
|
|
1015
|
+
confirmClaim(checkCode) {
|
|
1016
|
+
return __async(this, null, function* () {
|
|
1017
|
+
const url = new URL(`${__privateGet(this, _token).getAud()}/offer/confirm`);
|
|
1018
|
+
const token = yield __privateGet(this, _token).asSignedBase64();
|
|
1019
|
+
const body = {
|
|
1020
|
+
claimCode: __privateGet(this, _claimCode),
|
|
1021
|
+
checkCode
|
|
1022
|
+
};
|
|
1023
|
+
const response2 = yield fetch(url, {
|
|
1024
|
+
method: "POST",
|
|
1025
|
+
headers: {
|
|
1026
|
+
"x-tabserver-token": token,
|
|
1027
|
+
"content-type": "application/json"
|
|
1028
|
+
},
|
|
1029
|
+
body: JSON.stringify(body)
|
|
1030
|
+
});
|
|
1031
|
+
const json = yield response2.json();
|
|
1032
|
+
if ("error" in json) {
|
|
1033
|
+
throw json.error;
|
|
1034
|
+
}
|
|
1035
|
+
return json.ok;
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
/**
|
|
1039
|
+
* Returns the daemon name.
|
|
1040
|
+
*
|
|
1041
|
+
* @return {string} daemon name, e.g. 'daemon.once.id'.
|
|
1042
|
+
*/
|
|
1043
|
+
getDaemon() {
|
|
1044
|
+
return __privateGet(this, _daemon);
|
|
1045
|
+
}
|
|
1046
|
+
/**
|
|
1047
|
+
* Returns the daemon origin, suitable for redirection or claiming
|
|
1048
|
+
* a code.
|
|
1049
|
+
*
|
|
1050
|
+
* @return {URL} daemon origin e.g. 'https://daemon.once.id'.
|
|
1051
|
+
*/
|
|
1052
|
+
getDaemonUrl() {
|
|
1053
|
+
return new URL(`${__privateGet(this, _issUrl).protocol}//${__privateGet(this, _daemon)}`);
|
|
1054
|
+
}
|
|
1055
|
+
};
|
|
1056
|
+
_privateJwk = new WeakMap();
|
|
1057
|
+
_publicJwk = new WeakMap();
|
|
1058
|
+
_daemon = new WeakMap();
|
|
1059
|
+
_issUrl = new WeakMap();
|
|
1060
|
+
_source = new WeakMap();
|
|
1061
|
+
_token = new WeakMap();
|
|
1062
|
+
_claimCode = new WeakMap();
|
|
1063
|
+
_ParentHelper_instances = new WeakSet();
|
|
1064
|
+
generateKeyPair_fn = function() {
|
|
1065
|
+
return __async(this, null, function* () {
|
|
1066
|
+
const keyPair = new KeyPair();
|
|
1067
|
+
yield keyPair.generate();
|
|
1068
|
+
__privateSet(this, _privateJwk, yield keyPair.privateJwk());
|
|
1069
|
+
__privateSet(this, _publicJwk, yield keyPair.publicJwk());
|
|
1070
|
+
});
|
|
1071
|
+
};
|
|
1072
|
+
buildToken_fn = function() {
|
|
1073
|
+
return __async(this, null, function* () {
|
|
1074
|
+
const iss = __privateGet(this, _issUrl).toString();
|
|
1075
|
+
const aud = `${__privateGet(this, _issUrl).protocol}//${__privateGet(this, _daemon)}`;
|
|
1076
|
+
const sub = __privateGet(this, _issUrl).origin;
|
|
1077
|
+
let src;
|
|
1078
|
+
if (__privateGet(this, _source)) {
|
|
1079
|
+
const srcUrl = new URL(__privateGet(this, _source));
|
|
1080
|
+
src = `${srcUrl.origin}${srcUrl.pathname}`;
|
|
1081
|
+
} else {
|
|
1082
|
+
src = "party:control";
|
|
1083
|
+
}
|
|
1084
|
+
const payload = {
|
|
1085
|
+
iss,
|
|
1086
|
+
aud,
|
|
1087
|
+
sub,
|
|
1088
|
+
src,
|
|
1089
|
+
scope: {
|
|
1090
|
+
"party:control": "offer"
|
|
1091
|
+
},
|
|
1092
|
+
iat: Date.now(),
|
|
1093
|
+
exp: Date.now() + 1e3 * 60 * 3
|
|
1094
|
+
};
|
|
1095
|
+
const token = new Token(payload);
|
|
1096
|
+
yield token.signWith(Promise.resolve(__privateGet(this, _privateJwk)));
|
|
1097
|
+
__privateSet(this, _token, token);
|
|
1098
|
+
});
|
|
1099
|
+
};
|
|
1100
|
+
|
|
1101
|
+
// js/QrCode.js
|
|
1102
|
+
var BASE_URL = "https://qrcode.tec-it.com/API/QRCode";
|
|
1103
|
+
var IMG_CLASS = "qrcode";
|
|
1104
|
+
var _img, _content;
|
|
1105
|
+
var QrCode = class {
|
|
1106
|
+
/**
|
|
1107
|
+
* Constructor takes img element and
|
|
1108
|
+
* the string to show in the QR code.
|
|
1109
|
+
* @param {HTMLImageElement} img the image element to use.
|
|
1110
|
+
* @param {string} content the content of the qr code.
|
|
1111
|
+
*/
|
|
1112
|
+
constructor(img, content) {
|
|
1113
|
+
__privateAdd(this, _img);
|
|
1114
|
+
__privateAdd(this, _content);
|
|
1115
|
+
__privateSet(this, _img, img);
|
|
1116
|
+
__privateSet(this, _content, content);
|
|
1117
|
+
}
|
|
1118
|
+
/**
|
|
1119
|
+
* Returns a promise that is resolved when the image loads
|
|
1120
|
+
* successfully, or rejected if the image load fails.
|
|
1121
|
+
*/
|
|
1122
|
+
generate() {
|
|
1123
|
+
const url = new URL(BASE_URL);
|
|
1124
|
+
url.searchParams.append("data", __privateGet(this, _content));
|
|
1125
|
+
__privateGet(this, _img).classList.add(IMG_CLASS);
|
|
1126
|
+
__privateGet(this, _img).src = url.toString();
|
|
1127
|
+
return new Promise((resolve, reject) => {
|
|
1128
|
+
__privateGet(this, _img).onload = resolve;
|
|
1129
|
+
__privateGet(this, _img).onerror = reject;
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
};
|
|
1133
|
+
_img = new WeakMap();
|
|
1134
|
+
_content = new WeakMap();
|
|
1135
|
+
|
|
1136
|
+
// js/Storage.js
|
|
1137
|
+
var KEY_PATTERN = /^[\w\-.]+(?:\/[\w\-.]+)*$/;
|
|
1138
|
+
var KEYLIKE_PATTERN = /^[\w\-.%_]+(?:\/[\w\-.%_]+)*$/;
|
|
1139
|
+
function assertValid(key) {
|
|
1140
|
+
if (key.match(KEY_PATTERN) == null) {
|
|
1141
|
+
throw `DaemonStorage: Invalid key: '${key}`;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
function assertValidLike(keylike) {
|
|
1145
|
+
if (keylike.match(KEYLIKE_PATTERN) == null) {
|
|
1146
|
+
throw `DaemonStorage: Invalid like key: ${keylike}`;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
function serialise(value) {
|
|
1150
|
+
try {
|
|
1151
|
+
return JSON.stringify(value);
|
|
1152
|
+
} catch (_e) {
|
|
1153
|
+
throw `DaemonStorage: Invalid value`;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
function setItem(token, key, value) {
|
|
1157
|
+
return __async(this, null, function* () {
|
|
1158
|
+
assertValid(key);
|
|
1159
|
+
const body = serialise(value);
|
|
1160
|
+
const url = `${token.getAud()}/storage/${key}`;
|
|
1161
|
+
const response2 = yield fetch(url, {
|
|
1162
|
+
method: "PUT",
|
|
1163
|
+
headers: {
|
|
1164
|
+
"X-Tabserver-Token": token.asSignedBase64(),
|
|
1165
|
+
"Content-Type": "application/json"
|
|
1166
|
+
},
|
|
1167
|
+
body
|
|
1168
|
+
});
|
|
1169
|
+
return response2.json();
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
function getItem(token, key) {
|
|
1173
|
+
return __async(this, null, function* () {
|
|
1174
|
+
assertValid(key);
|
|
1175
|
+
const url = `${token.getAud()}/storage/${key}`;
|
|
1176
|
+
const response2 = yield fetch(url, {
|
|
1177
|
+
method: "GET",
|
|
1178
|
+
headers: {
|
|
1179
|
+
"X-Tabserver-Token": token.asSignedBase64()
|
|
1180
|
+
}
|
|
1181
|
+
});
|
|
1182
|
+
return response2.json();
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
function getItemsLike(token, keylike) {
|
|
1186
|
+
return __async(this, null, function* () {
|
|
1187
|
+
assertValidLike(keylike);
|
|
1188
|
+
const url = new URL(`${token.getAud()}/storage`);
|
|
1189
|
+
url.searchParams.append("like", keylike);
|
|
1190
|
+
console.log(url);
|
|
1191
|
+
const response2 = yield fetch(url, {
|
|
1192
|
+
method: "GET",
|
|
1193
|
+
headers: {
|
|
1194
|
+
"X-Tabserver-Token": token.asSignedBase64()
|
|
1195
|
+
}
|
|
1196
|
+
});
|
|
1197
|
+
return yield response2.json();
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
function removeItem(token, key) {
|
|
1201
|
+
return __async(this, null, function* () {
|
|
1202
|
+
assertValid(key);
|
|
1203
|
+
const url = `${token.getAud()}/storage/${key}`;
|
|
1204
|
+
const response2 = yield fetch(url, {
|
|
1205
|
+
method: "DELETE",
|
|
1206
|
+
headers: {
|
|
1207
|
+
"X-Tabserver-Token": token.asSignedBase64()
|
|
1208
|
+
}
|
|
1209
|
+
});
|
|
1210
|
+
return response2.json();
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
// ts/Assertions.ts
|
|
1215
|
+
function notNull(value) {
|
|
1216
|
+
return value !== null;
|
|
1217
|
+
}
|
|
1218
|
+
function isDefined(value) {
|
|
1219
|
+
return value !== void 0;
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
// ts/Responses.ts
|
|
1223
|
+
function response(rvalue, extraHeaders) {
|
|
1224
|
+
var _a;
|
|
1225
|
+
if ("ok" in rvalue) {
|
|
1226
|
+
const body2 = JSON.stringify(rvalue);
|
|
1227
|
+
return new Response(body2, {
|
|
1228
|
+
status: 200,
|
|
1229
|
+
headers: __spreadProps(__spreadValues({}, extraHeaders), {
|
|
1230
|
+
"Content-Type": "application/json",
|
|
1231
|
+
"Access-Control-Allow-Origin": "*"
|
|
1232
|
+
})
|
|
1233
|
+
});
|
|
1234
|
+
}
|
|
1235
|
+
const error = rvalue.error;
|
|
1236
|
+
const [, status, message] = (_a = error.match(/(\d{3})\s(.+)/)) != null ? _a : [null, "200", error];
|
|
1237
|
+
const body = JSON.stringify({
|
|
1238
|
+
error: message
|
|
1239
|
+
});
|
|
1240
|
+
return new Response(body, {
|
|
1241
|
+
status: Number(status),
|
|
1242
|
+
headers: __spreadProps(__spreadValues({}, extraHeaders), {
|
|
1243
|
+
"Content-Type": "application/json",
|
|
1244
|
+
"Access-Control-Allow-Origin": "*"
|
|
1245
|
+
})
|
|
1246
|
+
});
|
|
1247
|
+
}
|
|
1248
|
+
function response404(json = {
|
|
1249
|
+
error: "404 Not Found"
|
|
1250
|
+
}) {
|
|
1251
|
+
const body = JSON.stringify(json);
|
|
1252
|
+
return new Response(body, {
|
|
1253
|
+
status: 404,
|
|
1254
|
+
headers: {
|
|
1255
|
+
"Content-Type": "application/json",
|
|
1256
|
+
"Access-Control-Allow-Origin": "*"
|
|
1257
|
+
}
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
function response302(url, extraHeaders = []) {
|
|
1261
|
+
return new Response(null, {
|
|
1262
|
+
status: 302,
|
|
1263
|
+
headers: __spreadValues({
|
|
1264
|
+
"Location": url.toString(),
|
|
1265
|
+
"Access-Control-Allow-Origin": "*"
|
|
1266
|
+
}, extraHeaders)
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
function buildCookie(payload) {
|
|
1270
|
+
const {
|
|
1271
|
+
name,
|
|
1272
|
+
value,
|
|
1273
|
+
domain,
|
|
1274
|
+
sameSite,
|
|
1275
|
+
path = "/",
|
|
1276
|
+
expires,
|
|
1277
|
+
secure = true,
|
|
1278
|
+
httpOnly = true
|
|
1279
|
+
} = payload;
|
|
1280
|
+
const builder = [];
|
|
1281
|
+
builder.push(`${encodeURIComponent(name)}=${encodeURIComponent(value)}`);
|
|
1282
|
+
if (domain) builder.push(`Domain=${domain}`);
|
|
1283
|
+
if (sameSite) builder.push(`SameSite=${sameSite}`);
|
|
1284
|
+
builder.push(`Path=${path}`);
|
|
1285
|
+
if (expires) builder.push(`Expires=${expires.toUTCString()}`);
|
|
1286
|
+
if (secure) builder.push(`Secure`);
|
|
1287
|
+
if (httpOnly) builder.push("HttpOnly");
|
|
1288
|
+
const cookie = builder.join("; ");
|
|
1289
|
+
return cookie;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
// ts/Lifecycle.ts
|
|
1293
|
+
var DAEMON_PREFIX = "daemon";
|
|
1294
|
+
var CONFIG_PATH = "config";
|
|
1295
|
+
var EVENT_PATH = "event";
|
|
1296
|
+
var PUBLIC_JWK_PATH = "public.jwk";
|
|
1297
|
+
var CONFIG_KEY = "config";
|
|
1298
|
+
var PRIVATE_JWK_KEY = "privateJwk";
|
|
1299
|
+
var PUBLIC_JWK_KEY = "publicJwk";
|
|
1300
|
+
var DEFAULT_TTL_MILLIS = 1e3 * 60 * 60;
|
|
1301
|
+
var _instance2;
|
|
1302
|
+
var _Lifecycle = class _Lifecycle extends EventTarget {
|
|
1303
|
+
static getInstance() {
|
|
1304
|
+
return __privateGet(this, _instance2);
|
|
1305
|
+
}
|
|
1306
|
+
/**
|
|
1307
|
+
* All system requests are under the /daemon/ path.
|
|
1308
|
+
*
|
|
1309
|
+
* @param {Request} request to be tested.
|
|
1310
|
+
* @returns {boolean} true if the request is a lifecycle request.
|
|
1311
|
+
*/
|
|
1312
|
+
static shouldHandle(request) {
|
|
1313
|
+
return _Lifecycle.isConfig(request) || _Lifecycle.isEvent(request) || _Lifecycle.isPublicJwk(request);
|
|
1314
|
+
}
|
|
1315
|
+
static isConfig(request) {
|
|
1316
|
+
const url = new URL(request.url);
|
|
1317
|
+
return request.method == "POST" && url.pathname == `/${DAEMON_PREFIX}/${CONFIG_PATH}` && request.headers.get("Content-Type") == "application/json";
|
|
1318
|
+
}
|
|
1319
|
+
static isEvent(request) {
|
|
1320
|
+
const url = new URL(request.url);
|
|
1321
|
+
return request.method == "POST" && url.pathname == `/${DAEMON_PREFIX}/${EVENT_PATH}` && request.headers.get("Content-Type") == "application/json";
|
|
1322
|
+
}
|
|
1323
|
+
static isPublicJwk(request) {
|
|
1324
|
+
const url = new URL(request.url);
|
|
1325
|
+
return request.method == "GET" && url.pathname == `/${DAEMON_PREFIX}/${PUBLIC_JWK_PATH}`;
|
|
1326
|
+
}
|
|
1327
|
+
/**
|
|
1328
|
+
* Handles a lifecycle request.
|
|
1329
|
+
*
|
|
1330
|
+
* @param {Request} request to be handled.
|
|
1331
|
+
* @returns {Response} response for caller.
|
|
1332
|
+
*/
|
|
1333
|
+
handler(request) {
|
|
1334
|
+
return __async(this, null, function* () {
|
|
1335
|
+
if (_Lifecycle.isConfig(request)) {
|
|
1336
|
+
return yield this.handleConfig(request);
|
|
1337
|
+
}
|
|
1338
|
+
if (_Lifecycle.isEvent(request)) {
|
|
1339
|
+
return yield this.handleEvent(request);
|
|
1340
|
+
}
|
|
1341
|
+
if (_Lifecycle.isPublicJwk(request)) {
|
|
1342
|
+
return yield this.handlePublicJwk();
|
|
1343
|
+
}
|
|
1344
|
+
return response({
|
|
1345
|
+
error: "Invalid daemon request"
|
|
1346
|
+
});
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
/**
|
|
1350
|
+
* Saves the lifecycle config object provided in the request body.
|
|
1351
|
+
*
|
|
1352
|
+
* Fires a 'config' lifecycle event.
|
|
1353
|
+
*
|
|
1354
|
+
* @param {Request} request containing the configuration json.
|
|
1355
|
+
* @return {Response} ok response.
|
|
1356
|
+
*/
|
|
1357
|
+
handleConfig(request) {
|
|
1358
|
+
return __async(this, null, function* () {
|
|
1359
|
+
const configJson = yield request.json();
|
|
1360
|
+
sessionStorage.setItem(CONFIG_KEY, JSON.stringify(configJson));
|
|
1361
|
+
this.dispatchEvent(new CustomEvent("config", {
|
|
1362
|
+
detail: configJson
|
|
1363
|
+
}));
|
|
1364
|
+
return response({
|
|
1365
|
+
ok: true
|
|
1366
|
+
});
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
/**
|
|
1370
|
+
* Fires a lifecycle event upon receiving an event request.
|
|
1371
|
+
*
|
|
1372
|
+
* @param {Request} request containing the event.
|
|
1373
|
+
* @return {Response} ok response.
|
|
1374
|
+
*/
|
|
1375
|
+
handleEvent(request) {
|
|
1376
|
+
return __async(this, null, function* () {
|
|
1377
|
+
const {
|
|
1378
|
+
type,
|
|
1379
|
+
payload
|
|
1380
|
+
} = yield request.json();
|
|
1381
|
+
this.dispatchEvent(new CustomEvent(type, {
|
|
1382
|
+
detail: payload
|
|
1383
|
+
}));
|
|
1384
|
+
return response({
|
|
1385
|
+
ok: true
|
|
1386
|
+
});
|
|
1387
|
+
});
|
|
1388
|
+
}
|
|
1389
|
+
/**
|
|
1390
|
+
* Responds with the JSON public key used to verify tokens
|
|
1391
|
+
* produced by this instance.
|
|
1392
|
+
*
|
|
1393
|
+
* @return {Response} the response containing the Signatory (role, jwk).
|
|
1394
|
+
*/
|
|
1395
|
+
handlePublicJwk() {
|
|
1396
|
+
return __async(this, null, function* () {
|
|
1397
|
+
const role = "party";
|
|
1398
|
+
const jwk = yield _Lifecycle.getPublicKey();
|
|
1399
|
+
return Response.json({
|
|
1400
|
+
ok: {
|
|
1401
|
+
role,
|
|
1402
|
+
jwk
|
|
1403
|
+
}
|
|
1404
|
+
});
|
|
1405
|
+
});
|
|
1406
|
+
}
|
|
1407
|
+
/**
|
|
1408
|
+
* Returns the configuration object stored on initialisation.
|
|
1409
|
+
* @return {DaemonConfig} the daemon configuration object.
|
|
1410
|
+
* @throws {string} exception if config is not yet initialised.
|
|
1411
|
+
*/
|
|
1412
|
+
static getConfig() {
|
|
1413
|
+
const json = sessionStorage.getItem(CONFIG_KEY);
|
|
1414
|
+
if (!json) {
|
|
1415
|
+
throw "DaemonConfig not ready";
|
|
1416
|
+
}
|
|
1417
|
+
return JSON.parse(json);
|
|
1418
|
+
}
|
|
1419
|
+
/**
|
|
1420
|
+
* Generates if necessary and returns a private key for
|
|
1421
|
+
* use in signing tokens.
|
|
1422
|
+
*
|
|
1423
|
+
* @returns {JSONWebKey} the private key for this session.
|
|
1424
|
+
*/
|
|
1425
|
+
static getPrivateKey() {
|
|
1426
|
+
return __async(this, null, function* () {
|
|
1427
|
+
const jsonWebKey = sessionStorage.getItem(PRIVATE_JWK_KEY);
|
|
1428
|
+
if (!jsonWebKey) {
|
|
1429
|
+
const keyPair = yield _Lifecycle.generateKeys();
|
|
1430
|
+
return keyPair.privateJwk();
|
|
1431
|
+
}
|
|
1432
|
+
return JSON.parse(jsonWebKey);
|
|
1433
|
+
});
|
|
1434
|
+
}
|
|
1435
|
+
/**
|
|
1436
|
+
* Generates if necessary and returns a public key for
|
|
1437
|
+
* use by third parties to verifying tokens we generate.
|
|
1438
|
+
*
|
|
1439
|
+
* @returns {JSONWebKey} the public key for this session.
|
|
1440
|
+
*/
|
|
1441
|
+
static getPublicKey() {
|
|
1442
|
+
return __async(this, null, function* () {
|
|
1443
|
+
const jsonWebKey = sessionStorage.getItem(PUBLIC_JWK_KEY);
|
|
1444
|
+
if (!jsonWebKey) {
|
|
1445
|
+
const keyPair = yield _Lifecycle.generateKeys();
|
|
1446
|
+
return keyPair.publicJwk();
|
|
1447
|
+
}
|
|
1448
|
+
return JSON.parse(jsonWebKey);
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1451
|
+
/**
|
|
1452
|
+
* Generates and stores the serialised public and private JWK keys in
|
|
1453
|
+
* sessionStorage which survives the life of this process instance.
|
|
1454
|
+
*
|
|
1455
|
+
* @returns {KeyPair} keyPair as generated and stored in sessionStorage.
|
|
1456
|
+
*/
|
|
1457
|
+
static generateKeys() {
|
|
1458
|
+
return __async(this, null, function* () {
|
|
1459
|
+
const keyPair = new KeyPair();
|
|
1460
|
+
yield keyPair.generate();
|
|
1461
|
+
const privateJwk = yield keyPair.privateJwk();
|
|
1462
|
+
const publicJwk = yield keyPair.publicJwk();
|
|
1463
|
+
sessionStorage.setItem(PRIVATE_JWK_KEY, JSON.stringify(privateJwk));
|
|
1464
|
+
sessionStorage.setItem(PUBLIC_JWK_KEY, JSON.stringify(publicJwk));
|
|
1465
|
+
return keyPair;
|
|
1466
|
+
});
|
|
1467
|
+
}
|
|
1468
|
+
/**
|
|
1469
|
+
* Returns a token suitable for the `x-tabserver-token` header
|
|
1470
|
+
* in a request to the supplied party with the required
|
|
1471
|
+
* scope.
|
|
1472
|
+
*
|
|
1473
|
+
* If party is omitted, it defaults to this party.
|
|
1474
|
+
* If
|
|
1475
|
+
*
|
|
1476
|
+
* @param {Scope} scope map of string source -> space-separated capabilities.
|
|
1477
|
+
* @param {string=} party the party host name to whom the request will be sent.
|
|
1478
|
+
* @param { src=} src the HTML page from which the request is (actually or notionally) made.
|
|
1479
|
+
* @param {number=} ttlMillis the lifetime of this token.
|
|
1480
|
+
* @returns {Token} the signed token.
|
|
1481
|
+
* @throws {string} if configuration not yet initialised.
|
|
1482
|
+
*
|
|
1483
|
+
*/
|
|
1484
|
+
static getTokenFor(_0) {
|
|
1485
|
+
return __async(this, arguments, function* (scope, party = null, src = null, ttlMillis = DEFAULT_TTL_MILLIS) {
|
|
1486
|
+
const config = _Lifecycle.getConfig();
|
|
1487
|
+
if (!config) {
|
|
1488
|
+
throw "Lifecycle configuration not yet available";
|
|
1489
|
+
}
|
|
1490
|
+
const {
|
|
1491
|
+
system: {
|
|
1492
|
+
protocol,
|
|
1493
|
+
party: us,
|
|
1494
|
+
issuer,
|
|
1495
|
+
source
|
|
1496
|
+
}
|
|
1497
|
+
} = config;
|
|
1498
|
+
const appSourceUrl = new URL(source);
|
|
1499
|
+
const aud = party ? `${protocol}//${party}` : `${protocol}//${us}`;
|
|
1500
|
+
const sub = `${protocol}//${us}`;
|
|
1501
|
+
const now = Date.now();
|
|
1502
|
+
const payload = {
|
|
1503
|
+
iss: issuer,
|
|
1504
|
+
aud,
|
|
1505
|
+
sub,
|
|
1506
|
+
src: src != null ? src : `${appSourceUrl.origin}${appSourceUrl.pathname}`,
|
|
1507
|
+
scope,
|
|
1508
|
+
iat: now,
|
|
1509
|
+
exp: now + ttlMillis
|
|
1510
|
+
};
|
|
1511
|
+
const token = new Token(payload);
|
|
1512
|
+
yield token.signWith(_Lifecycle.getPrivateKey());
|
|
1513
|
+
return token;
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
/**
|
|
1517
|
+
* Returns a token for our party with the setitem and getitem
|
|
1518
|
+
* capabilities on the party control source.
|
|
1519
|
+
*/
|
|
1520
|
+
static getStorageToken() {
|
|
1521
|
+
const {
|
|
1522
|
+
system: {
|
|
1523
|
+
party
|
|
1524
|
+
}
|
|
1525
|
+
} = _Lifecycle.getConfig();
|
|
1526
|
+
const scope = {
|
|
1527
|
+
[Token.Source.PARTY_CONTROL]: `${Token.Capability.GET_ITEM} ${Token.Capability.SET_ITEM}`
|
|
1528
|
+
};
|
|
1529
|
+
return _Lifecycle.getTokenFor(scope, party);
|
|
1530
|
+
}
|
|
1531
|
+
};
|
|
1532
|
+
_instance2 = new WeakMap();
|
|
1533
|
+
__privateAdd(_Lifecycle, _instance2, new _Lifecycle());
|
|
1534
|
+
var Lifecycle = _Lifecycle;
|
|
1535
|
+
|
|
1536
|
+
// ts/Requests.ts
|
|
1537
|
+
var X_FORWARDED_HOST = "x-forwarded-host";
|
|
1538
|
+
var X_FORWARDED_PROTO = "x-forwarded-proto";
|
|
1539
|
+
var X_FORWARDED_PATHNAME = "x-forwarded-pathname";
|
|
1540
|
+
function getClientProtocol(request) {
|
|
1541
|
+
if (request.headers.has(X_FORWARDED_PROTO)) {
|
|
1542
|
+
return request.headers.get(X_FORWARDED_PROTO) + ":";
|
|
1543
|
+
}
|
|
1544
|
+
return new URL(request.url).protocol;
|
|
1545
|
+
}
|
|
1546
|
+
function getClientHost(request) {
|
|
1547
|
+
if (request.headers.has(X_FORWARDED_HOST)) {
|
|
1548
|
+
return request.headers.get(X_FORWARDED_HOST) || "";
|
|
1549
|
+
}
|
|
1550
|
+
return new URL(request.url).host;
|
|
1551
|
+
}
|
|
1552
|
+
function getClientPathname(request) {
|
|
1553
|
+
if (request.headers.has(X_FORWARDED_PATHNAME)) {
|
|
1554
|
+
return request.headers.get(X_FORWARDED_PATHNAME) || "";
|
|
1555
|
+
}
|
|
1556
|
+
return new URL(request.url).pathname;
|
|
1557
|
+
}
|
|
1558
|
+
function getClientUrlPhp(request) {
|
|
1559
|
+
const protocol = getClientProtocol(request);
|
|
1560
|
+
const host = getClientHost(request);
|
|
1561
|
+
const pathname = getClientPathname(request);
|
|
1562
|
+
return new URL(`${protocol}//${host}${pathname}`);
|
|
1563
|
+
}
|
|
1564
|
+
function getClientUrl(request) {
|
|
1565
|
+
const originalUrl = new URL(request.url);
|
|
1566
|
+
const newUrl = getClientUrlPhp(request);
|
|
1567
|
+
newUrl.search = originalUrl.search;
|
|
1568
|
+
newUrl.hash = originalUrl.hash;
|
|
1569
|
+
return newUrl;
|
|
1570
|
+
}
|
|
1571
|
+
function getPathname(request) {
|
|
1572
|
+
const url = new URL(request.url);
|
|
1573
|
+
return url.pathname;
|
|
1574
|
+
}
|
|
1575
|
+
function getCookie(request, cookieName) {
|
|
1576
|
+
const cookies = getCookies(request);
|
|
1577
|
+
if (cookieName in cookies) {
|
|
1578
|
+
return cookies[cookieName];
|
|
1579
|
+
}
|
|
1580
|
+
return null;
|
|
1581
|
+
}
|
|
1582
|
+
function getCookies(request) {
|
|
1583
|
+
const cookies = {};
|
|
1584
|
+
const headerValue = request.headers.get("Cookie");
|
|
1585
|
+
if (headerValue) {
|
|
1586
|
+
for (const keyVal of headerValue.split("; ")) {
|
|
1587
|
+
const [key, value] = keyVal.split("=");
|
|
1588
|
+
cookies[key] = value;
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
return cookies;
|
|
1592
|
+
}
|
|
1593
|
+
export {
|
|
1594
|
+
BrowserApp,
|
|
1595
|
+
CONFIG_PATH,
|
|
1596
|
+
DAEMON_PREFIX,
|
|
1597
|
+
EVENT_PATH,
|
|
1598
|
+
IMG_CLASS,
|
|
1599
|
+
KeyPair,
|
|
1600
|
+
Lifecycle,
|
|
1601
|
+
PUBLIC_JWK_PATH,
|
|
1602
|
+
ParentHelper,
|
|
1603
|
+
QrCode,
|
|
1604
|
+
Token,
|
|
1605
|
+
buildCookie,
|
|
1606
|
+
getClientHost,
|
|
1607
|
+
getClientPathname,
|
|
1608
|
+
getClientProtocol,
|
|
1609
|
+
getClientUrl,
|
|
1610
|
+
getClientUrlPhp,
|
|
1611
|
+
getCookie,
|
|
1612
|
+
getCookies,
|
|
1613
|
+
getItem,
|
|
1614
|
+
getItemsLike,
|
|
1615
|
+
getLaunchAlert,
|
|
1616
|
+
getPathname,
|
|
1617
|
+
isDefined,
|
|
1618
|
+
notNull,
|
|
1619
|
+
removeItem,
|
|
1620
|
+
response,
|
|
1621
|
+
response302,
|
|
1622
|
+
response404,
|
|
1623
|
+
setItem,
|
|
1624
|
+
shortHexDigest,
|
|
1625
|
+
shortSafeDigest
|
|
1626
|
+
};
|
|
1627
|
+
//# sourceMappingURL=index.js.map
|