js-dos 0.80.25

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.
Files changed (119) hide show
  1. package/.eslintrc.json +43 -0
  2. package/.github/workflows/build.yml +43 -0
  3. package/.gitmodules +4 -0
  4. package/README.md +24 -0
  5. package/index.html +106 -0
  6. package/package.json +48 -0
  7. package/postcss.config.cjs +8 -0
  8. package/src/app.css +14 -0
  9. package/src/base.css +10 -0
  10. package/src/components/checkbox.tsx +41 -0
  11. package/src/components/close-button.tsx +17 -0
  12. package/src/components/components.css +28 -0
  13. package/src/components/dos-option-checkbox.tsx +72 -0
  14. package/src/components/dos-option-select.tsx +94 -0
  15. package/src/components/dos-option-slider.tsx +35 -0
  16. package/src/components/error.tsx +16 -0
  17. package/src/components/loading.tsx +26 -0
  18. package/src/components/lock.tsx +10 -0
  19. package/src/components/select.tsx +33 -0
  20. package/src/components/slider.tsx +69 -0
  21. package/src/download-file.ts +18 -0
  22. package/src/frame/account-frame.tsx +155 -0
  23. package/src/frame/editor/defaults.ts +344 -0
  24. package/src/frame/editor/editor-conf-frame.tsx +42 -0
  25. package/src/frame/editor/editor-frame.css +55 -0
  26. package/src/frame/editor/editor-fs-frame.tsx +315 -0
  27. package/src/frame/frame.css +127 -0
  28. package/src/frame/frame.tsx +34 -0
  29. package/src/frame/host-cache-frame.tsx +124 -0
  30. package/src/frame/image-rendering-frame.tsx +8 -0
  31. package/src/frame/network-frame.tsx +157 -0
  32. package/src/frame/quick-save-frame.tsx +95 -0
  33. package/src/frame/settings-frame.tsx +14 -0
  34. package/src/frame/stats-frame.tsx +82 -0
  35. package/src/host/bundle-storage.ts +91 -0
  36. package/src/host/fullscreen.ts +36 -0
  37. package/src/host/lcache.ts +203 -0
  38. package/src/host/lstorage.ts +156 -0
  39. package/src/i18n.ts +167 -0
  40. package/src/index.css +7 -0
  41. package/src/load.ts +139 -0
  42. package/src/login/login.css +9 -0
  43. package/src/login/login.tsx +77 -0
  44. package/src/main.tsx +195 -0
  45. package/src/non-serializable-store.ts +35 -0
  46. package/src/public/types.ts +39 -0
  47. package/src/sidebar/account-button.tsx +32 -0
  48. package/src/sidebar/cloud-save-button.tsx +89 -0
  49. package/src/sidebar/diskette-icon.tsx +14 -0
  50. package/src/sidebar/fullscreen-button.tsx +44 -0
  51. package/src/sidebar/network-button.tsx +39 -0
  52. package/src/sidebar/sidebar-button.tsx +243 -0
  53. package/src/sidebar/sidebar.css +47 -0
  54. package/src/sidebar/sidebar.tsx +30 -0
  55. package/src/sockdrive/LICENSE +674 -0
  56. package/src/sockdrive/README.md +22 -0
  57. package/src/sockdrive/SDL2/LRUCache11.hpp +240 -0
  58. package/src/sockdrive/SDL2/sockdrive.cpp +207 -0
  59. package/src/sockdrive/include/sockdrive.h +23 -0
  60. package/src/sockdrive/js/.eslintrc.json +43 -0
  61. package/src/sockdrive/js/README.md +70 -0
  62. package/src/sockdrive/js/package.json +29 -0
  63. package/src/sockdrive/js/src/fatfs/chains.js +318 -0
  64. package/src/sockdrive/js/src/fatfs/dir.js +385 -0
  65. package/src/sockdrive/js/src/fatfs/helpers.js +210 -0
  66. package/src/sockdrive/js/src/fatfs/index.js +606 -0
  67. package/src/sockdrive/js/src/fatfs/structs.js +255 -0
  68. package/src/sockdrive/js/src/fatfs/vol.js +166 -0
  69. package/src/sockdrive/js/src/sockdrive/cache.ts +133 -0
  70. package/src/sockdrive/js/src/sockdrive/drive.ts +364 -0
  71. package/src/sockdrive/js/src/sockdrive/lru.ts +221 -0
  72. package/src/sockdrive/js/src/sockdrive/types.ts +10 -0
  73. package/src/sockdrive/js/src/sockdrive-fat.d.ts +64 -0
  74. package/src/sockdrive/js/src/sockdrive-fat.ts +228 -0
  75. package/src/sockdrive/js/src/sockdrive-native.ts +98 -0
  76. package/src/sockdrive/js/src/test/test.ts +186 -0
  77. package/src/sockdrive/js/tsconfig.json +11 -0
  78. package/src/sockdrive/js/webpack.config.js +68 -0
  79. package/src/sockdrive/js/yarn.lock +1808 -0
  80. package/src/sockdrive/lz4/lz4.c +2773 -0
  81. package/src/sockdrive/lz4/lz4.h +862 -0
  82. package/src/store/auth.ts +104 -0
  83. package/src/store/dos.ts +284 -0
  84. package/src/store/editor.ts +59 -0
  85. package/src/store/storage.ts +30 -0
  86. package/src/store/ui.ts +183 -0
  87. package/src/store.ts +27 -0
  88. package/src/ui.tsx +103 -0
  89. package/src/v8/changes.ts +56 -0
  90. package/src/v8/config.ts +18 -0
  91. package/src/v8/subscriptions.ts +62 -0
  92. package/src/vite-env.d.ts +2 -0
  93. package/src/window/dos/controls/keyboard.ts +32 -0
  94. package/src/window/dos/controls/keys.ts +331 -0
  95. package/src/window/dos/controls/mouse/mount.ts +99 -0
  96. package/src/window/dos/controls/mouse/mouse-locked.ts +51 -0
  97. package/src/window/dos/controls/mouse/mouse-not-locked.ts +84 -0
  98. package/src/window/dos/controls/mouse/mouse-swipe.ts +57 -0
  99. package/src/window/dos/controls/mouse/pointer.ts +85 -0
  100. package/src/window/dos/controls/mouse.ts +21 -0
  101. package/src/window/dos/dos-dhry2.tsx +191 -0
  102. package/src/window/dos/dos-runtime.tsx +185 -0
  103. package/src/window/dos/dos-window.tsx +82 -0
  104. package/src/window/dos/render/canvas.ts +65 -0
  105. package/src/window/dos/render/resize.ts +31 -0
  106. package/src/window/dos/render/webgl.ts +173 -0
  107. package/src/window/dos/sound/audio-node.ts +148 -0
  108. package/src/window/error-window.tsx +9 -0
  109. package/src/window/loading-window.tsx +54 -0
  110. package/src/window/prerun-window.tsx +98 -0
  111. package/src/window/select-window.tsx +118 -0
  112. package/src/window/window.css +78 -0
  113. package/src/window/window.tsx +49 -0
  114. package/src/ws/ws-sockdrive.ts +102 -0
  115. package/src/ws/ws-transport-layer.ts +457 -0
  116. package/tailwind.config.cjs +48 -0
  117. package/tsconfig.json +32 -0
  118. package/tsconfig.node.json +9 -0
  119. package/vite.config.ts +22 -0
