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/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
|
-
}
|
package/js/ParentHelper.js
DELETED
|
@@ -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
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
|
-
}
|