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/js/Digest.js DELETED
@@ -1,45 +0,0 @@
1
- /**
2
- * Generates a SHA-1 digest of the supplied string, and returns
3
- * it as a base64 string optionally truncated to the first 'length'
4
- * characters.
5
- *
6
- * @param {string} message to digest.
7
- * @return {Promise<string>} base64-encoded digest.
8
- */
9
- export async function shortSafeDigest(message, length = 0) {
10
- const msgUint8 = new TextEncoder().encode(message)
11
- if (!crypto.subtle) {
12
- throw 'Requires secure origin (localhost or https:)'
13
- }
14
- const hashBuffer = await crypto.subtle.digest("SHA-1", msgUint8)
15
- const hashArray = new Uint8Array(hashBuffer)
16
- const byteString = String.fromCodePoint(...hashArray)
17
- const base64 = btoa(byteString)
18
- const base64Safe = base64
19
- .replaceAll('+','-')
20
- .replaceAll('/','_')
21
- .replaceAll('=','')
22
- return length ? base64Safe.substring(0, length) : base64Safe
23
- }
24
-
25
- /**
26
- * Generates a SHA-1 digest of the supplied string, and returns
27
- * it as a hex string optionally truncated to the first 'length'
28
- * characters.
29
- *
30
- * @param {string} message to digest.
31
- * @return {Promise<string>} hex-encoded digest.
32
- */
33
- export async function shortHexDigest(message, length = 0) {
34
- const msgUint8 = new TextEncoder().encode(message)
35
- if (!crypto.subtle) {
36
- throw 'Requires secure origin (localhost or https:)'
37
- }
38
- const hashBuffer = await crypto.subtle.digest("SHA-1", msgUint8)
39
- const hashArray = Array.from(new Uint8Array(hashBuffer))
40
- const hashHex = hashArray
41
- .map((b) => b.toString(16).padStart(2, '0'))
42
- .join('')
43
-
44
- return length ? hashHex.substring(0, length) : hashHex
45
- }
package/js/KeyPair.js DELETED
@@ -1,38 +0,0 @@
1
- const DEFAULT_ALGORITHM = {
2
- name: 'RSASSA-PKCS1-v1_5',
3
- modulusLength: 2048,
4
- publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
5
- hash: 'SHA-256'
6
- }
7
-
8
- export class KeyPair {
9
- #algorithm
10
- #keypair
11
-
12
- constructor(algorithm = DEFAULT_ALGORITHM) {
13
- this.#algorithm = algorithm
14
- if (!crypto.subtle) {
15
- throw 'Crypto.subtle requires secure environment'
16
- }
17
- }
18
-
19
- async generate() {
20
- this.#keypair = await crypto.subtle.generateKey(
21
- this.#algorithm,
22
- true,
23
- ['sign', 'verify']
24
- )
25
- }
26
-
27
- async publicJwk() {
28
- const publicKey = this.#keypair.publicKey
29
- const publicJwk = await crypto.subtle.exportKey('jwk', publicKey)
30
- return publicJwk
31
- }
32
-
33
- async privateJwk() {
34
- const privateKey = this.#keypair.privateKey
35
- const privateJwk = await crypto.subtle.exportKey('jwk', privateKey)
36
- return privateJwk
37
- }
38
- }
@@ -1,302 +0,0 @@
1
- import { Token } from './Token.js'
2
- import { KeyPair } from './KeyPair.js'
3
-
4
- /**
5
- * This helper provides the parent capability for a website to
6
- * check and activate a daemon, and to create a device offer for
7
- * subsequent claim by consumer devices or browsers.
8
- *
9
- * The helper can operate either browser- or server-side.
10
- *
11
- * Usage
12
- * =====
13
- *
14
- * // Create the helper for a child daemon.
15
- * const helper = new ParentHelper()
16
- *
17
- * // User-supplied callback function receives public JWK and returns URL path.
18
- * const callback = ({role, publicJwk}) => {daemon, source, iss}
19
- * await helper.init(callback)
20
- *
21
- * // Succeeds if the child daemon is activated.
22
- * await helper.checkActivate()
23
- *
24
- * // Returns the claim code and check state of a new device offer.
25
- * await helper.fetchOffer()
26
- *
27
- */
28
-
29
- /*
30
- * @typedef {Object} JsonWebKey
31
- * @typedef {'parent'} Role
32
- *
33
- * @typedef {Object} CallbackArg
34
- * @property {JsonWebKey} publicJwk
35
- * @property {Role} role
36
- *
37
- * @typedef {Object} CallbackReturn
38
- * @typedef {string} daemon the host name of the daemon.
39
- * @typedef {string | undefined} source the URL of the source HTML page if request made from browser.
40
- * @property {URL} signatory URL used in the iss field of the token.
41
- *
42
- * @typedef {Object} Signatory the object to be served on the iss URL.
43
- * @property {string} role which is 'parent'.
44
- * @property {JsonWebKey} publicJwk
45
-
46
- * @callback SignatoryCallback
47
- * @param {Signatory} signatory which is what the user should serve.
48
- * @returns {Promise<CallbackReturn>} user provides daemon name and public URL of the signatory.
49
- *
50
- * @typedef {string} TokenBase64
51
- *
52
- * @typedef {Object} OfferOptions
53
- * @property {'TRANSIENT' | 'PERMANENT'} type the type of the device.
54
- * @property {number} ttl the time-to-live of device (transient only), in seconds.
55
- * @property {number} expiry the number of seconds before the offer expires.
56
- */
57
-
58
- export class ParentHelper {
59
- #privateJwk // Signs the token used for activate and offer.
60
- #publicJwk // Verifies the signed token, must be made publicly visible.
61
- #daemon // The host name of the daemon being parented.
62
- #issUrl // Callback-supplied URL for the pubicly visible issuer object.
63
- #source // The HTML source URL, which must match origin: header iff present in daemon request.
64
- #token // The token used for check activate and offer calls to the daemon.
65
- #claimCode // The claim code returned by fetchOffer.
66
-
67
- /**
68
- * Generates the keypair for this instance and invokes callback for daemon
69
- * name and signatory path.
70
- *
71
- * The callback should:
72
- * 1. Save the generated public key such that it is publically visible.
73
- * 2. Return the externally addressable signatory path.
74
- *
75
- * @param {SignatoryCallback} signatoryCallback
76
- * @return {Promise<TokenBase64>}
77
- */
78
- async init(signatoryCallback) {
79
- await this.#generateKeyPair()
80
-
81
- const {
82
- daemon,
83
- source, // Only needed if offer request is made from a browser.
84
- issUrl
85
- } = await signatoryCallback({
86
- role: 'parent',
87
- publicJwk: this.#publicJwk
88
- })
89
-
90
- this.#daemon = daemon
91
- this.#source = source
92
- this.#issUrl = issUrl
93
-
94
- await this.#buildToken()
95
- }
96
-
97
- /**
98
- * Uses the token to check activate the daemon. An already
99
- * activated daemon with this parent is retained, or a new
100
- * daemon is created so long as the sub of the pre-generated
101
- * token matches a parent record in the provider.
102
- *
103
- * @throws {string} exception if daemon cannot be activated.
104
- */
105
- async checkActivate() {
106
- const url = new URL(`${this.#token.getAud()}/activate`)
107
- const token = await this.#token.asSignedBase64()
108
- const body = {}
109
- let response
110
- try {
111
- response = await fetch(url, {
112
- method: 'POST',
113
- headers: {
114
- 'x-tabserver-token': token,
115
- 'content-type': 'application/json'
116
- },
117
- body: JSON.stringify(body)
118
- })
119
- }
120
- catch (e) {
121
- console.error(e)
122
- throw `Cannot activate ${this.#token.getParty()}`
123
- }
124
- const json = await response.json()
125
- if ('error' in json) {
126
- throw json.error
127
- }
128
- }
129
-
130
- /**
131
- * Uses the token to create a device offer on the daemon with default
132
- * type 'TRANSIENT' and ttl of 300s.
133
- *
134
- * @param {'NO_CHECK' | 'DO_CHECK'} flow to use. For QR offers, use checked flow.
135
- * @param {OfferOptions} options to use when making the offer, if any.
136
- * @return {Promise<Offer>} offer claimCode and checkState ('NO_CHECK' or 'AWAIT_CHECK').
137
- */
138
- async makeOffer(flow, options = {}) {
139
- const {
140
- type = 'TRANSIENT',
141
- ttl = 300,
142
- expiry = 30
143
- } = options
144
-
145
- const url = new URL(`${this.#token.getAud()}/offer`)
146
- const token = await this.#token.asSignedBase64()
147
- const body = {
148
- role: 'party',
149
- type,
150
- ttl,
151
- description: `Offered by ${this.#token.getCounterparty()}`,
152
- expiry: new Date(Date.now() + 1000 * expiry),
153
- flow
154
- }
155
- const response = await fetch(url, {
156
- method: 'POST',
157
- headers: {
158
- 'x-tabserver-token': token,
159
- 'content-type': 'application/json'
160
- },
161
- body: JSON.stringify(body)
162
- })
163
- const json = await response.json()
164
- if ('error' in json) {
165
- throw json.error
166
- }
167
-
168
- const {
169
- claimCode,
170
- _checkState
171
- } = json.ok
172
-
173
- this.#claimCode = claimCode
174
-
175
- return json.ok
176
- }
177
-
178
- /**
179
- * Returns the check state for the offer. This is used when
180
- * awaiting claim.
181
- *
182
- * @return {Promise<void>}
183
- */
184
- async checkState() {
185
- const url = new URL(`${this.#token.getAud()}/offer/check`)
186
- url.searchParams.append('claimCode', this.#claimCode)
187
- const token = await this.#token.asSignedBase64()
188
- const response = await fetch(url, {
189
- headers: {
190
- 'x-tabserver-token': token
191
- }
192
- })
193
- const json = await response.json()
194
- if ('error' in json) {
195
- throw json.error
196
- }
197
-
198
- /**
199
- * Result has `claimCode` and `checkState`.
200
- */
201
- return json.ok.checkState
202
- }
203
-
204
- /**
205
- * Posts the check code that confirms the offering device has
206
- * got the expected claiming device, and returns the resulting check state.
207
- *
208
- * @param {string} checkCode the check code obtained from the claiming device.
209
- * @return {Promise<ConfirmResult>} the check state following confirmation.
210
- */
211
- async confirmClaim(checkCode) {
212
- const url = new URL(`${this.#token.getAud()}/offer/confirm`)
213
- const token = await this.#token.asSignedBase64()
214
- const body = {
215
- claimCode: this.#claimCode,
216
- checkCode
217
- }
218
- const response = await fetch(url, {
219
- method: 'POST',
220
- headers: {
221
- 'x-tabserver-token': token,
222
- 'content-type': 'application/json'
223
- },
224
- body: JSON.stringify(body)
225
- })
226
- const json = await response.json()
227
- if ('error' in json) {
228
- throw json.error
229
- }
230
- return json.ok
231
- }
232
-
233
- /**
234
- * Returns the daemon name.
235
- *
236
- * @return {string} daemon name, e.g. 'daemon.once.id'.
237
- */
238
- getDaemon() {
239
- return this.#daemon
240
- }
241
-
242
- /**
243
- * Returns the daemon origin, suitable for redirection or claiming
244
- * a code.
245
- *
246
- * @return {URL} daemon origin e.g. 'https://daemon.once.id'.
247
- */
248
- getDaemonUrl() {
249
- return new URL(`${this.#issUrl.protocol}//${this.#daemon}`)
250
- }
251
-
252
- /**
253
- * Generates the public and private keypair used for the check
254
- * activate and offer calls to the daemon.
255
- *
256
- * @return {Promise<void>}
257
- */
258
- async #generateKeyPair() {
259
- const keyPair = new KeyPair()
260
- await keyPair.generate()
261
- this.#privateJwk = await keyPair.privateJwk()
262
- this.#publicJwk = await keyPair.publicJwk()
263
- }
264
-
265
- /**
266
- * Builds the token used in the check activate and offer calls toh
267
- * the daemon.
268
- *
269
- * @return {Promise<void>}
270
- */
271
- async #buildToken() {
272
- const iss = this.#issUrl.toString()
273
- const aud = `${this.#issUrl.protocol}//${this.#daemon}`
274
- const sub = this.#issUrl.origin
275
-
276
- // The `src` attribute is origin and pathname only.
277
- let src
278
- if (this.#source) {
279
- const srcUrl = new URL(this.#source)
280
- src = `${srcUrl.origin}${srcUrl.pathname}`
281
- }
282
- else {
283
- src = 'party:control'
284
- }
285
-
286
- const payload = {
287
- iss,
288
- aud,
289
- sub,
290
- src,
291
- scope: {
292
- 'party:control': 'offer'
293
- },
294
- iat: Date.now(),
295
- exp: Date.now() + 1000 * 60 * 3
296
- }
297
-
298
- const token = new Token(payload)
299
- await token.signWith(Promise.resolve(this.#privateJwk))
300
- this.#token = token
301
- }
302
- }
package/js/QrCode.js DELETED
@@ -1,37 +0,0 @@
1
- /**
2
- * Utility class to generate a QR code img element which is placed
3
- * under a parent element.
4
- */
5
- const BASE_URL = 'https://qrcode.tec-it.com/API/QRCode'
6
- export const IMG_CLASS = 'qrcode'
7
-
8
- export class QrCode {
9
- #img
10
- #content
11
-
12
- /**
13
- * Constructor takes img element and
14
- * the string to show in the QR code.
15
- * @param {HTMLImageElement} img the image element to use.
16
- * @param {string} content the content of the qr code.
17
- */
18
- constructor(img, content) {
19
- this.#img = img
20
- this.#content = content
21
- }
22
-
23
- /**
24
- * Returns a promise that is resolved when the image loads
25
- * successfully, or rejected if the image load fails.
26
- */
27
- generate() {
28
- const url = new URL(BASE_URL)
29
- url.searchParams.append('data', this.#content)
30
- this.#img.classList.add(IMG_CLASS)
31
- this.#img.src = url.toString()
32
- return new Promise((resolve, reject) => {
33
- this.#img.onload = resolve
34
- this.#img.onerror = reject
35
- })
36
- }
37
- }
package/js/README.html DELETED
@@ -1,13 +0,0 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <meta charset="UTF-8">
5
- <title>Readme</title>
6
- </head>
7
- <body>
8
- <h1 id="javascript-library">Javascript library</h1>
9
- <p>This directory holds Javascript source files designed to be used in
10
- either the browser or a daemon runner, served from a public site such as
11
- https://webdaemon.online.</p>
12
- </body>
13
- </html>
package/js/README.md DELETED
@@ -1,4 +0,0 @@
1
- # Javascript library
2
- This directory holds Javascript source files designed to be
3
- used in either the browser or a daemon runner, served from
4
- a public site such as https://webdaemon.online.
package/js/README.pdf DELETED
Binary file
package/js/Storage.js DELETED
@@ -1,155 +0,0 @@
1
- /**
2
- * Storage keys are 1 or more slash-separated components
3
- * using the characters a-z, A-Z, 0-9, hyphen and period.
4
- *
5
- * Examples:
6
- * - acmeSetting
7
- * - acme.com/applicant/22fc01-b0d3084870b-fcae49ac2-892668
8
- * - address/line1
9
- * - url/acme.com
10
- *
11
- * The key needs to work as part of a REST/HTTP pathname.
12
- */
13
- const KEY_PATTERN = /^[\w\-.]+(?:\/[\w\-.]+)*$/
14
-
15
- /**
16
- * As above, but % and _ are both allowed as a wildcards.
17
- */
18
- const KEYLIKE_PATTERN = /^[\w\-.%_]+(?:\/[\w\-.%_]+)*$/
19
-
20
- /**
21
- * @typedef {Object} Ok
22
- * @property {Plain} ok
23
- *
24
- * @typedef {Object} Error
25
- * @property {string} error
26
- */
27
-
28
- /**
29
- * Throws an exception if the key being used is not
30
- * in a valid format.
31
- */
32
- function assertValid(key) {
33
- if (key.match(KEY_PATTERN) == null) {
34
- throw `DaemonStorage: Invalid key: '${key}`
35
- }
36
- }
37
-
38
- /**
39
- * Throws an exception if the key like pattern being used
40
- * is not in a valid format.
41
- */
42
- function assertValidLike(keylike) {
43
- if (keylike.match(KEYLIKE_PATTERN) == null) {
44
- throw `DaemonStorage: Invalid like key: ${keylike}`
45
- }
46
- }
47
-
48
- /**
49
- * Returns the value as a JSON string
50
- *
51
- * @return {string} JSON string.
52
- * @throws {string} exception if the value is not JSON.
53
- */
54
- function serialise(value) {
55
- try {
56
- return JSON.stringify(value)
57
- }
58
- catch (_e) {
59
- throw `DaemonStorage: Invalid value`
60
- }
61
- }
62
-
63
- /**
64
- * Sets an item in daemon storage.
65
- *
66
- * The value must be an object or primitive
67
- * which is serialised before saving.
68
- *
69
- * @param {Token} token to use.
70
- * @param {string} key the item key.
71
- * @param {any} value the serialisable value for the item.
72
- * @return {Promise<Ok | Error>} ok or error object.
73
- */
74
- export async function setItem(token, key, value) {
75
- assertValid(key)
76
- const body = serialise(value)
77
- const url = `${token.getAud()}/storage/${key}`
78
- const response = await fetch(url, {
79
- method: 'PUT',
80
- headers: {
81
- 'X-Tabserver-Token': token.asSignedBase64(),
82
- 'Content-Type': 'application/json'
83
- },
84
- body
85
- })
86
- return response.json()
87
- }
88
-
89
- /**
90
- * Gets an item from daemon storage.
91
- *
92
- * Returns an 'ok' object with the value if present, otherwise
93
- * returns an 'error' object.
94
- *
95
- * @param {Token} token to use.
96
- * @param {string} key the item key to retrieve.
97
- * @return {Promise<Ok | Error>} ok object with value, or error.
98
- */
99
- export async function getItem(token, key) {
100
- assertValid(key)
101
- const url = `${token.getAud()}/storage/${key}`
102
- const response = await fetch(url, {
103
- method: 'GET',
104
- headers: {
105
- 'X-Tabserver-Token': token.asSignedBase64()
106
- }
107
- })
108
- return response.json()
109
- }
110
-
111
- /**
112
- * Gets zero or more items from daemon storage, whose
113
- * keys match the keyLike pattern. This is normally
114
- * something like 'my/prefix/%' which gets all values
115
- * with that prefix.
116
- *
117
- * You can use both '%' and '_' as wildcards for string
118
- * of any length and single character respectively.
119
- *
120
- * @param {Token} token to use.
121
- * @param {string} the item keylike pattern to match.
122
- * @return {Promise<Ok>} ok object with list, or error.
123
- */
124
- export async function getItemsLike(token, keylike) {
125
- assertValidLike(keylike)
126
- const url = new URL(`${token.getAud()}/storage`)
127
- url.searchParams.append('like', keylike)
128
- console.log(url)
129
- const response = await fetch(url, {
130
- method: 'GET',
131
- headers: {
132
- 'X-Tabserver-Token': token.asSignedBase64()
133
- }
134
- })
135
- return await response.json()
136
- }
137
-
138
- /**
139
- * Removes an item from daemon storage.
140
- *
141
- * @param {Token} token to use.
142
- * @param {string} key the item key to remove.
143
- * @return {Promise<Ok | Error>} ok or error object.
144
- */
145
- export async function removeItem(token, key) {
146
- assertValid(key)
147
- const url =`${token.getAud()}/storage/${key}`
148
- const response = await fetch(url, {
149
- method: 'DELETE',
150
- headers: {
151
- 'X-Tabserver-Token': token.asSignedBase64()
152
- }
153
- })
154
- return response.json()
155
- }