@@ -0,0 +1,457 @@
1
+ import {
2
+ TransportLayer, ClientMessage, MessageHandler,
3
+ ServerMessage, DataChunk,
4
+ } from "emulators/dist/types/protocol/protocol";
5
+
6
+ import { createSockdrive } from "./ws-sockdrive";
7
+
8
+ const sockdriveConfirmWrite = false;
9
+
10
+ export interface Hardware {
11
+ readConfig(): string;
12
+ sendMessage(payload: string): void;
13
+ addKey(key: number, pressed: number, timeMs: number): void;
14
+ mouseMove(x: number, y: number, relative: boolean, timeMs: number): void;
15
+ mouseButton(button: number, pressed: number, timeMs: number): void;
16
+ getFramePayload(): string;
17
+
18
+ createFile(path: string): string;
19
+ appendFile(blob: string): string;
20
+ writeFile(path: string): string;
21
+ closeFile(): string;
22
+ }
23
+
24
+ const textDecoder = new TextDecoder();
25
+ const textEncoder = new TextEncoder();
26
+ const fMultiplier = 200000000;
27
+
28
+ // eslint-disable-next-line max-len
29
+ const clientMessageValues: ClientMessage[] = [
30
+ "wc-install", "wc-run", "wc-pack-fs-to-bundle", "wc-add-key", "wc-mouse-move", "wc-mouse-button", "wc-mouse-sync",
31
+ "wc-exit", "wc-sync-sleep", "wc-pause", "wc-resume", "wc-mute", "wc-unmute", "wc-connect", "wc-disconnect",
32
+ "wc-backend-event", "wc-asyncify-stats", "wc-fs-tree", "wc-fs-get-file", "wc-send-data-chunk",
33
+ ];
34
+ const clientMessageEnum: { [msg: string]: number } = {};
35
+ clientMessageValues.forEach((v, i) => clientMessageEnum[v] = i);
36
+
37
+ const serverMessageValues: ServerMessage[] = [
38
+ "ws-extract-progress", "ws-ready", "ws-server-ready", "ws-frame-set-size",
39
+ "ws-update-lines", "ws-log", "ws-warn", "ws-err",
40
+ "ws-stdout", "ws-exit", "ws-persist", "ws-sound-init", "ws-sound-push",
41
+ "ws-config", "ws-sync-sleep", "ws-connected", "ws-disconnected",
42
+ "ws-asyncify-stats", "ws-fs-tree", "ws-send-data-chunk",
43
+ ];
44
+ const serverMessageEnum: { [num: string]: ServerMessage } = {};
45
+ serverMessageValues.forEach((v, i) => serverMessageEnum[i] = v);
46
+
47
+ export class WsTransportLayer implements TransportLayer {
48
+ socket: WebSocket;
49
+ sessionId: string = Date.now() + "";
50
+ hardware: Hardware;
51
+
52
+ private cycles = 0;
53
+ private sockdrive = createSockdrive(this.onSockdriveOpen.bind(this), this.onSockdriveError.bind(this));
54
+
55
+ private handler: MessageHandler = () => {/**/};
56
+
57
+ private writeUint32(container: Uint8Array, value: number, offset: number) {
58
+ container[offset] = value & 0xFF;
59
+ container[offset + 1] = (value & 0x0000FF00) >> 8;
60
+ container[offset + 2] = (value & 0x00FF0000) >> 16;
61
+ container[offset + 3] = (value & 0xFF000000) >> 24;
62
+ return offset + 4;
63
+ }
64
+
65
+ private readUint32(container: Uint8Array, offset: number) {
66
+ return (container[offset] & 0x000000FF) |
67
+ ((container[offset + 1] << 8) & 0x0000FF00) |
68
+ ((container[offset + 2] << 16) & 0x00FF0000) |
69
+ ((container[offset + 3] << 24) & 0xFF000000);
70
+ }
71
+
72
+ private readUint64(container: Uint8Array, offset: number) {
73
+ return this.readUint32(container, offset) + this.readUint32(container, offset + 4) * 2 ** 32;
74
+ }
75
+
76
+ private sendMessageToSocket(id: ClientMessage | number, ...payload: (Uint8Array | null)[]) {
77
+ let length = 1;
78
+ for (const next of payload) {
79
+ length += 4 + (next?.length ?? 0);
80
+ }
81
+
82
+ const encoded = new Uint8Array(length);
83
+ encoded[0] = typeof id === "string" ? clientMessageEnum[id] : id;
84
+ let position = 1;
85
+ for (const next of payload) {
86
+ this.writeUint32(encoded, next?.length ?? 0, position);
87
+ position += 4;
88
+ if (next) {
89
+ encoded.set(next, position);
90
+ position += next.length;
91
+ }
92
+ }
93
+
94
+ this.socket.send(encoded);
95
+ }
96
+
97
+ private readMessage(payload: Uint8Array): { id: number, payload: (Uint8Array | null)[] } {
98
+ const out: Uint8Array[] = [];
99
+ let position = 1;
100
+ while (position + 4 <= payload.length) {
101
+ const length = this.readUint32(payload, position);
102
+ position += 4;
103
+
104
+ if (position + length > payload.length) {
105
+ throw new Error("read_message out of bounds");
106
+ } else {
107
+ out.push(payload.slice(position, position + length));
108
+ }
109
+
110
+ position += length;
111
+ }
112
+
113
+ return {
114
+ id: payload[0],
115
+ payload: out,
116
+ };
117
+ }
118
+
119
+ private onMessage(msg: Uint8Array) {
120
+ const { id, payload } = this.readMessage(msg);
121
+ const message = serverMessageEnum[id];
122
+ switch (message) {
123
+ case "ws-ready":
124
+ case "ws-server-ready":
125
+ case "ws-exit":
126
+ {
127
+ // delay ws-server-ready until ws-sound-init
128
+ if (message !== "ws-server-ready") {
129
+ this.handler(message, {});
130
+ }
131
+ } break;
132
+ case "ws-stdout":
133
+ case "ws-log":
134
+ case "ws-warn":
135
+ case "ws-err": {
136
+ this.handler(message, {
137
+ tag: textDecoder.decode(payload[0]!),
138
+ message: textDecoder.decode(payload[1]!),
139
+ });
140
+ } break;
141
+ case "ws-send-data-chunk": {
142
+ const chunk: DataChunk = {
143
+ type: textDecoder.decode(payload[0]!) as any,
144
+ name: textDecoder.decode(payload[1]!),
145
+ data: payload[2],
146
+ };
147
+ this.handler(message, { chunk });
148
+ } break;
149
+ case "ws-config": {
150
+ this.handler(message, {
151
+ dosboxConf: payload[0]!,
152
+ jsdosConf: textDecoder.decode(payload[1]!),
153
+ });
154
+ } break;
155
+ case "ws-sound-init": {
156
+ this.handler(message, {
157
+ freq: this.readUint32(payload[0]!, 0),
158
+ });
159
+ this.handler("ws-server-ready", {});
160
+ } break;
161
+ case "ws-sound-push": {
162
+ this.handler(message, {
163
+ samples: new Float32Array(payload[0]!.buffer),
164
+ });
165
+ } break;
166
+ case "ws-frame-set-size": {
167
+ this.handler(message, {
168
+ width: this.readUint32(payload[0]!, 0),
169
+ height: this.readUint32(payload[0]!, 4),
170
+ });
171
+ } break;
172
+ case "ws-update-lines": {
173
+ if (payload.length > 0) {
174
+ const lines: { start: number, heapu8: Uint8Array }[] = [];
175
+ for (const next of payload) {
176
+ lines.push({
177
+ start: this.readUint32(next!, 0),
178
+ heapu8: next!.slice(4),
179
+ });
180
+ }
181
+ this.handler(message, { lines });
182
+ }
183
+ } break;
184
+ case "ws-asyncify-stats": {
185
+ this.cycles += this.readUint32(payload[0]!, 0);
186
+ const stats = {
187
+ messageSent: this.readUint32(payload[0]!, 4),
188
+ messageReceived: this.readUint32(payload[0]!, 8),
189
+ messageFrame: this.readUint32(payload[0]!, 12),
190
+ messageSound: this.readUint32(payload[0]!, 16),
191
+ nonSkippableSleepCount: 0,
192
+ sleepCount: 0,
193
+ sleepTime: 0,
194
+ cycles: this.cycles,
195
+ netSent: 0,
196
+ netRecv: 0,
197
+ driveSent: this.sockdrive.stats.write,
198
+ driveRecv: this.sockdrive.stats.read,
199
+ driveRecvTime: this.sockdrive.stats.readTotalTime,
200
+ driveCacheHit: this.sockdrive.stats.cacheHit,
201
+ driveCacheMiss: this.sockdrive.stats.cacheMiss,
202
+ driveCacheUsed: this.sockdrive.stats.cacheUsed,
203
+ };
204
+ this.handler(message, stats);
205
+ } break;
206
+ case "ws-connected": {
207
+ this.handler(message, { networkType: payload[0]![0], address: textDecoder.decode(payload[1]!) });
208
+ } break;
209
+ case "ws-disconnected": {
210
+ this.handler(message, { networkType: payload[0]![0] });
211
+ } break;
212
+ default: {
213
+ if (message === undefined) { // not standard messages
214
+ (async () => {
215
+ switch (id) {
216
+ case 100/* ws-sockdrive-open */: {
217
+ const url = textDecoder.decode(payload[0]!);
218
+ const owner = textDecoder.decode(payload[1]!);
219
+ const name = textDecoder.decode(payload[2]!);
220
+ const token = textDecoder.decode(payload[3]!);
221
+ const aheadRange = this.readUint32(payload[4]!, 0);
222
+ const handle = await this.sockdrive.open(url, owner, name, token, aheadRange);
223
+ const template = this.sockdrive.template(handle);
224
+ const packet = new Uint8Array(4 * 6);
225
+ let offset = 0;
226
+ offset = this.writeUint32(packet, handle, offset);
227
+ offset = this.writeUint32(packet, template.size, offset);
228
+ offset = this.writeUint32(packet, template.heads, offset);
229
+ offset = this.writeUint32(packet, template.cylinders, offset);
230
+ offset = this.writeUint32(packet, template.sectors, offset);
231
+ this.writeUint32(packet, template.sectorSize, offset);
232
+ this.sendMessageToSocket(100, packet);
233
+ } break;
234
+ case 101/* ws-sockdrive-read */: {
235
+ const handle = this.readUint32(payload[0]!, 0);
236
+ const sector = this.readUint32(payload[0]!, 4);
237
+ const response = await this.sockdrive.read(handle, sector);
238
+ const packet = new Uint8Array(4);
239
+ this.writeUint32(packet, response.code, 0);
240
+ this.sendMessageToSocket(101, packet, response.buffer ?? null);
241
+ } break;
242
+ case 102/* ws-sockdrive-write */: {
243
+ const handle = this.readUint32(payload[0]!, 0);
244
+ const sector = this.readUint32(payload[0]!, 4);
245
+ const code = this.sockdrive.write(handle, sector, payload[1]!);
246
+ const packet = new Uint8Array(4);
247
+ this.writeUint32(packet, code, 0);
248
+ if (sockdriveConfirmWrite) {
249
+ this.sendMessageToSocket(102, packet);
250
+ }
251
+ } break;
252
+ case 103/* ws-sockdrive-close */: {
253
+ this.sockdrive.close(this.readUint32(payload[0]!, 0));
254
+ } break;
255
+ default:
256
+ console.log("WARN! Unhandled server non standard message", id, payload);
257
+ }
258
+ })().catch((e) => {
259
+ this.handler("ws-err", {
260
+ tag: "panic",
261
+ message: "sockdrive error: " + (e.message ?? "???"),
262
+ });
263
+ });
264
+ } else {
265
+ console.warn("WARN! Unhandled server message", message);
266
+ }
267
+ }
268
+ }
269
+ }
270
+
271
+ constructor(socket: WebSocket) {
272
+ this.socket = socket;
273
+ this.socket.addEventListener("error", (e) => {
274
+ this.handler("ws-err", { tag: "ws", message: (e as any).message ?? "Unknown transport layer error" });
275
+ this.handler("ws-exit", {});
276
+ this.socket.close();
277
+ });
278
+ this.socket.addEventListener("message", async (ev) => {
279
+ const blob: Blob = ev.data;
280
+ this.onMessage(new Uint8Array(await blob.arrayBuffer()));
281
+ });
282
+ this.sendMessageToSocket("wc-install");
283
+ this.hardware = (null) as any;
284
+ }
285
+
286
+ async sendMessageToServer(name: ClientMessage, props?: { [key: string]: any; }) {
287
+ if (props === undefined || props?.sessionId !== this.sessionId) {
288
+ return;
289
+ }
290
+
291
+ const messageId = clientMessageEnum[name];
292
+ switch (name) {
293
+ case "wc-run": {
294
+ const token = props.token ?? "";
295
+ this.sendMessageToSocket(messageId, textEncoder.encode(token));
296
+ } break;
297
+ // case "wc-run": {
298
+ // let errorMessage = writeFile(this.hardware, "bundle_0.zip", props.bundles[0]);
299
+
300
+ // if (errorMessage.length > 0) {
301
+ // console.error(errorMessage);
302
+ // throw new Error(errorMessage);
303
+ // }
304
+
305
+ // if (props.bundles[1] !== undefined) {
306
+ // errorMessage = writeFile(this.hardware, "bundle_1.zip", props.bundles[1]);
307
+ // if (errorMessage.length > 0) {
308
+ // console.error(errorMessage);
309
+ // throw new Error(errorMessage);
310
+ // }
311
+ // }
312
+
313
+ // const payload = "wc-run\n";
314
+ // this.hardware.sendMessage(payload);
315
+ // } break;
316
+ // case "wc-add-key": {
317
+ // this.hardware.addKey(props.key, props.pressed ? 1 : 0, props.timeMs);
318
+ // } break;
319
+ // case "wc-pause": {
320
+ // this.hardware.sendMessage("wc-pause\n" + this.sessionId + "\n");
321
+ // } break;
322
+ // case "wc-resume": {
323
+ // this.hardware.sendMessage("wc-resume\n" + this.sessionId + "\n");
324
+ // } break;
325
+ // case "wc-mute": {
326
+ // this.hardware.sendMessage("wc-mute\n" + this.sessionId + "\n");
327
+ // } break;
328
+ // case "wc-unmute": {
329
+ // this.hardware.sendMessage("wc-unmute\n" + this.sessionId + "\n");
330
+ // } break;
331
+ // case "wc-exit": {
332
+ // this.alive = false;
333
+ // this.hardware.sendMessage("wc-exit\n" + this.sessionId + "\n");
334
+ // } break;
335
+ // case "wc-mouse-move": {
336
+ // this.hardware.mouseMove(props.x, props.y, props.relative, props.timeMs);
337
+ // } break;
338
+ // case "wc-mouse-button": {
339
+ // this.hardware.mouseButton(props.button, props.pressed ? 1 : 0, props.timeMs);
340
+ // } break;
341
+ // case "wc-pack-fs-to-bundle": {
342
+ // this.hardware.sendMessage("wc-pack-fs-to-bundle\n" + this.sessionId + "\n");
343
+ // } break;
344
+ // case "wc-connect": {
345
+ // this.hardware.sendMessage("wc-connect\n" + this.sessionId + "\n" +
346
+ // props.networkType + "\n" +
347
+ // props.address.replace("ws://", "").replace("wss://", "") + "\n" +
348
+ // (props.port - 1) + "\n");
349
+ // } break;
350
+ // case "wc-disconnect": {
351
+ // this.hardware.sendMessage("wc-disconnect\n" + this.sessionId + "\n" +
352
+ // props.networkType + "\n");
353
+ // } break;
354
+ case "wc-send-data-chunk": {
355
+ const chunk: DataChunk = props.chunk;
356
+ this.sendMessageToSocket(messageId,
357
+ textEncoder.encode(chunk.type),
358
+ textEncoder.encode(chunk.name),
359
+ chunk.data ? new Uint8Array(chunk.data) : null);
360
+ } break;
361
+ case "wc-asyncify-stats": {
362
+ this.sendMessageToSocket(messageId);
363
+ } break;
364
+ case "wc-add-key": {
365
+ const payload = new Uint8Array(3 * 4);
366
+ let offset = 0;
367
+ offset = this.writeUint32(payload, props.key, offset);
368
+ offset = this.writeUint32(payload, props.pressed ? 1 : 0, offset);
369
+ this.writeUint32(payload, props.timeMs, offset);
370
+ this.sendMessageToSocket(messageId, payload);
371
+ } break;
372
+ case "wc-mouse-move": {
373
+ const payload = new Uint8Array(3 * 4 + 3);
374
+ let offset = 0;
375
+ offset = this.writeUint32(payload, Math.abs(props.x) * fMultiplier, offset);
376
+ offset = this.writeUint32(payload, Math.abs(props.y) * fMultiplier, offset);
377
+ offset = this.writeUint32(payload, props.timeMs, offset);
378
+ payload[offset] = props.relative ? 1 : 0;
379
+ payload[offset + 1] = props.x >= 0 ? 0 : 1;
380
+ payload[offset + 2] = props.y >= 0 ? 0 : 1;
381
+ this.sendMessageToSocket(messageId, payload);
382
+ } break;
383
+ case "wc-mouse-button": {
384
+ const payload = new Uint8Array(4 + 2);
385
+ payload[0] = props.button;
386
+ payload[1] = props.pressed ? 1 : 0;
387
+ this.writeUint32(payload, props.timeMs, 2);
388
+ this.sendMessageToSocket(messageId, payload);
389
+ } break;
390
+ case "wc-mouse-sync": {
391
+ const payload = new Uint8Array(4);
392
+ this.writeUint32(payload, props.timeMs, 0);
393
+ this.sendMessageToSocket(messageId, payload);
394
+ } break;
395
+ case "wc-connect": {
396
+ this.sendMessageToSocket(messageId, new Uint8Array([props.networkType]),
397
+ textEncoder.encode(props.address));
398
+ } break;
399
+ default: {
400
+ console.log("Unhandled client message (wc):", name, messageId, props);
401
+ } break;
402
+ }
403
+ }
404
+
405
+ initMessageHandler(handler: MessageHandler) {
406
+ this.handler = (message, props) => {
407
+ props.sessionId = this.sessionId;
408
+ handler(message, props);
409
+ };
410
+ }
411
+
412
+ exit() {
413
+ this.sendMessageToSocket("wc-exit");
414
+ }
415
+
416
+ onSockdriveOpen(drive: string, read: boolean, write: boolean) {
417
+ this.handler("ws-log", {
418
+ tag: "worker",
419
+ message: "sockdrive: " + drive + ", read=" + read + ", write=" + write,
420
+ });
421
+ }
422
+
423
+ onSockdriveError(e: Error) {
424
+ this.handler("ws-err", {
425
+ tag: "panic",
426
+ message: e.message ?? "unable to open sockdrive",
427
+ });
428
+ }
429
+ }
430
+
431
+ export function createWsTransportLayer(url: string): Promise<TransportLayer> {
432
+ return new Promise<TransportLayer>((resolve) => {
433
+ let locked = false;
434
+ const inervalId = setInterval(() => {
435
+ if (locked) {
436
+ return;
437
+ }
438
+
439
+ locked = true;
440
+ const ws = new WebSocket(url);
441
+ const onSuccess = () => {
442
+ clearInterval(inervalId);
443
+ console.log("Connected to", url);
444
+ resolve(new WsTransportLayer(ws));
445
+ };
446
+
447
+ ws.addEventListener("error", (error) => {
448
+ console.error("Can't conect to ", url, error);
449
+ ws.removeEventListener("open", onSuccess);
450
+ ws.close();
451
+ locked = false;
452
+ });
453
+
454
+ ws.addEventListener("open", onSuccess);
455
+ }, 1000);
456
+ });
457
+ }
@@ -0,0 +1,48 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ module.exports = {
3
+ content: [
4
+ "./index.html",
5
+ "./src/**/*.{js,ts,jsx,tsx}",
6
+ ],
7
+ theme: {
8
+ extend: {},
9
+ },
10
+ plugins: [require("daisyui")],
11
+ safelist: [
12
+ "alert-success",
13
+ "alert-error",
14
+ ],
15
+ daisyui: {
16
+ themes: [
17
+ "light",
18
+ "dark",
19
+ "cupcake",
20
+ "bumblebee",
21
+ "emerald",
22
+ "corporate",
23
+ "synthwave",
24
+ "retro",
25
+ "cyberpunk",
26
+ "valentine",
27
+ "halloween",
28
+ "garden",
29
+ "forest",
30
+ "aqua",
31
+ "lofi",
32
+ "pastel",
33
+ "fantasy",
34
+ "wireframe",
35
+ "black",
36
+ "luxury",
37
+ "dracula",
38
+ "cmyk",
39
+ "autumn",
40
+ "business",
41
+ "acid",
42
+ "lemonade",
43
+ "night",
44
+ "coffee",
45
+ "winter",
46
+ ],
47
+ }
48
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "compilerOptions": {
3
+ "paths": {
4
+ "react": ["./node_modules/preact/compat/"],
5
+ "react-dom": ["./node_modules/preact/compat/"]
6
+ },
7
+ "target": "ESNext",
8
+ "useDefineForClassFields": true,
9
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
10
+ "allowJs": false,
11
+ "skipLibCheck": true,
12
+ "esModuleInterop": false,
13
+ "allowSyntheticDefaultImports": true,
14
+ "strict": true,
15
+ "forceConsistentCasingInFileNames": true,
16
+ "module": "ESNext",
17
+ "moduleResolution": "Node",
18
+ "resolveJsonModule": true,
19
+ "isolatedModules": true,
20
+ "noEmit": true,
21
+ "jsx": "react-jsx",
22
+ "jsxImportSource": "preact"
23
+ },
24
+ "include": ["src"],
25
+ "exclude": [
26
+ "src/sockdrive/js/src/test",
27
+ "src/sockdrive/js/src/sockdrive-fat.ts",
28
+ "src/sockdrive/js/src/sockdrive-native.ts",
29
+ "src/sockdrive/js/src/fatfs"
30
+ ],
31
+ "references": [{ "path": "./tsconfig.node.json" }]
32
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "module": "ESNext",
5
+ "moduleResolution": "Node",
6
+ "allowSyntheticDefaultImports": true
7
+ },
8
+ "include": ["vite.config.ts"]
9
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,22 @@
1
+ import { defineConfig } from "vite";
2
+ import preact from "@preact/preset-vite";
3
+
4
+ // https://vitejs.dev/config/
5
+ export default defineConfig({
6
+ plugins: [preact()],
7
+ server: {
8
+ port: 3000,
9
+ host: "0.0.0.0",
10
+ cors: true,
11
+ },
12
+ build: {
13
+ rollupOptions: {
14
+ output: {
15
+ entryFileNames: "js-dos.js",
16
+ assetFileNames: (info) => {
17
+ return info.name === "index.css" ? "js-dos.css" : info.name;
18
+ },
19
+ },
20
+ },
21
+ },
22
+ });