@monimesl/dot-simulator 0.1.0-alpha.10
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/README.md +20 -0
- package/dist/client/service-bridge.js +647 -0
- package/dist/client/service-bridge.js.map +1 -0
- package/dist/client/simulator.js +1386 -0
- package/dist/client/simulator.js.map +1 -0
- package/dist/client/webview-bridge.js +44 -0
- package/dist/client/webview-bridge.js.map +1 -0
- package/dist/node/index.d.ts +108 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +1477 -0
- package/dist/node/index.js.map +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1,1386 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
//#region ../../core/dist/index.js
|
|
3
|
+
function defineComponent(manifest) {
|
|
4
|
+
return {
|
|
5
|
+
props: {},
|
|
6
|
+
events: [],
|
|
7
|
+
...manifest
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
function componentRegistry(manifests) {
|
|
11
|
+
const registry = {};
|
|
12
|
+
for (const manifest of manifests) {
|
|
13
|
+
if (manifest.name in registry) throw new Error(`Component <${manifest.name}> is defined twice`);
|
|
14
|
+
registry[manifest.name] = manifest;
|
|
15
|
+
}
|
|
16
|
+
return registry;
|
|
17
|
+
}
|
|
18
|
+
const AmountInput = defineComponent({
|
|
19
|
+
name: "AmountInput",
|
|
20
|
+
props: {
|
|
21
|
+
value: "number",
|
|
22
|
+
currency: "string",
|
|
23
|
+
minorUnits: "number",
|
|
24
|
+
unit: ["minor", "major"],
|
|
25
|
+
label: "string",
|
|
26
|
+
placeholder: "string",
|
|
27
|
+
hint: "string",
|
|
28
|
+
error: "string",
|
|
29
|
+
disabled: "boolean"
|
|
30
|
+
},
|
|
31
|
+
events: ["change"],
|
|
32
|
+
children: "none"
|
|
33
|
+
});
|
|
34
|
+
const Button = defineComponent({
|
|
35
|
+
name: "Button",
|
|
36
|
+
props: {
|
|
37
|
+
variant: ["primary", "secondary"],
|
|
38
|
+
disabled: "boolean",
|
|
39
|
+
loading: "boolean"
|
|
40
|
+
},
|
|
41
|
+
events: ["click"],
|
|
42
|
+
children: "text"
|
|
43
|
+
});
|
|
44
|
+
const SIZES = [
|
|
45
|
+
"small",
|
|
46
|
+
"medium",
|
|
47
|
+
"large"
|
|
48
|
+
];
|
|
49
|
+
const SPACES = [
|
|
50
|
+
"none",
|
|
51
|
+
"small",
|
|
52
|
+
"medium",
|
|
53
|
+
"large"
|
|
54
|
+
];
|
|
55
|
+
const PADDING_PROPS = {
|
|
56
|
+
padding: SPACES,
|
|
57
|
+
paddingTop: SPACES,
|
|
58
|
+
paddingStart: SPACES,
|
|
59
|
+
paddingEnd: SPACES,
|
|
60
|
+
paddingBottom: SPACES
|
|
61
|
+
};
|
|
62
|
+
const ASPECTS = [
|
|
63
|
+
"1:1",
|
|
64
|
+
"4:3",
|
|
65
|
+
"3:2",
|
|
66
|
+
"16:9",
|
|
67
|
+
"3:4",
|
|
68
|
+
"2:3",
|
|
69
|
+
"9:16"
|
|
70
|
+
];
|
|
71
|
+
const TONES = [
|
|
72
|
+
"default",
|
|
73
|
+
"muted",
|
|
74
|
+
"info",
|
|
75
|
+
"success",
|
|
76
|
+
"warning",
|
|
77
|
+
"danger"
|
|
78
|
+
];
|
|
79
|
+
const STATUS_TONES = [
|
|
80
|
+
"default",
|
|
81
|
+
"info",
|
|
82
|
+
"success",
|
|
83
|
+
"warning",
|
|
84
|
+
"danger"
|
|
85
|
+
];
|
|
86
|
+
const ICON_NAMES = [
|
|
87
|
+
"arrow-down",
|
|
88
|
+
"arrow-left",
|
|
89
|
+
"arrow-right",
|
|
90
|
+
"arrow-up",
|
|
91
|
+
"bank",
|
|
92
|
+
"bell",
|
|
93
|
+
"bolt",
|
|
94
|
+
"calendar",
|
|
95
|
+
"card",
|
|
96
|
+
"cart",
|
|
97
|
+
"cash",
|
|
98
|
+
"check",
|
|
99
|
+
"chevron-down",
|
|
100
|
+
"chevron-left",
|
|
101
|
+
"chevron-right",
|
|
102
|
+
"chevron-up",
|
|
103
|
+
"clock",
|
|
104
|
+
"close",
|
|
105
|
+
"copy",
|
|
106
|
+
"download",
|
|
107
|
+
"edit",
|
|
108
|
+
"error",
|
|
109
|
+
"external-link",
|
|
110
|
+
"eye",
|
|
111
|
+
"eye-off",
|
|
112
|
+
"file",
|
|
113
|
+
"gift",
|
|
114
|
+
"globe",
|
|
115
|
+
"grid",
|
|
116
|
+
"headphones",
|
|
117
|
+
"heart",
|
|
118
|
+
"help",
|
|
119
|
+
"history",
|
|
120
|
+
"home",
|
|
121
|
+
"image",
|
|
122
|
+
"info",
|
|
123
|
+
"link",
|
|
124
|
+
"list",
|
|
125
|
+
"location",
|
|
126
|
+
"lock",
|
|
127
|
+
"logout",
|
|
128
|
+
"mail",
|
|
129
|
+
"microphone",
|
|
130
|
+
"minus",
|
|
131
|
+
"mobile",
|
|
132
|
+
"more",
|
|
133
|
+
"music",
|
|
134
|
+
"phone",
|
|
135
|
+
"playlist",
|
|
136
|
+
"plus",
|
|
137
|
+
"qr-code",
|
|
138
|
+
"receipt",
|
|
139
|
+
"refresh",
|
|
140
|
+
"search",
|
|
141
|
+
"send",
|
|
142
|
+
"settings",
|
|
143
|
+
"share",
|
|
144
|
+
"star",
|
|
145
|
+
"success",
|
|
146
|
+
"ticket",
|
|
147
|
+
"trash",
|
|
148
|
+
"upload",
|
|
149
|
+
"user",
|
|
150
|
+
"users",
|
|
151
|
+
"video",
|
|
152
|
+
"wallet",
|
|
153
|
+
"warning",
|
|
154
|
+
"water",
|
|
155
|
+
"wifi"
|
|
156
|
+
];
|
|
157
|
+
const Carousel = defineComponent({
|
|
158
|
+
name: "Carousel",
|
|
159
|
+
props: {
|
|
160
|
+
autoplay: "boolean",
|
|
161
|
+
interval: "number",
|
|
162
|
+
loop: "boolean",
|
|
163
|
+
indicator: ["dots", "none"],
|
|
164
|
+
gap: SPACES,
|
|
165
|
+
effect: ["slide", "scale"],
|
|
166
|
+
index: "number",
|
|
167
|
+
...PADDING_PROPS
|
|
168
|
+
},
|
|
169
|
+
events: ["change"],
|
|
170
|
+
children: "elements"
|
|
171
|
+
});
|
|
172
|
+
const Chip = defineComponent({
|
|
173
|
+
name: "Chip",
|
|
174
|
+
props: {
|
|
175
|
+
label: "string",
|
|
176
|
+
icon: ICON_NAMES,
|
|
177
|
+
selected: "boolean",
|
|
178
|
+
disabled: "boolean"
|
|
179
|
+
},
|
|
180
|
+
events: ["tap"],
|
|
181
|
+
children: "none"
|
|
182
|
+
});
|
|
183
|
+
const CodeInput = defineComponent({
|
|
184
|
+
name: "CodeInput",
|
|
185
|
+
props: {
|
|
186
|
+
value: "string",
|
|
187
|
+
length: "number",
|
|
188
|
+
label: "string",
|
|
189
|
+
hint: "string",
|
|
190
|
+
error: "string",
|
|
191
|
+
secure: "boolean",
|
|
192
|
+
disabled: "boolean"
|
|
193
|
+
},
|
|
194
|
+
events: ["change", "complete"],
|
|
195
|
+
children: "none"
|
|
196
|
+
});
|
|
197
|
+
const DetailRow = defineComponent({
|
|
198
|
+
name: "DetailRow",
|
|
199
|
+
props: {
|
|
200
|
+
label: "string",
|
|
201
|
+
value: "string",
|
|
202
|
+
tone: TONES,
|
|
203
|
+
weight: [
|
|
204
|
+
"regular",
|
|
205
|
+
"medium",
|
|
206
|
+
"bold"
|
|
207
|
+
],
|
|
208
|
+
...PADDING_PROPS
|
|
209
|
+
},
|
|
210
|
+
children: "none"
|
|
211
|
+
});
|
|
212
|
+
const Divider = defineComponent({
|
|
213
|
+
name: "Divider",
|
|
214
|
+
children: "none"
|
|
215
|
+
});
|
|
216
|
+
const EmptyState = defineComponent({
|
|
217
|
+
name: "EmptyState",
|
|
218
|
+
props: {
|
|
219
|
+
icon: ICON_NAMES,
|
|
220
|
+
title: "string",
|
|
221
|
+
text: "string",
|
|
222
|
+
...PADDING_PROPS
|
|
223
|
+
},
|
|
224
|
+
children: "elements"
|
|
225
|
+
});
|
|
226
|
+
const Spinner = defineComponent({
|
|
227
|
+
name: "Spinner",
|
|
228
|
+
props: { size: SIZES },
|
|
229
|
+
children: "none"
|
|
230
|
+
});
|
|
231
|
+
const ProgressBar = defineComponent({
|
|
232
|
+
name: "ProgressBar",
|
|
233
|
+
props: { value: "number" },
|
|
234
|
+
children: "none"
|
|
235
|
+
});
|
|
236
|
+
const Notice = defineComponent({
|
|
237
|
+
name: "Notice",
|
|
238
|
+
props: {
|
|
239
|
+
tone: STATUS_TONES,
|
|
240
|
+
title: "string",
|
|
241
|
+
...PADDING_PROPS
|
|
242
|
+
},
|
|
243
|
+
children: "text"
|
|
244
|
+
});
|
|
245
|
+
const Badge = defineComponent({
|
|
246
|
+
name: "Badge",
|
|
247
|
+
props: { tone: STATUS_TONES },
|
|
248
|
+
children: "text"
|
|
249
|
+
});
|
|
250
|
+
const Footer = defineComponent({
|
|
251
|
+
name: "Footer",
|
|
252
|
+
props: PADDING_PROPS,
|
|
253
|
+
children: "elements"
|
|
254
|
+
});
|
|
255
|
+
const Grid = defineComponent({
|
|
256
|
+
name: "Grid",
|
|
257
|
+
props: {
|
|
258
|
+
columns: "number",
|
|
259
|
+
gap: SPACES,
|
|
260
|
+
...PADDING_PROPS
|
|
261
|
+
},
|
|
262
|
+
children: "elements"
|
|
263
|
+
});
|
|
264
|
+
const GridItem = defineComponent({
|
|
265
|
+
name: "GridItem",
|
|
266
|
+
props: {
|
|
267
|
+
label: "string",
|
|
268
|
+
icon: ICON_NAMES,
|
|
269
|
+
image: "string",
|
|
270
|
+
badge: "string"
|
|
271
|
+
},
|
|
272
|
+
events: ["tap"],
|
|
273
|
+
children: "none"
|
|
274
|
+
});
|
|
275
|
+
const Icon = defineComponent({
|
|
276
|
+
name: "Icon",
|
|
277
|
+
props: {
|
|
278
|
+
name: ICON_NAMES,
|
|
279
|
+
size: SIZES,
|
|
280
|
+
tone: TONES
|
|
281
|
+
},
|
|
282
|
+
children: "none",
|
|
283
|
+
inline: true
|
|
284
|
+
});
|
|
285
|
+
const Image = defineComponent({
|
|
286
|
+
name: "Image",
|
|
287
|
+
props: {
|
|
288
|
+
src: "string",
|
|
289
|
+
alt: "string",
|
|
290
|
+
width: "number",
|
|
291
|
+
height: "number",
|
|
292
|
+
fit: ["cover", "contain"],
|
|
293
|
+
shape: [
|
|
294
|
+
"rounded",
|
|
295
|
+
"square",
|
|
296
|
+
"circle"
|
|
297
|
+
],
|
|
298
|
+
aspect: ASPECTS
|
|
299
|
+
},
|
|
300
|
+
events: ["load", "error"],
|
|
301
|
+
children: "none",
|
|
302
|
+
overrides: [{
|
|
303
|
+
prop: "aspect",
|
|
304
|
+
ignores: "height",
|
|
305
|
+
because: "the aspect sets the height"
|
|
306
|
+
}]
|
|
307
|
+
});
|
|
308
|
+
const Input = defineComponent({
|
|
309
|
+
name: "Input",
|
|
310
|
+
props: {
|
|
311
|
+
value: "string",
|
|
312
|
+
placeholder: "string",
|
|
313
|
+
label: "string",
|
|
314
|
+
hint: "string",
|
|
315
|
+
error: "string",
|
|
316
|
+
prefix: "string",
|
|
317
|
+
suffix: "string",
|
|
318
|
+
type: [
|
|
319
|
+
"text",
|
|
320
|
+
"number",
|
|
321
|
+
"email",
|
|
322
|
+
"phone",
|
|
323
|
+
"password"
|
|
324
|
+
],
|
|
325
|
+
maxLength: "number",
|
|
326
|
+
disabled: "boolean"
|
|
327
|
+
},
|
|
328
|
+
events: ["change", "submit"],
|
|
329
|
+
children: "none"
|
|
330
|
+
});
|
|
331
|
+
const Link = defineComponent({
|
|
332
|
+
name: "Link",
|
|
333
|
+
props: {
|
|
334
|
+
href: "string",
|
|
335
|
+
tone: TONES
|
|
336
|
+
},
|
|
337
|
+
events: ["tap"],
|
|
338
|
+
children: "text",
|
|
339
|
+
inline: true
|
|
340
|
+
});
|
|
341
|
+
const List = defineComponent({
|
|
342
|
+
name: "List",
|
|
343
|
+
props: {
|
|
344
|
+
size: ["sm", "md"],
|
|
345
|
+
...PADDING_PROPS
|
|
346
|
+
},
|
|
347
|
+
events: ["endReached"],
|
|
348
|
+
children: "elements"
|
|
349
|
+
});
|
|
350
|
+
const ListItem = defineComponent({
|
|
351
|
+
name: "ListItem",
|
|
352
|
+
props: {
|
|
353
|
+
title: "string",
|
|
354
|
+
subtitle: "string",
|
|
355
|
+
value: "string",
|
|
356
|
+
icon: ICON_NAMES,
|
|
357
|
+
image: "string",
|
|
358
|
+
...PADDING_PROPS
|
|
359
|
+
},
|
|
360
|
+
events: ["tap"],
|
|
361
|
+
children: "elements"
|
|
362
|
+
});
|
|
363
|
+
const Sheet = defineComponent({
|
|
364
|
+
name: "Sheet",
|
|
365
|
+
props: {
|
|
366
|
+
open: "boolean",
|
|
367
|
+
title: "string",
|
|
368
|
+
showCloseIcon: "boolean",
|
|
369
|
+
...PADDING_PROPS
|
|
370
|
+
},
|
|
371
|
+
events: ["close"],
|
|
372
|
+
children: "elements"
|
|
373
|
+
});
|
|
374
|
+
const Toast = defineComponent({
|
|
375
|
+
name: "Toast",
|
|
376
|
+
props: {
|
|
377
|
+
message: "string",
|
|
378
|
+
tone: STATUS_TONES,
|
|
379
|
+
duration: "number"
|
|
380
|
+
},
|
|
381
|
+
events: ["hide"],
|
|
382
|
+
children: "none"
|
|
383
|
+
});
|
|
384
|
+
const QRCode = defineComponent({
|
|
385
|
+
name: "QRCode",
|
|
386
|
+
props: {
|
|
387
|
+
value: "string",
|
|
388
|
+
size: "number",
|
|
389
|
+
label: "string"
|
|
390
|
+
},
|
|
391
|
+
children: "none"
|
|
392
|
+
});
|
|
393
|
+
const Select = defineComponent({
|
|
394
|
+
name: "Select",
|
|
395
|
+
props: {
|
|
396
|
+
value: "string",
|
|
397
|
+
label: "string",
|
|
398
|
+
placeholder: "string",
|
|
399
|
+
hint: "string",
|
|
400
|
+
error: "string",
|
|
401
|
+
disabled: "boolean"
|
|
402
|
+
},
|
|
403
|
+
events: ["change"],
|
|
404
|
+
children: "elements"
|
|
405
|
+
});
|
|
406
|
+
const Option = defineComponent({
|
|
407
|
+
name: "Option",
|
|
408
|
+
props: {
|
|
409
|
+
value: "string",
|
|
410
|
+
label: "string"
|
|
411
|
+
},
|
|
412
|
+
children: "none"
|
|
413
|
+
});
|
|
414
|
+
const Scroll = defineComponent({
|
|
415
|
+
name: "Scroll",
|
|
416
|
+
props: {
|
|
417
|
+
gap: SPACES,
|
|
418
|
+
snap: [
|
|
419
|
+
"none",
|
|
420
|
+
"start",
|
|
421
|
+
"center"
|
|
422
|
+
],
|
|
423
|
+
index: "number",
|
|
424
|
+
...PADDING_PROPS
|
|
425
|
+
},
|
|
426
|
+
events: ["change"],
|
|
427
|
+
children: "elements"
|
|
428
|
+
});
|
|
429
|
+
const Skeleton = defineComponent({
|
|
430
|
+
name: "Skeleton",
|
|
431
|
+
props: {
|
|
432
|
+
shape: [
|
|
433
|
+
"text",
|
|
434
|
+
"circle",
|
|
435
|
+
"box"
|
|
436
|
+
],
|
|
437
|
+
lines: "number",
|
|
438
|
+
height: "number"
|
|
439
|
+
},
|
|
440
|
+
children: "none"
|
|
441
|
+
});
|
|
442
|
+
const Tabs = defineComponent({
|
|
443
|
+
name: "Tabs",
|
|
444
|
+
props: {
|
|
445
|
+
value: "string",
|
|
446
|
+
variant: ["underline", "segmented"],
|
|
447
|
+
...PADDING_PROPS
|
|
448
|
+
},
|
|
449
|
+
events: ["change"],
|
|
450
|
+
children: "elements"
|
|
451
|
+
});
|
|
452
|
+
const Tab = defineComponent({
|
|
453
|
+
name: "Tab",
|
|
454
|
+
props: {
|
|
455
|
+
value: "string",
|
|
456
|
+
label: "string",
|
|
457
|
+
badge: "number"
|
|
458
|
+
},
|
|
459
|
+
children: "none"
|
|
460
|
+
});
|
|
461
|
+
const Text = defineComponent({
|
|
462
|
+
name: "Text",
|
|
463
|
+
props: {
|
|
464
|
+
variant: [
|
|
465
|
+
"title",
|
|
466
|
+
"body",
|
|
467
|
+
"caption"
|
|
468
|
+
],
|
|
469
|
+
tone: TONES,
|
|
470
|
+
weight: [
|
|
471
|
+
"regular",
|
|
472
|
+
"medium",
|
|
473
|
+
"bold"
|
|
474
|
+
],
|
|
475
|
+
align: [
|
|
476
|
+
"start",
|
|
477
|
+
"center",
|
|
478
|
+
"end"
|
|
479
|
+
],
|
|
480
|
+
maxLines: "number",
|
|
481
|
+
...PADDING_PROPS
|
|
482
|
+
},
|
|
483
|
+
children: "text",
|
|
484
|
+
inline: true
|
|
485
|
+
});
|
|
486
|
+
const TextArea = defineComponent({
|
|
487
|
+
name: "TextArea",
|
|
488
|
+
props: {
|
|
489
|
+
value: "string",
|
|
490
|
+
placeholder: "string",
|
|
491
|
+
label: "string",
|
|
492
|
+
hint: "string",
|
|
493
|
+
error: "string",
|
|
494
|
+
rows: "number",
|
|
495
|
+
maxLength: "number",
|
|
496
|
+
disabled: "boolean"
|
|
497
|
+
},
|
|
498
|
+
events: ["change"],
|
|
499
|
+
children: "none"
|
|
500
|
+
});
|
|
501
|
+
const Switch = defineComponent({
|
|
502
|
+
name: "Switch",
|
|
503
|
+
props: {
|
|
504
|
+
checked: "boolean",
|
|
505
|
+
label: "string",
|
|
506
|
+
disabled: "boolean"
|
|
507
|
+
},
|
|
508
|
+
events: ["change"],
|
|
509
|
+
children: "none"
|
|
510
|
+
});
|
|
511
|
+
const Checkbox = defineComponent({
|
|
512
|
+
name: "Checkbox",
|
|
513
|
+
props: {
|
|
514
|
+
checked: "boolean",
|
|
515
|
+
label: "string",
|
|
516
|
+
disabled: "boolean"
|
|
517
|
+
},
|
|
518
|
+
events: ["change"],
|
|
519
|
+
children: "none"
|
|
520
|
+
});
|
|
521
|
+
const RadioGroup = defineComponent({
|
|
522
|
+
name: "RadioGroup",
|
|
523
|
+
props: {
|
|
524
|
+
value: "string",
|
|
525
|
+
label: "string",
|
|
526
|
+
disabled: "boolean"
|
|
527
|
+
},
|
|
528
|
+
events: ["change"],
|
|
529
|
+
children: "elements"
|
|
530
|
+
});
|
|
531
|
+
const builtinComponents = {
|
|
532
|
+
AmountInput,
|
|
533
|
+
Badge,
|
|
534
|
+
Button,
|
|
535
|
+
Carousel,
|
|
536
|
+
Checkbox,
|
|
537
|
+
Chip,
|
|
538
|
+
CodeInput,
|
|
539
|
+
DetailRow,
|
|
540
|
+
Divider,
|
|
541
|
+
EmptyState,
|
|
542
|
+
Footer,
|
|
543
|
+
Grid,
|
|
544
|
+
GridItem,
|
|
545
|
+
Icon,
|
|
546
|
+
Image,
|
|
547
|
+
Input,
|
|
548
|
+
Link,
|
|
549
|
+
List,
|
|
550
|
+
ListItem,
|
|
551
|
+
Notice,
|
|
552
|
+
Option,
|
|
553
|
+
ProgressBar,
|
|
554
|
+
QRCode,
|
|
555
|
+
Radio: defineComponent({
|
|
556
|
+
name: "Radio",
|
|
557
|
+
props: {
|
|
558
|
+
value: "string",
|
|
559
|
+
label: "string",
|
|
560
|
+
disabled: "boolean"
|
|
561
|
+
},
|
|
562
|
+
children: "none"
|
|
563
|
+
}),
|
|
564
|
+
RadioGroup,
|
|
565
|
+
Scroll,
|
|
566
|
+
Select,
|
|
567
|
+
Sheet,
|
|
568
|
+
Skeleton,
|
|
569
|
+
Spinner,
|
|
570
|
+
Switch,
|
|
571
|
+
Tab,
|
|
572
|
+
Tabs,
|
|
573
|
+
Text,
|
|
574
|
+
TextArea,
|
|
575
|
+
Toast,
|
|
576
|
+
View: defineComponent({
|
|
577
|
+
name: "View",
|
|
578
|
+
props: {
|
|
579
|
+
gap: SPACES,
|
|
580
|
+
direction: ["column", "row"],
|
|
581
|
+
align: [
|
|
582
|
+
"stretch",
|
|
583
|
+
"start",
|
|
584
|
+
"center",
|
|
585
|
+
"end"
|
|
586
|
+
],
|
|
587
|
+
justify: [
|
|
588
|
+
"start",
|
|
589
|
+
"center",
|
|
590
|
+
"end",
|
|
591
|
+
"between"
|
|
592
|
+
],
|
|
593
|
+
wrap: "boolean",
|
|
594
|
+
grow: "boolean",
|
|
595
|
+
...PADDING_PROPS,
|
|
596
|
+
variant: ["plain", "card"]
|
|
597
|
+
},
|
|
598
|
+
events: ["tap"],
|
|
599
|
+
children: "elements"
|
|
600
|
+
})
|
|
601
|
+
};
|
|
602
|
+
componentRegistry(Object.values(builtinComponents));
|
|
603
|
+
var HostError = class extends Error {
|
|
604
|
+
name = "HostError";
|
|
605
|
+
code;
|
|
606
|
+
constructor(code, message) {
|
|
607
|
+
super(message);
|
|
608
|
+
this.code = code;
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
const LABEL = "[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?";
|
|
612
|
+
new RegExp(`^(?:\\*\\.${LABEL}(?:\\.${LABEL})+|${LABEL}(?:\\.${LABEL})*)$`);
|
|
613
|
+
const SEMVER_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
|
|
614
|
+
function majorVersion(version) {
|
|
615
|
+
return parseVersion(version).major;
|
|
616
|
+
}
|
|
617
|
+
function isVersionAtLeast(version, minimum) {
|
|
618
|
+
return compareVersions(version, minimum) >= 0;
|
|
619
|
+
}
|
|
620
|
+
function compareVersions(left, right) {
|
|
621
|
+
const leftVersion = parseVersion(left);
|
|
622
|
+
const rightVersion = parseVersion(right);
|
|
623
|
+
const differences = [
|
|
624
|
+
leftVersion.major - rightVersion.major,
|
|
625
|
+
leftVersion.minor - rightVersion.minor,
|
|
626
|
+
leftVersion.patch - rightVersion.patch
|
|
627
|
+
];
|
|
628
|
+
for (const difference of differences) if (difference !== 0) return difference;
|
|
629
|
+
return comparePrereleases(leftVersion.prerelease, rightVersion.prerelease);
|
|
630
|
+
}
|
|
631
|
+
function parseVersion(version) {
|
|
632
|
+
const match = SEMVER_PATTERN.exec(version);
|
|
633
|
+
if (!match) throw new Error(`"${version}" is not a semantic version`);
|
|
634
|
+
const [, major, minor, patch, prerelease] = match;
|
|
635
|
+
return {
|
|
636
|
+
major: Number(major),
|
|
637
|
+
minor: Number(minor),
|
|
638
|
+
patch: Number(patch),
|
|
639
|
+
prerelease: prerelease === void 0 ? [] : prerelease.split(".")
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
function comparePrereleases(left, right) {
|
|
643
|
+
if (left.length === 0 || right.length === 0) return right.length - left.length;
|
|
644
|
+
const shared = Math.min(left.length, right.length);
|
|
645
|
+
for (let index = 0; index < shared; index++) {
|
|
646
|
+
const order = compareIdentifiers(left[index], right[index]);
|
|
647
|
+
if (order !== 0) return order;
|
|
648
|
+
}
|
|
649
|
+
return left.length - right.length;
|
|
650
|
+
}
|
|
651
|
+
function compareIdentifiers(left, right) {
|
|
652
|
+
const leftIsNumber = /^\d+$/.test(left);
|
|
653
|
+
const rightIsNumber = /^\d+$/.test(right);
|
|
654
|
+
if (leftIsNumber && rightIsNumber) return Number(left) - Number(right);
|
|
655
|
+
if (leftIsNumber) return -1;
|
|
656
|
+
if (rightIsNumber) return 1;
|
|
657
|
+
if (left < right) return -1;
|
|
658
|
+
if (left > right) return 1;
|
|
659
|
+
return 0;
|
|
660
|
+
}
|
|
661
|
+
function isReactCompatible(hostVersion, dotVersion) {
|
|
662
|
+
return majorVersion(hostVersion) === majorVersion(dotVersion) && isVersionAtLeast(hostVersion, dotVersion);
|
|
663
|
+
}
|
|
664
|
+
function createAdaptorTransport(adapter) {
|
|
665
|
+
return {
|
|
666
|
+
post: (message) => adapter.postMessage(message),
|
|
667
|
+
listen(listener) {
|
|
668
|
+
const onMessage = (event) => listener(event.data);
|
|
669
|
+
adapter.addEventListener("message", onMessage);
|
|
670
|
+
return () => adapter.removeEventListener("message", onMessage);
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
var NavigationStack = class {
|
|
675
|
+
limit;
|
|
676
|
+
#pages = [];
|
|
677
|
+
#nextInstanceId = 1;
|
|
678
|
+
constructor(limit = 8) {
|
|
679
|
+
this.limit = limit;
|
|
680
|
+
}
|
|
681
|
+
get pages() {
|
|
682
|
+
return this.#pages;
|
|
683
|
+
}
|
|
684
|
+
get top() {
|
|
685
|
+
return this.#pages.at(-1);
|
|
686
|
+
}
|
|
687
|
+
apply(navigation) {
|
|
688
|
+
switch (navigation.action) {
|
|
689
|
+
case "push":
|
|
690
|
+
if (this.#pages.length >= this.limit) return false;
|
|
691
|
+
break;
|
|
692
|
+
case "replace":
|
|
693
|
+
this.#pages.pop();
|
|
694
|
+
break;
|
|
695
|
+
case "reset":
|
|
696
|
+
this.#pages = [];
|
|
697
|
+
break;
|
|
698
|
+
case "back": {
|
|
699
|
+
const count = Math.min(navigation.delta, this.#pages.length - 1);
|
|
700
|
+
if (count < 1) return false;
|
|
701
|
+
this.#pages = this.#pages.slice(0, -count);
|
|
702
|
+
return true;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
const { page, options } = navigation;
|
|
706
|
+
this.#pages = [...this.#pages, {
|
|
707
|
+
instanceId: this.#nextInstanceId++,
|
|
708
|
+
page,
|
|
709
|
+
options
|
|
710
|
+
}];
|
|
711
|
+
return true;
|
|
712
|
+
}
|
|
713
|
+
};
|
|
714
|
+
//#endregion
|
|
715
|
+
//#region src/compatibility.ts
|
|
716
|
+
var IncompatibleDotError = class extends Error {
|
|
717
|
+
name = "IncompatibleDotError";
|
|
718
|
+
reason;
|
|
719
|
+
constructor(reason, message) {
|
|
720
|
+
super(message);
|
|
721
|
+
this.reason = reason;
|
|
722
|
+
}
|
|
723
|
+
};
|
|
724
|
+
function checkCompatibility(manifest, { dashVersion, minRuntimeVersion, reactVersions }) {
|
|
725
|
+
if (!isVersionAtLeast(manifest.runtime, minRuntimeVersion)) throw new IncompatibleDotError("runtimeTooOld", `This dot was built with an old Dot runtime, ${manifest.runtime}. Rebuild it with @monimesl/dot-runtime ${minRuntimeVersion} or newer.`);
|
|
726
|
+
const minDashVersion = manifest.dash?.minVersion;
|
|
727
|
+
if (minDashVersion !== void 0 && !isVersionAtLeast(dashVersion, minDashVersion)) throw new IncompatibleDotError("dashTooOld", `This dot needs Dash ${minDashVersion} or newer. This is Dash ${dashVersion}.`);
|
|
728
|
+
if (reactVersions !== void 0 && !reactVersions.some((version) => isReactCompatible(version, manifest.react))) throw new IncompatibleDotError("reactUnavailable", `This dot needs React ${manifest.react}, which this host doesn't have.`);
|
|
729
|
+
}
|
|
730
|
+
//#endregion
|
|
731
|
+
//#region src/host-calls.ts
|
|
732
|
+
const UI_METHOD_PREFIX = "dash.ui.";
|
|
733
|
+
var HostCallRunner = class {
|
|
734
|
+
#handlers;
|
|
735
|
+
#permissions;
|
|
736
|
+
#answer;
|
|
737
|
+
#running = /* @__PURE__ */ new Map();
|
|
738
|
+
constructor(handlers, permissions, answer) {
|
|
739
|
+
this.#handlers = handlers;
|
|
740
|
+
this.#permissions = permissions;
|
|
741
|
+
this.#answer = answer;
|
|
742
|
+
}
|
|
743
|
+
run({ id, method, params, timeout }) {
|
|
744
|
+
const entry = this.#handlers[method];
|
|
745
|
+
if (!entry) {
|
|
746
|
+
this.#answer({
|
|
747
|
+
id,
|
|
748
|
+
error: {
|
|
749
|
+
code: "unsupported",
|
|
750
|
+
message: `This host doesn't support "${method}"`
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
const call = {
|
|
756
|
+
method,
|
|
757
|
+
controller: new AbortController()
|
|
758
|
+
};
|
|
759
|
+
if (timeout !== void 0) call.timer = setTimeout(() => {
|
|
760
|
+
this.#fail(id, new HostError("timeout", `"${method}" took longer than ${timeout}ms`));
|
|
761
|
+
}, timeout);
|
|
762
|
+
this.#running.set(id, call);
|
|
763
|
+
this.#invoke(id, entry, {
|
|
764
|
+
method,
|
|
765
|
+
params,
|
|
766
|
+
signal: call.controller.signal
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
cancel(id) {
|
|
770
|
+
this.#end(id)?.controller.abort();
|
|
771
|
+
}
|
|
772
|
+
dismissUI() {
|
|
773
|
+
for (const [id, call] of this.#running) if (call.method.startsWith(UI_METHOD_PREFIX)) this.#fail(id, new HostError("cancelled", "The page that showed this dialog is no longer on screen"));
|
|
774
|
+
}
|
|
775
|
+
async #invoke(id, entry, call) {
|
|
776
|
+
try {
|
|
777
|
+
if (typeof entry === "function") {
|
|
778
|
+
this.#succeed(id, await entry(call));
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
await this.#permissions.check(call.method, entry.permission, call.signal);
|
|
782
|
+
this.#succeed(id, await entry.handle(call));
|
|
783
|
+
} catch (error) {
|
|
784
|
+
this.#fail(id, error);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
#succeed(id, value) {
|
|
788
|
+
if (this.#end(id)) this.#answer({
|
|
789
|
+
id,
|
|
790
|
+
value
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
#fail(id, error) {
|
|
794
|
+
const call = this.#end(id);
|
|
795
|
+
if (!call) return;
|
|
796
|
+
call.controller.abort();
|
|
797
|
+
this.#answer({
|
|
798
|
+
id,
|
|
799
|
+
error: describeError(error)
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
#end(id) {
|
|
803
|
+
const call = this.#running.get(id);
|
|
804
|
+
if (call) {
|
|
805
|
+
this.#running.delete(id);
|
|
806
|
+
clearTimeout(call.timer);
|
|
807
|
+
}
|
|
808
|
+
return call;
|
|
809
|
+
}
|
|
810
|
+
};
|
|
811
|
+
function describeError(error) {
|
|
812
|
+
if (error instanceof HostError) return {
|
|
813
|
+
code: error.code,
|
|
814
|
+
message: error.message
|
|
815
|
+
};
|
|
816
|
+
return {
|
|
817
|
+
code: "failed",
|
|
818
|
+
message: error instanceof Error ? error.message : String(error)
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
//#endregion
|
|
822
|
+
//#region src/permissions.ts
|
|
823
|
+
var PermissionGate = class {
|
|
824
|
+
#dotName;
|
|
825
|
+
#declared;
|
|
826
|
+
#ask;
|
|
827
|
+
#answers = /* @__PURE__ */ new Map();
|
|
828
|
+
constructor(dotName, declared, ask) {
|
|
829
|
+
this.#dotName = dotName;
|
|
830
|
+
this.#declared = declared;
|
|
831
|
+
this.#ask = ask;
|
|
832
|
+
}
|
|
833
|
+
async check(method, permission, signal) {
|
|
834
|
+
const reason = this.#declared[permission];
|
|
835
|
+
if (reason === void 0) throw new HostError("denied", `"${method}" needs the "${permission}" permission. Add it to permissions in dot.json, with a reason to show the person.`);
|
|
836
|
+
let allowed = this.#answers.get(permission);
|
|
837
|
+
if (allowed === void 0) {
|
|
838
|
+
allowed = await this.#ask({
|
|
839
|
+
dotName: this.#dotName,
|
|
840
|
+
permission,
|
|
841
|
+
reason
|
|
842
|
+
}, signal);
|
|
843
|
+
this.#answers.set(permission, allowed);
|
|
844
|
+
}
|
|
845
|
+
if (!allowed) throw new HostError("denied", `The person didn't allow the "${permission}" permission`);
|
|
846
|
+
}
|
|
847
|
+
};
|
|
848
|
+
//#endregion
|
|
849
|
+
//#region src/host.ts
|
|
850
|
+
function startHost({ logic, ui, manifest, dashVersion, minRuntimeVersion, systemContext, handlers = {}, askPermission, observe, restorePages = [], onBarsChange }) {
|
|
851
|
+
checkCompatibility(manifest, {
|
|
852
|
+
dashVersion,
|
|
853
|
+
minRuntimeVersion
|
|
854
|
+
});
|
|
855
|
+
const stack = new NavigationStack();
|
|
856
|
+
let context = systemContext;
|
|
857
|
+
const calls = new HostCallRunner(handlers, new PermissionGate(manifest.name, manifest.permissions ?? {}, askPermission), (result) => {
|
|
858
|
+
const message = {
|
|
859
|
+
type: "hostResult",
|
|
860
|
+
payload: result
|
|
861
|
+
};
|
|
862
|
+
observe?.("fromHost", message);
|
|
863
|
+
logic.post(message);
|
|
864
|
+
});
|
|
865
|
+
const broadcast = (message) => {
|
|
866
|
+
observe?.("fromHost", message);
|
|
867
|
+
ui.post(message);
|
|
868
|
+
logic.post(message);
|
|
869
|
+
};
|
|
870
|
+
const barsByInstance = /* @__PURE__ */ new Map();
|
|
871
|
+
const showBars = () => {
|
|
872
|
+
const top = stack.top;
|
|
873
|
+
const bars = top && barsByInstance.get(top.instanceId);
|
|
874
|
+
onBarsChange?.({
|
|
875
|
+
title: bars?.navigationBar.title ?? manifest.name,
|
|
876
|
+
navigationBarHidden: bars?.navigationBar.hidden ?? false,
|
|
877
|
+
navigationBarColor: bars?.navigationBar.backgroundColor,
|
|
878
|
+
navigationBarTextColor: bars?.navigationBar.textColor,
|
|
879
|
+
tabBarHidden: bars?.tabBar.hidden ?? false
|
|
880
|
+
}, stack.pages);
|
|
881
|
+
};
|
|
882
|
+
const syncStack = () => {
|
|
883
|
+
const message = {
|
|
884
|
+
type: "syncStack",
|
|
885
|
+
payload: {
|
|
886
|
+
pages: stack.pages,
|
|
887
|
+
limit: stack.limit
|
|
888
|
+
}
|
|
889
|
+
};
|
|
890
|
+
broadcast(message);
|
|
891
|
+
const open = new Set(stack.pages.map((entry) => entry.instanceId));
|
|
892
|
+
for (const instanceId of barsByInstance.keys()) if (!open.has(instanceId)) barsByInstance.delete(instanceId);
|
|
893
|
+
showBars();
|
|
894
|
+
};
|
|
895
|
+
const syncSystemContext = () => {
|
|
896
|
+
broadcast({
|
|
897
|
+
type: "syncSystemContext",
|
|
898
|
+
payload: context
|
|
899
|
+
});
|
|
900
|
+
};
|
|
901
|
+
const navigate = (navigation) => {
|
|
902
|
+
if (navigation.action !== "back" && !manifest.pages.includes(navigation.page)) return false;
|
|
903
|
+
const previousTop = stack.top;
|
|
904
|
+
if (!stack.apply(navigation)) return false;
|
|
905
|
+
syncStack();
|
|
906
|
+
if (stack.top !== previousTop) calls.dismissUI();
|
|
907
|
+
return true;
|
|
908
|
+
};
|
|
909
|
+
const openFirstPages = () => {
|
|
910
|
+
const [first, ...rest] = restorePages.filter((entry) => manifest.pages.includes(entry.page));
|
|
911
|
+
if (!first) {
|
|
912
|
+
navigate({
|
|
913
|
+
action: "reset",
|
|
914
|
+
page: manifest.entry,
|
|
915
|
+
options: {}
|
|
916
|
+
});
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
stack.apply({
|
|
920
|
+
action: "reset",
|
|
921
|
+
page: first.page,
|
|
922
|
+
options: first.options
|
|
923
|
+
});
|
|
924
|
+
for (const entry of rest) stack.apply({
|
|
925
|
+
action: "push",
|
|
926
|
+
page: entry.page,
|
|
927
|
+
options: entry.options
|
|
928
|
+
});
|
|
929
|
+
syncStack();
|
|
930
|
+
};
|
|
931
|
+
const navigateFromPage = ({ from, ...navigation }) => {
|
|
932
|
+
if (from === stack.top?.instanceId) navigate(navigation);
|
|
933
|
+
};
|
|
934
|
+
const hostMessageHandlers = /* @__PURE__ */ new Map([
|
|
935
|
+
["navigate", (payload) => navigateFromPage(payload)],
|
|
936
|
+
["hostCall", (payload) => calls.run(payload)],
|
|
937
|
+
["hostCancel", (payload) => calls.cancel(payload.id)],
|
|
938
|
+
["pageBars", (payload) => {
|
|
939
|
+
const { instanceId, ...bars } = payload;
|
|
940
|
+
barsByInstance.set(instanceId, bars);
|
|
941
|
+
showBars();
|
|
942
|
+
}]
|
|
943
|
+
]);
|
|
944
|
+
const stopService = logic.listen((raw) => {
|
|
945
|
+
const message = raw;
|
|
946
|
+
const handleHostMessage = hostMessageHandlers.get(message.type);
|
|
947
|
+
if (handleHostMessage) {
|
|
948
|
+
observe?.("toHost", message);
|
|
949
|
+
handleHostMessage(message.payload);
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
observe?.("toWebview", message);
|
|
953
|
+
ui.post(message);
|
|
954
|
+
});
|
|
955
|
+
const stopWebview = ui.listen((raw) => {
|
|
956
|
+
const message = raw;
|
|
957
|
+
observe?.("toService", message);
|
|
958
|
+
logic.post(message);
|
|
959
|
+
if (message.type === "connect") {
|
|
960
|
+
syncSystemContext();
|
|
961
|
+
openFirstPages();
|
|
962
|
+
}
|
|
963
|
+
});
|
|
964
|
+
return {
|
|
965
|
+
get pages() {
|
|
966
|
+
return stack.pages;
|
|
967
|
+
},
|
|
968
|
+
back: () => navigate({
|
|
969
|
+
action: "back",
|
|
970
|
+
delta: 1
|
|
971
|
+
}),
|
|
972
|
+
setSystemContext: (next) => {
|
|
973
|
+
context = next;
|
|
974
|
+
syncSystemContext();
|
|
975
|
+
},
|
|
976
|
+
stop: () => {
|
|
977
|
+
stopService();
|
|
978
|
+
stopWebview();
|
|
979
|
+
}
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
const SIMULATOR_VERSIONS = {
|
|
983
|
+
dashVersion: "1.0.0",
|
|
984
|
+
minRuntimeVersion: "0.1.0-alpha.1"
|
|
985
|
+
};
|
|
986
|
+
//#endregion
|
|
987
|
+
//#region src/paths.ts
|
|
988
|
+
const CLIENT_PATH = "/__dot/";
|
|
989
|
+
const EVENTS_PATH = `${CLIENT_PATH}events`;
|
|
990
|
+
const HTTP_PATH = `${CLIENT_PATH}network/http/`;
|
|
991
|
+
const FILE_PATH = `${CLIENT_PATH}storage/file/`;
|
|
992
|
+
const CACHE_PATH = `${CLIENT_PATH}storage/cache/`;
|
|
993
|
+
const SECRET_PATH = `${CLIENT_PATH}storage/secret/`;
|
|
994
|
+
const DOT_PATH = "/dot/";
|
|
995
|
+
const WEBVIEW_URL = `${DOT_PATH}ui/index.html`;
|
|
996
|
+
const MANIFEST_URL = `${DOT_PATH}manifest.json`;
|
|
997
|
+
//#endregion
|
|
998
|
+
//#region src/client/dash/dev-server.ts
|
|
999
|
+
async function callDevServer(path, params, signal) {
|
|
1000
|
+
const answer = await (await fetch(path, {
|
|
1001
|
+
method: "POST",
|
|
1002
|
+
headers: { "Content-Type": "application/json" },
|
|
1003
|
+
body: JSON.stringify(params),
|
|
1004
|
+
signal
|
|
1005
|
+
})).json();
|
|
1006
|
+
if ("error" in answer) throw new HostError(answer.error.code, answer.error.message);
|
|
1007
|
+
return answer.result;
|
|
1008
|
+
}
|
|
1009
|
+
//#endregion
|
|
1010
|
+
//#region src/client/dash/system.ts
|
|
1011
|
+
const SHAKE_DURATION_MS = 300;
|
|
1012
|
+
const SHAKE = [
|
|
1013
|
+
{ transform: "translateX(0)" },
|
|
1014
|
+
{ transform: "translateX(-5px)" },
|
|
1015
|
+
{ transform: "translateX(5px)" },
|
|
1016
|
+
{ transform: "translateX(-3px)" },
|
|
1017
|
+
{ transform: "translateX(3px)" },
|
|
1018
|
+
{ transform: "translateX(0)" }
|
|
1019
|
+
];
|
|
1020
|
+
async function vibrate(phone, note, options) {
|
|
1021
|
+
note(`vibrated: ${options.style}`);
|
|
1022
|
+
await phone.animate(SHAKE, { duration: SHAKE_DURATION_MS }).finished;
|
|
1023
|
+
}
|
|
1024
|
+
function torch(phone, note, state) {
|
|
1025
|
+
phone.dataset.torch = state;
|
|
1026
|
+
note(`torch: ${state}`);
|
|
1027
|
+
}
|
|
1028
|
+
async function copyText(note, text) {
|
|
1029
|
+
try {
|
|
1030
|
+
await navigator.clipboard.writeText(text);
|
|
1031
|
+
} catch (error) {
|
|
1032
|
+
throw new HostError("failed", `The browser blocked the clipboard: ${error.message}`);
|
|
1033
|
+
}
|
|
1034
|
+
note(`copied: ${text}`);
|
|
1035
|
+
}
|
|
1036
|
+
function share(note, params) {
|
|
1037
|
+
const parts = [
|
|
1038
|
+
params.text,
|
|
1039
|
+
params.url,
|
|
1040
|
+
params.file && `file ${params.file}`
|
|
1041
|
+
].filter(Boolean);
|
|
1042
|
+
if (parts.length === 0) throw new HostError("invalid", "share needs a text, a url or a file");
|
|
1043
|
+
note(`shared: ${parts.join(" · ")}`);
|
|
1044
|
+
}
|
|
1045
|
+
function openUrl(note, url) {
|
|
1046
|
+
if (!isHttps(url)) throw new HostError("invalid", `Dash only opens https addresses, not ${url}`);
|
|
1047
|
+
window.open(url, "_blank", "noopener");
|
|
1048
|
+
note(`opened: ${url}`);
|
|
1049
|
+
}
|
|
1050
|
+
function isHttps(url) {
|
|
1051
|
+
try {
|
|
1052
|
+
return new URL(url).protocol === "https:";
|
|
1053
|
+
} catch {
|
|
1054
|
+
return false;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
function delay(milliseconds, signal) {
|
|
1058
|
+
if (!Number.isFinite(milliseconds) || milliseconds < 0) throw new HostError("invalid", `A delay is a number of milliseconds, 0 or more, not ${milliseconds}`);
|
|
1059
|
+
return new Promise((resolve) => {
|
|
1060
|
+
const timer = setTimeout(resolve, milliseconds);
|
|
1061
|
+
signal.addEventListener("abort", () => clearTimeout(timer));
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
//#endregion
|
|
1065
|
+
//#region src/client/dom.ts
|
|
1066
|
+
function element(selector) {
|
|
1067
|
+
const node = document.querySelector(selector);
|
|
1068
|
+
if (!node) throw new Error(`The simulator page is missing ${selector}`);
|
|
1069
|
+
return node;
|
|
1070
|
+
}
|
|
1071
|
+
function createElement(tag, className, text) {
|
|
1072
|
+
const node = document.createElement(tag);
|
|
1073
|
+
node.className = className;
|
|
1074
|
+
if (text !== void 0) node.textContent = text;
|
|
1075
|
+
return node;
|
|
1076
|
+
}
|
|
1077
|
+
//#endregion
|
|
1078
|
+
//#region src/client/dash/ui.ts
|
|
1079
|
+
const DEFAULT_ACTIONS = [{
|
|
1080
|
+
id: "ok",
|
|
1081
|
+
label: "OK"
|
|
1082
|
+
}];
|
|
1083
|
+
function alert(phone, options, signal) {
|
|
1084
|
+
return new Promise((resolve, reject) => {
|
|
1085
|
+
const layer = createElement("div", "dialog-layer");
|
|
1086
|
+
const dialog = createElement("div", "dialog");
|
|
1087
|
+
dialog.setAttribute("role", "alertdialog");
|
|
1088
|
+
const body = createElement("div", "dialog-body");
|
|
1089
|
+
body.append(createElement("div", "dialog-title", options.title));
|
|
1090
|
+
if (options.message) body.append(createElement("div", "dialog-message", options.message));
|
|
1091
|
+
const actions = options.actions ?? DEFAULT_ACTIONS;
|
|
1092
|
+
const buttons = createElement("div", actions.length === 2 ? "dialog-actions" : "dialog-actions stacked");
|
|
1093
|
+
for (const action of actions) {
|
|
1094
|
+
const button = createElement("button", action.role ?? "default", action.label);
|
|
1095
|
+
button.addEventListener("click", () => {
|
|
1096
|
+
layer.remove();
|
|
1097
|
+
resolve({ action: action.id });
|
|
1098
|
+
});
|
|
1099
|
+
buttons.append(button);
|
|
1100
|
+
}
|
|
1101
|
+
dialog.append(body, buttons);
|
|
1102
|
+
layer.append(dialog);
|
|
1103
|
+
signal.addEventListener("abort", () => {
|
|
1104
|
+
layer.remove();
|
|
1105
|
+
reject(new HostError("cancelled", "The alert was dismissed"));
|
|
1106
|
+
});
|
|
1107
|
+
phone.append(layer);
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
1110
|
+
//#endregion
|
|
1111
|
+
//#region src/client/dash/index.ts
|
|
1112
|
+
const HTTP_METHODS = [
|
|
1113
|
+
"request",
|
|
1114
|
+
"download",
|
|
1115
|
+
"upload"
|
|
1116
|
+
];
|
|
1117
|
+
const FILE_OPERATIONS = [
|
|
1118
|
+
"read",
|
|
1119
|
+
"write",
|
|
1120
|
+
"stat",
|
|
1121
|
+
"list",
|
|
1122
|
+
"createFolder",
|
|
1123
|
+
"move",
|
|
1124
|
+
"copy",
|
|
1125
|
+
"remove",
|
|
1126
|
+
"save",
|
|
1127
|
+
"open",
|
|
1128
|
+
"usage"
|
|
1129
|
+
];
|
|
1130
|
+
const CACHE_OPERATIONS = [
|
|
1131
|
+
"get",
|
|
1132
|
+
"set",
|
|
1133
|
+
"remove",
|
|
1134
|
+
"exists",
|
|
1135
|
+
"keys",
|
|
1136
|
+
"clear"
|
|
1137
|
+
];
|
|
1138
|
+
const SECRET_OPERATIONS = [
|
|
1139
|
+
"get",
|
|
1140
|
+
"set",
|
|
1141
|
+
"remove",
|
|
1142
|
+
"clear"
|
|
1143
|
+
];
|
|
1144
|
+
function dashHandlers(phone, note) {
|
|
1145
|
+
return {
|
|
1146
|
+
"dash.ui.alert": ({ params, signal }) => alert(phone, params, signal),
|
|
1147
|
+
"dash.system.vibrate": ({ params }) => vibrate(phone, note, params),
|
|
1148
|
+
"dash.system.torch": ({ params }) => torch(phone, note, params.state),
|
|
1149
|
+
"dash.system.copyText": ({ params }) => copyText(note, params.text),
|
|
1150
|
+
"dash.system.share": ({ params }) => share(note, params),
|
|
1151
|
+
"dash.system.openUrl": ({ params }) => openUrl(note, params.url),
|
|
1152
|
+
"dash.system.delay": ({ params, signal }) => delay(params.milliseconds, signal),
|
|
1153
|
+
...devServerHandlers("dash.network.http", HTTP_PATH, HTTP_METHODS),
|
|
1154
|
+
...devServerHandlers("dash.storage.file", FILE_PATH, FILE_OPERATIONS),
|
|
1155
|
+
...devServerHandlers("dash.storage.cache", CACHE_PATH, CACHE_OPERATIONS),
|
|
1156
|
+
...devServerHandlers("dash.storage.secret", SECRET_PATH, SECRET_OPERATIONS)
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
function devServerHandlers(group, path, names) {
|
|
1160
|
+
return Object.fromEntries(names.map((name) => [`${group}.${name}`, ({ params, signal }) => callDevServer(`${path}${name}`, params, signal)]));
|
|
1161
|
+
}
|
|
1162
|
+
//#endregion
|
|
1163
|
+
//#region src/client/dash/permissions.ts
|
|
1164
|
+
const PERMISSION_SUMMARIES = { account: "see your Dash account: your name, and your email or phone" };
|
|
1165
|
+
async function askPermission(phone, note, request, signal) {
|
|
1166
|
+
const { action } = await alert(phone, {
|
|
1167
|
+
title: `Allow “${request.dotName}” to ${PERMISSION_SUMMARIES[request.permission]}?`,
|
|
1168
|
+
message: request.reason,
|
|
1169
|
+
actions: [{
|
|
1170
|
+
id: "deny",
|
|
1171
|
+
label: "Don't Allow",
|
|
1172
|
+
role: "cancel"
|
|
1173
|
+
}, {
|
|
1174
|
+
id: "allow",
|
|
1175
|
+
label: "Allow"
|
|
1176
|
+
}]
|
|
1177
|
+
}, signal);
|
|
1178
|
+
const allowed = action === "allow";
|
|
1179
|
+
if (allowed) note(`permission ${request.permission}: allowed`);
|
|
1180
|
+
else note(`permission ${request.permission}: not allowed`);
|
|
1181
|
+
return allowed;
|
|
1182
|
+
}
|
|
1183
|
+
//#endregion
|
|
1184
|
+
//#region src/client/wire.ts
|
|
1185
|
+
function isWire(data) {
|
|
1186
|
+
return typeof data?.dot === "string";
|
|
1187
|
+
}
|
|
1188
|
+
function wireTransport(channel) {
|
|
1189
|
+
return {
|
|
1190
|
+
post: (message) => channel.post({ dot: JSON.stringify(message) }),
|
|
1191
|
+
listen: (listener) => channel.listen((data) => {
|
|
1192
|
+
if (isWire(data)) listener(JSON.parse(data.dot));
|
|
1193
|
+
})
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1196
|
+
function windowChannel(other) {
|
|
1197
|
+
return {
|
|
1198
|
+
post: (message) => other()?.postMessage(message, location.origin),
|
|
1199
|
+
listen(listener) {
|
|
1200
|
+
const onMessage = (event) => {
|
|
1201
|
+
if (event.source === other()) listener(event.data);
|
|
1202
|
+
};
|
|
1203
|
+
window.addEventListener("message", onMessage);
|
|
1204
|
+
return () => window.removeEventListener("message", onMessage);
|
|
1205
|
+
}
|
|
1206
|
+
};
|
|
1207
|
+
}
|
|
1208
|
+
//#endregion
|
|
1209
|
+
//#region src/client/simulator.ts
|
|
1210
|
+
const MAX_LOG_ENTRIES = 500;
|
|
1211
|
+
const screen = element("#screen");
|
|
1212
|
+
const log = element("#log");
|
|
1213
|
+
const errorBanner = element("#error");
|
|
1214
|
+
const phone = element(".phone");
|
|
1215
|
+
const schemeSelect = element("#scheme");
|
|
1216
|
+
const navigationBar = element(".navigation-bar");
|
|
1217
|
+
const navigationTitle = element("#navigation-title");
|
|
1218
|
+
const navigationBack = element("#navigation-back");
|
|
1219
|
+
const tabBar = element(".tab-bar");
|
|
1220
|
+
const clock = element(".clock");
|
|
1221
|
+
const browserDark = matchMedia("(prefers-color-scheme: dark)");
|
|
1222
|
+
const timeFormat = new Intl.DateTimeFormat("en-GB", {
|
|
1223
|
+
hour: "2-digit",
|
|
1224
|
+
minute: "2-digit",
|
|
1225
|
+
hourCycle: "h23"
|
|
1226
|
+
});
|
|
1227
|
+
function showTime() {
|
|
1228
|
+
clock.textContent = timeFormat.format(/* @__PURE__ */ new Date());
|
|
1229
|
+
}
|
|
1230
|
+
showTime();
|
|
1231
|
+
setInterval(showTime, 1e3);
|
|
1232
|
+
let runningDot = null;
|
|
1233
|
+
let launchCount = 0;
|
|
1234
|
+
let hotReloads = 0;
|
|
1235
|
+
async function launch(restorePages = []) {
|
|
1236
|
+
stopRunningDot();
|
|
1237
|
+
launchCount += 1;
|
|
1238
|
+
const thisLaunch = launchCount;
|
|
1239
|
+
if (log.childElementCount > 0) note("dot restarted");
|
|
1240
|
+
let manifest;
|
|
1241
|
+
try {
|
|
1242
|
+
manifest = await (await fetch(MANIFEST_URL)).json();
|
|
1243
|
+
} catch {
|
|
1244
|
+
showError(`There's nothing to run, because ${MANIFEST_URL} is missing. Check whether the build failed.`);
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
if (thisLaunch !== launchCount) return;
|
|
1248
|
+
try {
|
|
1249
|
+
checkCompatibility(manifest, SIMULATOR_VERSIONS);
|
|
1250
|
+
} catch (error) {
|
|
1251
|
+
screen.src = "about:blank";
|
|
1252
|
+
showError(error.message);
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
const worker = new Worker(`${CLIENT_PATH}service-bridge.js`);
|
|
1256
|
+
const host = startHost({
|
|
1257
|
+
logic: wireTransport(createAdaptorTransport(worker)),
|
|
1258
|
+
ui: wireTransport(windowChannel(() => screen.contentWindow)),
|
|
1259
|
+
manifest,
|
|
1260
|
+
...SIMULATOR_VERSIONS,
|
|
1261
|
+
systemContext: systemContext(),
|
|
1262
|
+
handlers: dashHandlers(phone, note),
|
|
1263
|
+
askPermission: (request, signal) => askPermission(phone, note, request, signal),
|
|
1264
|
+
observe: record,
|
|
1265
|
+
restorePages,
|
|
1266
|
+
onBarsChange: showBars
|
|
1267
|
+
});
|
|
1268
|
+
worker.addEventListener("error", (event) => showError(`The dot's service crashed: ${event.message}`));
|
|
1269
|
+
screen.src = WEBVIEW_URL;
|
|
1270
|
+
runningDot = {
|
|
1271
|
+
host,
|
|
1272
|
+
worker
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1275
|
+
function stopRunningDot() {
|
|
1276
|
+
if (!runningDot) return;
|
|
1277
|
+
runningDot.host.stop();
|
|
1278
|
+
runningDot.worker.terminate();
|
|
1279
|
+
runningDot = null;
|
|
1280
|
+
}
|
|
1281
|
+
function showBars(bars, pages) {
|
|
1282
|
+
navigationBar.hidden = bars.navigationBarHidden;
|
|
1283
|
+
navigationTitle.textContent = bars.title;
|
|
1284
|
+
navigationBack.hidden = pages.length < 2;
|
|
1285
|
+
setBarColor("--bar", bars.navigationBarColor);
|
|
1286
|
+
setBarColor("--bar-text", bars.navigationBarTextColor);
|
|
1287
|
+
tabBar.hidden = bars.tabBarHidden;
|
|
1288
|
+
}
|
|
1289
|
+
function setBarColor(property, color) {
|
|
1290
|
+
if (color) phone.style.setProperty(property, color);
|
|
1291
|
+
else phone.style.removeProperty(property);
|
|
1292
|
+
}
|
|
1293
|
+
function goBack() {
|
|
1294
|
+
if (runningDot && !runningDot.host.back()) note("back on the first page, where a phone would close the dot");
|
|
1295
|
+
}
|
|
1296
|
+
const ARROWS = {
|
|
1297
|
+
toHost: "both --> host",
|
|
1298
|
+
fromHost: "host --> both",
|
|
1299
|
+
toService: "ui --> logic",
|
|
1300
|
+
toWebview: "ui <-- logic"
|
|
1301
|
+
};
|
|
1302
|
+
function note(text) {
|
|
1303
|
+
log.append(createElement("li", "note", text));
|
|
1304
|
+
}
|
|
1305
|
+
function record(direction, message) {
|
|
1306
|
+
const entry = createElement("li", direction);
|
|
1307
|
+
const details = document.createElement("details");
|
|
1308
|
+
const summary = document.createElement("summary");
|
|
1309
|
+
const time = (/* @__PURE__ */ new Date()).toLocaleTimeString([], { hour12: false });
|
|
1310
|
+
summary.append(createElement("span", "time", time), createElement("span", "arrow", ARROWS[direction]), createElement("span", "type", message.type));
|
|
1311
|
+
const payload = document.createElement("pre");
|
|
1312
|
+
payload.textContent = JSON.stringify(message.payload, null, 2);
|
|
1313
|
+
details.append(summary, payload);
|
|
1314
|
+
entry.append(details);
|
|
1315
|
+
if (message.type === "error") entry.classList.add("failed");
|
|
1316
|
+
log.append(entry);
|
|
1317
|
+
while (log.childElementCount > MAX_LOG_ENTRIES) log.firstElementChild?.remove();
|
|
1318
|
+
log.scrollTop = log.scrollHeight;
|
|
1319
|
+
}
|
|
1320
|
+
function restart(restorePages = []) {
|
|
1321
|
+
launch(restorePages).catch((error) => {
|
|
1322
|
+
showError(`Could not start the dot: ${String(error)}`);
|
|
1323
|
+
});
|
|
1324
|
+
}
|
|
1325
|
+
function relaunch() {
|
|
1326
|
+
restart(runningDot?.host.pages ?? []);
|
|
1327
|
+
}
|
|
1328
|
+
function hotReload() {
|
|
1329
|
+
if (!runningDot) {
|
|
1330
|
+
restart();
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
hotReloads += 1;
|
|
1334
|
+
const reload = { dotHotReload: hotReloads };
|
|
1335
|
+
runningDot.worker.postMessage(reload);
|
|
1336
|
+
screen.contentWindow?.postMessage(reload, location.origin);
|
|
1337
|
+
note("dot updated in place");
|
|
1338
|
+
}
|
|
1339
|
+
function showError(message) {
|
|
1340
|
+
errorBanner.hidden = message === null;
|
|
1341
|
+
errorBanner.textContent = message ?? "";
|
|
1342
|
+
}
|
|
1343
|
+
function syncSystemContext() {
|
|
1344
|
+
runningDot?.host.setSystemContext(systemContext());
|
|
1345
|
+
}
|
|
1346
|
+
function systemContext() {
|
|
1347
|
+
return {
|
|
1348
|
+
scheme: schemeSelect.value,
|
|
1349
|
+
appState: appState()
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
function appState() {
|
|
1353
|
+
if (document.visibilityState === "hidden") return "background";
|
|
1354
|
+
return "active";
|
|
1355
|
+
}
|
|
1356
|
+
function browserScheme() {
|
|
1357
|
+
return browserDark.matches ? "dark" : "light";
|
|
1358
|
+
}
|
|
1359
|
+
function changeScheme(scheme) {
|
|
1360
|
+
schemeSelect.value = scheme;
|
|
1361
|
+
phone.dataset.scheme = scheme;
|
|
1362
|
+
syncSystemContext();
|
|
1363
|
+
}
|
|
1364
|
+
const events = new EventSource(EVENTS_PATH);
|
|
1365
|
+
events.addEventListener("reload", () => {
|
|
1366
|
+
showError(null);
|
|
1367
|
+
relaunch();
|
|
1368
|
+
});
|
|
1369
|
+
events.addEventListener("hot-reload", () => {
|
|
1370
|
+
showError(null);
|
|
1371
|
+
hotReload();
|
|
1372
|
+
});
|
|
1373
|
+
events.addEventListener("build-error", (event) => showError(String(event.data)));
|
|
1374
|
+
element("#reload").addEventListener("click", () => restart());
|
|
1375
|
+
element("#back").addEventListener("click", goBack);
|
|
1376
|
+
navigationBack.addEventListener("click", goBack);
|
|
1377
|
+
element("#clear").addEventListener("click", () => log.replaceChildren());
|
|
1378
|
+
schemeSelect.addEventListener("change", () => changeScheme(schemeSelect.value));
|
|
1379
|
+
browserDark.addEventListener("change", () => changeScheme(browserScheme()));
|
|
1380
|
+
document.addEventListener("visibilitychange", syncSystemContext);
|
|
1381
|
+
changeScheme(browserScheme());
|
|
1382
|
+
restart();
|
|
1383
|
+
//#endregion
|
|
1384
|
+
})();
|
|
1385
|
+
|
|
1386
|
+
//# sourceMappingURL=simulator.js.map
|