quickwin 1.0.0
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.en.md +197 -0
- package/README.md +196 -0
- package/examples/PdfCanvas.js +67 -0
- package/examples/PdfCanvas.tsx +83 -0
- package/examples/pdf_preview2.js +431 -0
- package/examples/pdf_preview2.ts +375 -0
- package/examples/pdf_viewer.js +17623 -0
- package/examples/pdf_viewer.tsx +279 -0
- package/examples/test_gallery.js +17452 -0
- package/examples/test_gallery.tsx +285 -0
- package/examples/test_react_lazy.js +17336 -0
- package/examples/test_react_lazy.tsx +63 -0
- package/examples/test_react_listbox.js +17339 -0
- package/examples/test_react_listbox.tsx +71 -0
- package/examples/test_root_window.js +17313 -0
- package/examples/test_root_window.tsx +41 -0
- package/examples/test_tab.js +17339 -0
- package/examples/test_tab.tsx +75 -0
- package/examples/tray_demo.js +79 -0
- package/examples/tray_demo.tsx +81 -0
- package/lib/assert.js +8 -0
- package/lib/assert.ts +7 -0
- package/lib/cache-utils.js +43 -0
- package/lib/cache-utils.ts +40 -0
- package/lib/dumpRects.js +25 -0
- package/lib/dumpRects.ts +25 -0
- package/lib/fetch.js +490 -0
- package/lib/fetch.ts +502 -0
- package/lib/http-common.js +342 -0
- package/lib/http-common.ts +399 -0
- package/lib/http-server.js +416 -0
- package/lib/http-server.ts +424 -0
- package/lib/polyfill.js +156 -0
- package/lib/polyfill.ts +171 -0
- package/lib/react-qw/components/Button.js +8 -0
- package/lib/react-qw/components/CheckBox.js +31 -0
- package/lib/react-qw/components/ComboBox.js +42 -0
- package/lib/react-qw/components/DateTimePicker.js +96 -0
- package/lib/react-qw/components/Input.js +50 -0
- package/lib/react-qw/components/Link.js +43 -0
- package/lib/react-qw/components/ListBox.js +48 -0
- package/lib/react-qw/components/ListView.js +328 -0
- package/lib/react-qw/components/PathPicker.js +136 -0
- package/lib/react-qw/components/ProgressBar.js +22 -0
- package/lib/react-qw/components/RadioButton.js +31 -0
- package/lib/react-qw/components/ScrollView.js +139 -0
- package/lib/react-qw/components/Slider.js +45 -0
- package/lib/react-qw/components/Tab.js +61 -0
- package/lib/react-qw/components/Tooltip.js +55 -0
- package/lib/react-qw/components/TreeView.js +130 -0
- package/lib/react-qw/components/index.js +16 -0
- package/lib/react-qw/components/index.ts +17 -0
- package/lib/react-qw/index.js +73 -0
- package/lib/react-qw/index.ts +90 -0
- package/lib/react-qw/jsx.d.ts +34 -0
- package/lib/react-qw/layout.js +82 -0
- package/lib/react-qw/layout.ts +111 -0
- package/lib/react-qw/props.js +27 -0
- package/lib/react-qw/props.ts +36 -0
- package/lib/react-qw/reconciler.js +333 -0
- package/lib/react-qw/reconciler.ts +373 -0
- package/lib/stream.js +2 -0
- package/lib/stream.ts +22 -0
- package/lib/text-codec.js +197 -0
- package/lib/text-codec.ts +171 -0
- package/lib/text-measure.js +52 -0
- package/lib/text-measure.ts +54 -0
- package/lib/url.js +278 -0
- package/lib/url.ts +201 -0
- package/lib/vendor/web-streams/ponyfill.d.mts +829 -0
- package/lib/vendor/web-streams/ponyfill.mjs +8 -0
- package/lib/websocket.js +541 -0
- package/lib/websocket.ts +555 -0
- package/package.json +44 -0
- package/quickwin.d.ts +594 -0
- package/quickwin_const.d.ts +900 -0
- package/qwin-nowasm.exe +0 -0
- package/qwin.exe +0 -0
- package/test/add.wasm +0 -0
- package/test/complex.wasm +0 -0
- package/test/complex_imports.wasm +0 -0
- package/test/debug/http_server_e2e.js +31 -0
- package/test/debug/minwasm.js +52 -0
- package/test/debug/mupdf_debug.js +104 -0
- package/test/debug/wasm.js +122 -0
- package/test/debug/wasm_export_debug.js +64 -0
- package/test/debug/wasm_names.js +46 -0
- package/test/global_imports.wasm +0 -0
- package/test/import_func.wasm +0 -0
- package/test/imports.wasm +0 -0
- package/test/run.js +91 -0
- package/test/run.ts +96 -0
- package/test/sjlj.wasm +0 -0
- package/test/test_basic.js +7 -0
- package/test/test_basic.ts +9 -0
- package/test/test_basic_fetch.js +34 -0
- package/test/test_basic_fetch.ts +33 -0
- package/test/test_brotli.js +48 -0
- package/test/test_brotli.ts +52 -0
- package/test/test_fetch_cache.js +169 -0
- package/test/test_fetch_cache.ts +187 -0
- package/test/test_ffi.js +157 -0
- package/test/test_ffi.ts +174 -0
- package/test/test_frame_encoding.js +128 -0
- package/test/test_frame_encoding.ts +132 -0
- package/test/test_helper.js +84 -0
- package/test/test_helper.ts +80 -0
- package/test/test_http_import.js +78 -0
- package/test/test_http_import.ts +74 -0
- package/test/test_http_server.js +142 -0
- package/test/test_http_server.ts +136 -0
- package/test/test_import_dir.js +4 -0
- package/test/test_import_dir.ts +5 -0
- package/test/test_ipv6.js +134 -0
- package/test/test_ipv6.ts +129 -0
- package/test/test_jsx.js +1314 -0
- package/test/test_meta_url.js +3 -0
- package/test/test_meta_url.ts +4 -0
- package/test/test_mupdf_render.js +69 -0
- package/test/test_mupdf_render.ts +74 -0
- package/test/test_mupdf_twice.js +77 -0
- package/test/test_mupdf_twice.ts +81 -0
- package/test/test_mupdf_wasm.js +33 -0
- package/test/test_mupdf_wasm.ts +30 -0
- package/test/test_net_event.js +57 -0
- package/test/test_net_event.ts +55 -0
- package/test/test_net_fetch.js +259 -0
- package/test/test_net_fetch.ts +238 -0
- package/test/test_net_websocket.js +180 -0
- package/test/test_net_websocket.ts +164 -0
- package/test/test_polyfill.js +144 -0
- package/test/test_polyfill.ts +159 -0
- package/test/test_react_complex.js +17440 -0
- package/test/test_react_counter.js +17331 -0
- package/test/test_react_flex.js +17643 -0
- package/test/test_react_hidden.js +17336 -0
- package/test/test_react_listview_link.js +17371 -0
- package/test/test_react_render.js +17326 -0
- package/test/test_url.js +173 -0
- package/test/test_url.ts +183 -0
- package/test/test_wasm_basic.js +85 -0
- package/test/test_wasm_basic.ts +73 -0
- package/test/test_wasm_import_global.js +41 -0
- package/test/test_wasm_import_global.ts +45 -0
- package/test/test_wasm_sjlj.js +145 -0
- package/test/test_wasm_sjlj.ts +125 -0
- package/test/test_wasm_types.js +97 -0
- package/test/test_wasm_types.ts +124 -0
- package/test/test_worker.js +21 -0
- package/test/test_worker.ts +24 -0
- package/test/test_worker_fetch.js +26 -0
- package/test/test_worker_fetch.ts +27 -0
- package/test/test_worker_net.js +51 -0
- package/test/test_worker_net.ts +54 -0
- package/test/test_worker_wasm_concurrent.js +44 -0
- package/test/test_worker_wasm_concurrent.ts +45 -0
- package/test/types.wasm +0 -0
- package/test/worker_fetch.js +13 -0
- package/test/worker_fetch.ts +13 -0
- package/test/worker_helper.js +1 -0
- package/test/worker_helper.ts +1 -0
- package/test/worker_http.js +18 -0
- package/test/worker_http.ts +20 -0
- package/test/worker_import_lib.js +12 -0
- package/test/worker_import_lib.ts +13 -0
- package/test/worker_module.js +16 -0
- package/test/worker_module.ts +17 -0
- package/test/worker_wasm_concurrent.js +22 -0
- package/test/worker_wasm_concurrent.ts +22 -0
- package/tsconfig.json +27 -0
- package/vendor/mupdf-wasm/mupdf-wasm.d.ts +571 -0
- package/vendor/mupdf-wasm/mupdf-wasm.js +2749 -0
- package/vendor/mupdf-wasm/mupdf-wasm.wasm +0 -0
- package/vendor/mupdf-wasm/mupdf.d.ts +939 -0
- package/vendor/mupdf-wasm/mupdf.js +3317 -0
package/test/test_ffi.ts
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import * as std from 'std'
|
|
2
|
+
import * as win from 'win'
|
|
3
|
+
import * as ffi from 'ffi'
|
|
4
|
+
import { Tester } from './test_helper.js'
|
|
5
|
+
|
|
6
|
+
function decodeWideAtPtr(ptr: number): string {
|
|
7
|
+
if (!ptr) return ''
|
|
8
|
+
const chars: number[] = []
|
|
9
|
+
let pos = ptr
|
|
10
|
+
while (true) {
|
|
11
|
+
const low = ffi.readByte(pos)
|
|
12
|
+
const high = ffi.readByte(pos + 1)
|
|
13
|
+
const ch = low + high * 256
|
|
14
|
+
if (ch === 0) break
|
|
15
|
+
chars.push(ch)
|
|
16
|
+
pos += 2
|
|
17
|
+
}
|
|
18
|
+
return String.fromCharCode(...chars)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function readPtr(dv: DataView, offset: number): number {
|
|
22
|
+
const low = dv.getUint32(offset, true)
|
|
23
|
+
const high = dv.getUint32(offset + 4, true)
|
|
24
|
+
return low + high * 4294967296
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const STATUS_FLAGS: [number, string][] = [
|
|
28
|
+
[0x00000001, 'PAUSED'],
|
|
29
|
+
[0x00000002, 'ERROR'],
|
|
30
|
+
[0x00000004, 'PENDING_DELETION'],
|
|
31
|
+
[0x00000008, 'PAPER_JAM'],
|
|
32
|
+
[0x00000010, 'PAPER_OUT'],
|
|
33
|
+
[0x00000020, 'MANUAL_FEED'],
|
|
34
|
+
[0x00000040, 'PAPER_PROBLEM'],
|
|
35
|
+
[0x00000080, 'OFFLINE'],
|
|
36
|
+
[0x00000100, 'IO_ACTIVE'],
|
|
37
|
+
[0x00000200, 'BUSY'],
|
|
38
|
+
[0x00000400, 'PRINTING'],
|
|
39
|
+
[0x00000800, 'OUTPUT_BIN_FULL'],
|
|
40
|
+
[0x00001000, 'NOT_AVAILABLE'],
|
|
41
|
+
[0x00002000, 'WAITING'],
|
|
42
|
+
[0x00004000, 'PROCESSING'],
|
|
43
|
+
[0x00008000, 'INITIALIZING'],
|
|
44
|
+
[0x00010000, 'WARMING_UP'],
|
|
45
|
+
[0x00020000, 'TONER_LOW'],
|
|
46
|
+
[0x00040000, 'NO_TONER'],
|
|
47
|
+
[0x00080000, 'PAGE_PUNT'],
|
|
48
|
+
[0x00100000, 'USER_INTERVENTION'],
|
|
49
|
+
[0x00200000, 'OUT_OF_MEMORY'],
|
|
50
|
+
[0x00400000, 'DOOR_OPEN'],
|
|
51
|
+
[0x00800000, 'SERVER_UNKNOWN'],
|
|
52
|
+
[0x01000000, 'POWER_SAVE'],
|
|
53
|
+
[0x02000000, 'SERVER_OFFLINE'],
|
|
54
|
+
[0x04000000, 'DRIVER_UPDATE'],
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
function formatStatus(status: number): string {
|
|
58
|
+
if (status === 0) return 'OK'
|
|
59
|
+
const parts: string[] = []
|
|
60
|
+
for (const [flag, name] of STATUS_FLAGS) {
|
|
61
|
+
if (status & flag) parts.push(name)
|
|
62
|
+
}
|
|
63
|
+
return parts.join('|') || 'UNKNOWN'
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const suite = {
|
|
67
|
+
name: 'ffi',
|
|
68
|
+
run: (t: Tester) => {
|
|
69
|
+
t.section('EnumPrintersW')
|
|
70
|
+
const hWinspool = win.LoadLibrary('winspool.drv')
|
|
71
|
+
t.checkTrue('LoadLibrary("winspool.drv") succeeds', hWinspool !== null)
|
|
72
|
+
if (!hWinspool) return
|
|
73
|
+
|
|
74
|
+
const enumPrinters = win.GetProcAddress(hWinspool, 'EnumPrintersW')
|
|
75
|
+
t.checkTrue('GetProcAddress("EnumPrintersW") succeeds', enumPrinters !== null)
|
|
76
|
+
if (!enumPrinters) return
|
|
77
|
+
|
|
78
|
+
const flags = 0x06
|
|
79
|
+
const level = 2
|
|
80
|
+
const neededBuf = new Uint32Array(new ArrayBuffer(4))
|
|
81
|
+
const returnedBuf = new Uint32Array(new ArrayBuffer(4))
|
|
82
|
+
|
|
83
|
+
const ret1 = ffi.ffiCall(
|
|
84
|
+
enumPrinters,
|
|
85
|
+
[
|
|
86
|
+
ffi.FFI_TYPE_UINT32,
|
|
87
|
+
ffi.FFI_TYPE_POINTER,
|
|
88
|
+
ffi.FFI_TYPE_UINT32,
|
|
89
|
+
ffi.FFI_TYPE_POINTER,
|
|
90
|
+
ffi.FFI_TYPE_UINT32,
|
|
91
|
+
ffi.FFI_TYPE_POINTER,
|
|
92
|
+
ffi.FFI_TYPE_POINTER,
|
|
93
|
+
],
|
|
94
|
+
[
|
|
95
|
+
flags,
|
|
96
|
+
null,
|
|
97
|
+
level,
|
|
98
|
+
null,
|
|
99
|
+
0,
|
|
100
|
+
neededBuf.buffer,
|
|
101
|
+
returnedBuf.buffer,
|
|
102
|
+
],
|
|
103
|
+
ffi.FFI_TYPE_SINT32
|
|
104
|
+
)
|
|
105
|
+
std.printf(' first call: ret=%d needed=%d returned=%d\n', ret1, neededBuf[0], returnedBuf[0])
|
|
106
|
+
t.checkTrue('pcbNeeded > 0', neededBuf[0]! > 0)
|
|
107
|
+
if (neededBuf[0]! <= 0) return
|
|
108
|
+
|
|
109
|
+
const printerBuf = new ArrayBuffer(neededBuf[0]!)
|
|
110
|
+
const ret2 = ffi.ffiCall(
|
|
111
|
+
enumPrinters,
|
|
112
|
+
[
|
|
113
|
+
ffi.FFI_TYPE_UINT32,
|
|
114
|
+
ffi.FFI_TYPE_POINTER,
|
|
115
|
+
ffi.FFI_TYPE_UINT32,
|
|
116
|
+
ffi.FFI_TYPE_POINTER,
|
|
117
|
+
ffi.FFI_TYPE_UINT32,
|
|
118
|
+
ffi.FFI_TYPE_POINTER,
|
|
119
|
+
ffi.FFI_TYPE_POINTER,
|
|
120
|
+
],
|
|
121
|
+
[
|
|
122
|
+
flags,
|
|
123
|
+
null,
|
|
124
|
+
level,
|
|
125
|
+
printerBuf,
|
|
126
|
+
neededBuf[0]!,
|
|
127
|
+
neededBuf.buffer,
|
|
128
|
+
returnedBuf.buffer,
|
|
129
|
+
],
|
|
130
|
+
ffi.FFI_TYPE_SINT32
|
|
131
|
+
)
|
|
132
|
+
t.checkTrue('EnumPrintersW succeeds', ret2 !== 0)
|
|
133
|
+
if (ret2 === 0) return
|
|
134
|
+
|
|
135
|
+
std.printf(' printers found: %d\n', returnedBuf[0])
|
|
136
|
+
|
|
137
|
+
if (returnedBuf[0]! > 0) {
|
|
138
|
+
const dv = new DataView(printerBuf)
|
|
139
|
+
const structSize = 136
|
|
140
|
+
|
|
141
|
+
for (let i = 0; i < returnedBuf[0]! && i < 20; i++) {
|
|
142
|
+
const off = i * structSize
|
|
143
|
+
const name = decodeWideAtPtr(readPtr(dv, off + 8))
|
|
144
|
+
const port = decodeWideAtPtr(readPtr(dv, off + 24))
|
|
145
|
+
const driver = decodeWideAtPtr(readPtr(dv, off + 32))
|
|
146
|
+
const location = decodeWideAtPtr(readPtr(dv, off + 48))
|
|
147
|
+
const comment = decodeWideAtPtr(readPtr(dv, off + 40))
|
|
148
|
+
const status = dv.getUint32(off + 124, true)
|
|
149
|
+
std.printf(' [%d] %s\n', i + 1, name)
|
|
150
|
+
std.printf(' port: %s\n', port)
|
|
151
|
+
std.printf(' driver: %s\n', driver)
|
|
152
|
+
if (location) std.printf(' location: %s\n', location)
|
|
153
|
+
if (comment) std.printf(' comment: %s\n', comment)
|
|
154
|
+
std.printf(' status: 0x%08X (%s)\n', status, formatStatus(status))
|
|
155
|
+
}
|
|
156
|
+
} else {
|
|
157
|
+
std.printf(' no printers installed (skip)\n')
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
t.section('pointer arg type check')
|
|
161
|
+
let threw = false
|
|
162
|
+
try {
|
|
163
|
+
ffi.ffiCall(
|
|
164
|
+
enumPrinters,
|
|
165
|
+
[ffi.FFI_TYPE_POINTER] as any,
|
|
166
|
+
[123] as any,
|
|
167
|
+
ffi.FFI_TYPE_VOID
|
|
168
|
+
)
|
|
169
|
+
} catch (e) {
|
|
170
|
+
threw = true
|
|
171
|
+
}
|
|
172
|
+
t.checkTrue('non-ArrayBuffer pointer arg throws TypeError', threw)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import * as std from 'std';
|
|
2
|
+
export const suite = {
|
|
3
|
+
name: 'wasm-frame-encoding',
|
|
4
|
+
run: (t) => {
|
|
5
|
+
function createFrameHeader(payloadLen) {
|
|
6
|
+
let headerSize = payloadLen < 126 ? 6 : (payloadLen < 65536 ? 8 : 14);
|
|
7
|
+
let buf = new ArrayBuffer(headerSize);
|
|
8
|
+
let view = new Uint8Array(buf);
|
|
9
|
+
let off = 0;
|
|
10
|
+
view[off++] = 0x81;
|
|
11
|
+
if (payloadLen < 126) {
|
|
12
|
+
view[off++] = 0x80 | payloadLen;
|
|
13
|
+
}
|
|
14
|
+
else if (payloadLen < 65536) {
|
|
15
|
+
view[off++] = 0x80 | 126;
|
|
16
|
+
view[off++] = (payloadLen >> 8) & 0xFF;
|
|
17
|
+
view[off++] = payloadLen & 0xFF;
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
view[off++] = 0x80 | 127;
|
|
21
|
+
const len = payloadLen;
|
|
22
|
+
const hi = Math.floor(len / 0x100000000) >>> 0;
|
|
23
|
+
const lo = len >>> 0;
|
|
24
|
+
view[off++] = (hi >> 24) & 0xFF;
|
|
25
|
+
view[off++] = (hi >> 16) & 0xFF;
|
|
26
|
+
view[off++] = (hi >> 8) & 0xFF;
|
|
27
|
+
view[off++] = hi & 0xFF;
|
|
28
|
+
view[off++] = (lo >> 24) & 0xFF;
|
|
29
|
+
view[off++] = (lo >> 16) & 0xFF;
|
|
30
|
+
view[off++] = (lo >> 8) & 0xFF;
|
|
31
|
+
view[off++] = lo & 0xFF;
|
|
32
|
+
}
|
|
33
|
+
return view;
|
|
34
|
+
}
|
|
35
|
+
function tryParseFrameLen(data) {
|
|
36
|
+
if (data.length < 2)
|
|
37
|
+
return null;
|
|
38
|
+
const b1 = data[1];
|
|
39
|
+
let payloadLen = b1 & 0x7F;
|
|
40
|
+
let offset = 2;
|
|
41
|
+
if (payloadLen === 126) {
|
|
42
|
+
if (data.length < 4)
|
|
43
|
+
return null;
|
|
44
|
+
payloadLen = (data[2] << 8) | data[3];
|
|
45
|
+
}
|
|
46
|
+
else if (payloadLen === 127) {
|
|
47
|
+
if (data.length < 10)
|
|
48
|
+
return null;
|
|
49
|
+
let hi = 0, lo = 0;
|
|
50
|
+
for (let i = 0; i < 4; i++)
|
|
51
|
+
hi = (hi * 256 + data[offset + i]) >>> 0;
|
|
52
|
+
for (let i = 4; i < 8; i++)
|
|
53
|
+
lo = (lo * 256 + data[offset + i]) >>> 0;
|
|
54
|
+
payloadLen = hi * 0x100000000 + lo;
|
|
55
|
+
}
|
|
56
|
+
return payloadLen;
|
|
57
|
+
}
|
|
58
|
+
function assert(name, ok) {
|
|
59
|
+
if (ok) {
|
|
60
|
+
t.ok++;
|
|
61
|
+
std.printf(' PASS: %s\n', name);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
t.fail++;
|
|
65
|
+
std.printf(' FAIL: %s\n', name);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// small frame (100 bytes)
|
|
69
|
+
t.section('inline length (<126)');
|
|
70
|
+
let h = createFrameHeader(100);
|
|
71
|
+
assert('length=100', h[1] === (0x80 | 100));
|
|
72
|
+
assert('parse 100', tryParseFrameLen(h) === 100);
|
|
73
|
+
// 1 (edge case)
|
|
74
|
+
h = createFrameHeader(1);
|
|
75
|
+
assert('length=1', h[1] === (0x80 | 1));
|
|
76
|
+
assert('parse 1', tryParseFrameLen(h) === 1);
|
|
77
|
+
// 125 (max inline)
|
|
78
|
+
h = createFrameHeader(125);
|
|
79
|
+
assert('length=125', h[1] === (0x80 | 125));
|
|
80
|
+
assert('parse 125', tryParseFrameLen(h) === 125);
|
|
81
|
+
// 126 (enters 16-bit extended)
|
|
82
|
+
t.section('16-bit extended length (126–65535)');
|
|
83
|
+
h = createFrameHeader(126);
|
|
84
|
+
assert('126 marker', h[1] === (0x80 | 126));
|
|
85
|
+
assert('126 value', h[2] === 0 && h[3] === 126);
|
|
86
|
+
assert('parse 126', tryParseFrameLen(h) === 126);
|
|
87
|
+
// 65535 (max 16-bit)
|
|
88
|
+
h = createFrameHeader(65535);
|
|
89
|
+
assert('65535 marker', h[1] === (0x80 | 126));
|
|
90
|
+
assert('65535 hi byte', h[2] === 0xFF);
|
|
91
|
+
assert('65535 lo byte', h[3] === 0xFF);
|
|
92
|
+
assert('parse 65535', tryParseFrameLen(h) === 65535);
|
|
93
|
+
// 65536 (enters 64-bit extended)
|
|
94
|
+
t.section('64-bit extended length (>=65536)');
|
|
95
|
+
h = createFrameHeader(65536);
|
|
96
|
+
assert('65536 marker', h[1] === (0x80 | 127));
|
|
97
|
+
assert('parse 65536', tryParseFrameLen(h) === 65536);
|
|
98
|
+
// 66000
|
|
99
|
+
h = createFrameHeader(66000);
|
|
100
|
+
assert('66000 marker', h[1] === (0x80 | 127));
|
|
101
|
+
assert('parse 66000', tryParseFrameLen(h) === 66000);
|
|
102
|
+
// 70000 — verify exact bytes
|
|
103
|
+
h = createFrameHeader(70000);
|
|
104
|
+
assert('70000 marker', h[1] === (0x80 | 127));
|
|
105
|
+
// 70000 = 0x11170, big-endian 8 bytes: 00 00 00 00 00 01 11 70
|
|
106
|
+
assert('70000 bytes big-endian', h[2] === 0x00 && h[3] === 0x00 && h[4] === 0x00 && h[5] === 0x00 &&
|
|
107
|
+
h[6] === 0x00 && h[7] === 0x01 && h[8] === 0x11 && h[9] === 0x70);
|
|
108
|
+
assert('parse 70000', tryParseFrameLen(h) === 70000);
|
|
109
|
+
// Large value near 2^32 boundary
|
|
110
|
+
t.section('large values');
|
|
111
|
+
h = createFrameHeader(0xABCD1234);
|
|
112
|
+
assert('0xABCD1234 marker', h[1] === (0x80 | 127));
|
|
113
|
+
assert('parse 0xABCD1234', tryParseFrameLen(h) === 0xABCD1234);
|
|
114
|
+
assert('MSB non-zero', h[2] === 0x00 && h[3] === 0x00 && h[4] === 0x00 && h[5] === 0x00 &&
|
|
115
|
+
h[6] === 0xAB && h[7] === 0xCD && h[8] === 0x12 && h[9] === 0x34);
|
|
116
|
+
// 0x100000001 (just above 2^32)
|
|
117
|
+
h = createFrameHeader(0x100000001);
|
|
118
|
+
assert('2^32+1 parse', tryParseFrameLen(h) === 0x100000001);
|
|
119
|
+
assert('2^32+1 bytes', h[2] === 0x00 && h[3] === 0x00 && h[4] === 0x00 && h[5] === 0x01 &&
|
|
120
|
+
h[6] === 0x00 && h[7] === 0x00 && h[8] === 0x00 && h[9] === 0x01);
|
|
121
|
+
// Full 8-byte roundtrip: 0x0123456789ABCDEF
|
|
122
|
+
// This exceeds Number's safe integer range (2^53) slightly,
|
|
123
|
+
// but our test value 0x0123456789ABCDEF = 81985529216486895 < 2^53 ✓
|
|
124
|
+
const bigVal = 0x0123456789ABCDEF;
|
|
125
|
+
h = createFrameHeader(bigVal);
|
|
126
|
+
assert('big val parse', tryParseFrameLen(h) === bigVal);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import * as std from 'std'
|
|
2
|
+
import { Tester } from './test_helper.js'
|
|
3
|
+
|
|
4
|
+
export const suite = {
|
|
5
|
+
name: 'wasm-frame-encoding',
|
|
6
|
+
run: (t: Tester) => {
|
|
7
|
+
function createFrameHeader(payloadLen: number): Uint8Array {
|
|
8
|
+
let headerSize = payloadLen < 126 ? 6 : (payloadLen < 65536 ? 8 : 14)
|
|
9
|
+
let buf = new ArrayBuffer(headerSize)
|
|
10
|
+
let view = new Uint8Array(buf)
|
|
11
|
+
let off = 0
|
|
12
|
+
view[off++] = 0x81
|
|
13
|
+
if (payloadLen < 126) {
|
|
14
|
+
view[off++] = 0x80 | payloadLen
|
|
15
|
+
} else if (payloadLen < 65536) {
|
|
16
|
+
view[off++] = 0x80 | 126
|
|
17
|
+
view[off++] = (payloadLen >> 8) & 0xFF
|
|
18
|
+
view[off++] = payloadLen & 0xFF
|
|
19
|
+
} else {
|
|
20
|
+
view[off++] = 0x80 | 127
|
|
21
|
+
const len = payloadLen
|
|
22
|
+
const hi = Math.floor(len / 0x100000000) >>> 0
|
|
23
|
+
const lo = len >>> 0
|
|
24
|
+
view[off++] = (hi >> 24) & 0xFF
|
|
25
|
+
view[off++] = (hi >> 16) & 0xFF
|
|
26
|
+
view[off++] = (hi >> 8) & 0xFF
|
|
27
|
+
view[off++] = hi & 0xFF
|
|
28
|
+
view[off++] = (lo >> 24) & 0xFF
|
|
29
|
+
view[off++] = (lo >> 16) & 0xFF
|
|
30
|
+
view[off++] = (lo >> 8) & 0xFF
|
|
31
|
+
view[off++] = lo & 0xFF
|
|
32
|
+
}
|
|
33
|
+
return view
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function tryParseFrameLen(data: Uint8Array): number | null {
|
|
37
|
+
if (data.length < 2) return null
|
|
38
|
+
const b1 = data[1]!
|
|
39
|
+
let payloadLen = b1 & 0x7F
|
|
40
|
+
let offset = 2
|
|
41
|
+
if (payloadLen === 126) {
|
|
42
|
+
if (data.length < 4) return null
|
|
43
|
+
payloadLen = (data[2]! << 8) | data[3]!
|
|
44
|
+
} else if (payloadLen === 127) {
|
|
45
|
+
if (data.length < 10) return null
|
|
46
|
+
let hi = 0, lo = 0
|
|
47
|
+
for (let i = 0; i < 4; i++) hi = (hi * 256 + data[offset + i]!) >>> 0
|
|
48
|
+
for (let i = 4; i < 8; i++) lo = (lo * 256 + data[offset + i]!) >>> 0
|
|
49
|
+
payloadLen = hi * 0x100000000 + lo
|
|
50
|
+
}
|
|
51
|
+
return payloadLen
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function assert(name: string, ok: boolean): void {
|
|
55
|
+
if (ok) { t.ok++; std.printf(' PASS: %s\n', name) }
|
|
56
|
+
else { t.fail++; std.printf(' FAIL: %s\n', name) }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// small frame (100 bytes)
|
|
60
|
+
t.section('inline length (<126)')
|
|
61
|
+
let h = createFrameHeader(100)
|
|
62
|
+
assert('length=100', h[1] === (0x80 | 100))
|
|
63
|
+
assert('parse 100', tryParseFrameLen(h) === 100)
|
|
64
|
+
|
|
65
|
+
// 1 (edge case)
|
|
66
|
+
h = createFrameHeader(1)
|
|
67
|
+
assert('length=1', h[1] === (0x80 | 1))
|
|
68
|
+
assert('parse 1', tryParseFrameLen(h) === 1)
|
|
69
|
+
|
|
70
|
+
// 125 (max inline)
|
|
71
|
+
h = createFrameHeader(125)
|
|
72
|
+
assert('length=125', h[1] === (0x80 | 125))
|
|
73
|
+
assert('parse 125', tryParseFrameLen(h) === 125)
|
|
74
|
+
|
|
75
|
+
// 126 (enters 16-bit extended)
|
|
76
|
+
t.section('16-bit extended length (126–65535)')
|
|
77
|
+
h = createFrameHeader(126)
|
|
78
|
+
assert('126 marker', h[1] === (0x80 | 126))
|
|
79
|
+
assert('126 value', h[2] === 0 && h[3] === 126)
|
|
80
|
+
assert('parse 126', tryParseFrameLen(h) === 126)
|
|
81
|
+
|
|
82
|
+
// 65535 (max 16-bit)
|
|
83
|
+
h = createFrameHeader(65535)
|
|
84
|
+
assert('65535 marker', h[1] === (0x80 | 126))
|
|
85
|
+
assert('65535 hi byte', h[2] === 0xFF)
|
|
86
|
+
assert('65535 lo byte', h[3] === 0xFF)
|
|
87
|
+
assert('parse 65535', tryParseFrameLen(h) === 65535)
|
|
88
|
+
|
|
89
|
+
// 65536 (enters 64-bit extended)
|
|
90
|
+
t.section('64-bit extended length (>=65536)')
|
|
91
|
+
h = createFrameHeader(65536)
|
|
92
|
+
assert('65536 marker', h[1] === (0x80 | 127))
|
|
93
|
+
assert('parse 65536', tryParseFrameLen(h) === 65536)
|
|
94
|
+
|
|
95
|
+
// 66000
|
|
96
|
+
h = createFrameHeader(66000)
|
|
97
|
+
assert('66000 marker', h[1] === (0x80 | 127))
|
|
98
|
+
assert('parse 66000', tryParseFrameLen(h) === 66000)
|
|
99
|
+
|
|
100
|
+
// 70000 — verify exact bytes
|
|
101
|
+
h = createFrameHeader(70000)
|
|
102
|
+
assert('70000 marker', h[1] === (0x80 | 127))
|
|
103
|
+
// 70000 = 0x11170, big-endian 8 bytes: 00 00 00 00 00 01 11 70
|
|
104
|
+
assert('70000 bytes big-endian',
|
|
105
|
+
h[2] === 0x00 && h[3] === 0x00 && h[4] === 0x00 && h[5] === 0x00 &&
|
|
106
|
+
h[6] === 0x00 && h[7] === 0x01 && h[8] === 0x11 && h[9] === 0x70)
|
|
107
|
+
assert('parse 70000', tryParseFrameLen(h) === 70000)
|
|
108
|
+
|
|
109
|
+
// Large value near 2^32 boundary
|
|
110
|
+
t.section('large values')
|
|
111
|
+
h = createFrameHeader(0xABCD1234)
|
|
112
|
+
assert('0xABCD1234 marker', h[1] === (0x80 | 127))
|
|
113
|
+
assert('parse 0xABCD1234', tryParseFrameLen(h) === 0xABCD1234)
|
|
114
|
+
assert('MSB non-zero',
|
|
115
|
+
h[2] === 0x00 && h[3] === 0x00 && h[4] === 0x00 && h[5] === 0x00 &&
|
|
116
|
+
h[6] === 0xAB && h[7] === 0xCD && h[8] === 0x12 && h[9] === 0x34)
|
|
117
|
+
|
|
118
|
+
// 0x100000001 (just above 2^32)
|
|
119
|
+
h = createFrameHeader(0x100000001)
|
|
120
|
+
assert('2^32+1 parse', tryParseFrameLen(h) === 0x100000001)
|
|
121
|
+
assert('2^32+1 bytes',
|
|
122
|
+
h[2] === 0x00 && h[3] === 0x00 && h[4] === 0x00 && h[5] === 0x01 &&
|
|
123
|
+
h[6] === 0x00 && h[7] === 0x00 && h[8] === 0x00 && h[9] === 0x01)
|
|
124
|
+
|
|
125
|
+
// Full 8-byte roundtrip: 0x0123456789ABCDEF
|
|
126
|
+
// This exceeds Number's safe integer range (2^53) slightly,
|
|
127
|
+
// but our test value 0x0123456789ABCDEF = 81985529216486895 < 2^53 ✓
|
|
128
|
+
const bigVal = 0x0123456789ABCDEF
|
|
129
|
+
h = createFrameHeader(bigVal)
|
|
130
|
+
assert('big val parse', tryParseFrameLen(h) === bigVal)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import * as std from 'std';
|
|
2
|
+
const GREEN = '\x1b[32m';
|
|
3
|
+
const RED = '\x1b[31m';
|
|
4
|
+
const BOLD = '\x1b[1m';
|
|
5
|
+
const RESET = '\x1b[0m';
|
|
6
|
+
function formatDuration(ms) {
|
|
7
|
+
if (ms >= 1000)
|
|
8
|
+
return (ms / 1000).toFixed(2) + 's';
|
|
9
|
+
return ms + 'ms';
|
|
10
|
+
}
|
|
11
|
+
export function readWasmFile(path) {
|
|
12
|
+
const base = import.meta.url.slice(0, import.meta.url.lastIndexOf('/') + 1);
|
|
13
|
+
const parts = (base + path).split('/');
|
|
14
|
+
const out = [];
|
|
15
|
+
for (const p of parts) {
|
|
16
|
+
if (p === '..')
|
|
17
|
+
out.pop();
|
|
18
|
+
else if (p !== '.')
|
|
19
|
+
out.push(p);
|
|
20
|
+
}
|
|
21
|
+
let filePath = out.join('/').slice(7);
|
|
22
|
+
if (filePath.length >= 3 && filePath[1] === ':')
|
|
23
|
+
filePath = filePath.slice(1);
|
|
24
|
+
const fp = std.open(filePath, 'rb');
|
|
25
|
+
if (!fp)
|
|
26
|
+
return null;
|
|
27
|
+
fp.seek(0, 2);
|
|
28
|
+
const size = fp.tell();
|
|
29
|
+
fp.seek(0, 0);
|
|
30
|
+
const buffer = new ArrayBuffer(size);
|
|
31
|
+
fp.read(buffer, 0, size);
|
|
32
|
+
fp.close();
|
|
33
|
+
return buffer;
|
|
34
|
+
}
|
|
35
|
+
export class Tester {
|
|
36
|
+
ok = 0;
|
|
37
|
+
fail = 0;
|
|
38
|
+
startTime = Date.now();
|
|
39
|
+
sectionStart = this.startTime;
|
|
40
|
+
lastSection = '';
|
|
41
|
+
section(name) {
|
|
42
|
+
const now = Date.now();
|
|
43
|
+
if (this.lastSection) {
|
|
44
|
+
const elapsed = now - this.sectionStart;
|
|
45
|
+
std.printf(' (%s)\n', formatDuration(elapsed));
|
|
46
|
+
}
|
|
47
|
+
this.sectionStart = now;
|
|
48
|
+
this.lastSection = name;
|
|
49
|
+
std.printf('\n%s=== %s ===%s\n', BOLD, name, RESET);
|
|
50
|
+
}
|
|
51
|
+
check(name, expected, actual) {
|
|
52
|
+
if (typeof expected === 'number' && typeof actual === 'number') {
|
|
53
|
+
if (Math.abs(expected - actual) < 0.0001) {
|
|
54
|
+
this.ok++;
|
|
55
|
+
std.printf(' %sPASS:%s %s = %s\n', GREEN, RESET, name, String(actual));
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
this.fail++;
|
|
59
|
+
std.printf(' %sFAIL:%s %s = %s (expected %s)\n', RED, RESET, name, String(actual), String(expected));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else if (expected === actual) {
|
|
63
|
+
this.ok++;
|
|
64
|
+
std.printf(' %sPASS:%s %s = %s\n', GREEN, RESET, name, String(actual));
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
this.fail++;
|
|
68
|
+
std.printf(' %sFAIL:%s %s = %s (expected %s)\n', RED, RESET, name, String(actual), String(expected));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
checkTrue(name, actual) {
|
|
72
|
+
this.check(name, true, actual);
|
|
73
|
+
}
|
|
74
|
+
summary() {
|
|
75
|
+
const now = Date.now();
|
|
76
|
+
if (this.lastSection) {
|
|
77
|
+
const elapsed = now - this.sectionStart;
|
|
78
|
+
std.printf(' (%s)\n', formatDuration(elapsed));
|
|
79
|
+
}
|
|
80
|
+
const total = now - this.startTime;
|
|
81
|
+
const color = this.fail > 0 ? RED : GREEN;
|
|
82
|
+
std.printf('\n%s%d/%d passed (%s)%s\n', color, this.ok, this.ok + this.fail, formatDuration(total), RESET);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import * as std from 'std'
|
|
2
|
+
|
|
3
|
+
const GREEN = '\x1b[32m'
|
|
4
|
+
const RED = '\x1b[31m'
|
|
5
|
+
const BOLD = '\x1b[1m'
|
|
6
|
+
const RESET = '\x1b[0m'
|
|
7
|
+
|
|
8
|
+
function formatDuration(ms: number): string {
|
|
9
|
+
if (ms >= 1000) return (ms / 1000).toFixed(2) + 's'
|
|
10
|
+
return ms + 'ms'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function readWasmFile(path: string): ArrayBuffer | null {
|
|
14
|
+
const base = import.meta.url.slice(0, import.meta.url.lastIndexOf('/') + 1)
|
|
15
|
+
const parts = (base + path).split('/')
|
|
16
|
+
const out: string[] = []
|
|
17
|
+
for (const p of parts) {
|
|
18
|
+
if (p === '..') out.pop()
|
|
19
|
+
else if (p !== '.') out.push(p)
|
|
20
|
+
}
|
|
21
|
+
let filePath = out.join('/').slice(7)
|
|
22
|
+
if (filePath.length >= 3 && filePath[1] === ':') filePath = filePath.slice(1)
|
|
23
|
+
const fp = std.open(filePath, 'rb')
|
|
24
|
+
if (!fp) return null
|
|
25
|
+
fp.seek(0, 2)
|
|
26
|
+
const size = fp.tell()
|
|
27
|
+
fp.seek(0, 0)
|
|
28
|
+
const buffer = new ArrayBuffer(size)
|
|
29
|
+
fp.read(buffer, 0, size)
|
|
30
|
+
fp.close()
|
|
31
|
+
return buffer
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class Tester {
|
|
35
|
+
ok = 0
|
|
36
|
+
fail = 0
|
|
37
|
+
private startTime = Date.now()
|
|
38
|
+
private sectionStart = this.startTime
|
|
39
|
+
private lastSection = ''
|
|
40
|
+
|
|
41
|
+
section(name: string): void {
|
|
42
|
+
const now = Date.now()
|
|
43
|
+
if (this.lastSection) {
|
|
44
|
+
const elapsed = now - this.sectionStart
|
|
45
|
+
std.printf(' (%s)\n', formatDuration(elapsed))
|
|
46
|
+
}
|
|
47
|
+
this.sectionStart = now
|
|
48
|
+
this.lastSection = name
|
|
49
|
+
std.printf('\n%s=== %s ===%s\n', BOLD, name, RESET)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
check(name: string, expected: unknown, actual: unknown): void {
|
|
53
|
+
if (typeof expected === 'number' && typeof actual === 'number') {
|
|
54
|
+
if (Math.abs(expected - actual) < 0.0001) {
|
|
55
|
+
this.ok++; std.printf(' %sPASS:%s %s = %s\n', GREEN, RESET, name, String(actual))
|
|
56
|
+
} else {
|
|
57
|
+
this.fail++; std.printf(' %sFAIL:%s %s = %s (expected %s)\n', RED, RESET, name, String(actual), String(expected))
|
|
58
|
+
}
|
|
59
|
+
} else if (expected === actual) {
|
|
60
|
+
this.ok++; std.printf(' %sPASS:%s %s = %s\n', GREEN, RESET, name, String(actual))
|
|
61
|
+
} else {
|
|
62
|
+
this.fail++; std.printf(' %sFAIL:%s %s = %s (expected %s)\n', RED, RESET, name, String(actual), String(expected))
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
checkTrue(name: string, actual: boolean): void {
|
|
67
|
+
this.check(name, true, actual)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
summary(): void {
|
|
71
|
+
const now = Date.now()
|
|
72
|
+
if (this.lastSection) {
|
|
73
|
+
const elapsed = now - this.sectionStart
|
|
74
|
+
std.printf(' (%s)\n', formatDuration(elapsed))
|
|
75
|
+
}
|
|
76
|
+
const total = now - this.startTime
|
|
77
|
+
const color = this.fail > 0 ? RED : GREEN
|
|
78
|
+
std.printf('\n%s%d/%d passed (%s)%s\n', color, this.ok, this.ok + this.fail, formatDuration(total), RESET)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import * as std from 'std';
|
|
2
|
+
import * as os from 'os';
|
|
3
|
+
import { parseIni } from '../lib/cache-utils.js';
|
|
4
|
+
function getCacheDir() {
|
|
5
|
+
// import.meta.url: file:///C:/.../_build/test/file.js
|
|
6
|
+
const url = import.meta.url;
|
|
7
|
+
// Strip file:// (7 chars), giving /C:/.../
|
|
8
|
+
let path = url.slice(7);
|
|
9
|
+
// Remove leading / before drive letter
|
|
10
|
+
if (path.length >= 3 && path[1] === ':')
|
|
11
|
+
path = path.slice(1);
|
|
12
|
+
// path now: C:/Users/Docker/quickwin/_build/test/file.js
|
|
13
|
+
// Go up to _build/
|
|
14
|
+
const idx = path.lastIndexOf('/');
|
|
15
|
+
if (idx < 0)
|
|
16
|
+
return '_cache';
|
|
17
|
+
const buildDir = path.slice(0, idx); // .../_build/test
|
|
18
|
+
const idx2 = buildDir.lastIndexOf('/');
|
|
19
|
+
if (idx2 < 0)
|
|
20
|
+
return '_cache';
|
|
21
|
+
return buildDir.slice(0, idx2 + 1) + '_cache'; // .../_build/_cache
|
|
22
|
+
}
|
|
23
|
+
export const suite = {
|
|
24
|
+
name: 'http-import',
|
|
25
|
+
run: async (t) => {
|
|
26
|
+
function assert(name, ok) {
|
|
27
|
+
if (ok) {
|
|
28
|
+
t.ok++;
|
|
29
|
+
std.printf(' PASS: %s\n', name);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
t.fail++;
|
|
33
|
+
std.printf(' FAIL: %s\n', name);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const url = 'https://esm.sh/left-pad@1.3.0';
|
|
37
|
+
const cacheDir = getCacheDir();
|
|
38
|
+
t.section('list cache before');
|
|
39
|
+
const [before, err] = os.readdir(cacheDir);
|
|
40
|
+
if (err !== 0) {
|
|
41
|
+
std.printf(' cache dir: %s (error %d, will be created on first request)\n', cacheDir, err);
|
|
42
|
+
}
|
|
43
|
+
t.section('import module');
|
|
44
|
+
let mod;
|
|
45
|
+
try {
|
|
46
|
+
mod = await import(url);
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
assert('import succeeds', false);
|
|
50
|
+
std.printf(' ERROR: %s\n', String(e));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
assert('import succeeds', true);
|
|
54
|
+
assert('default is function', typeof mod.default === 'function');
|
|
55
|
+
assert('left-pad works', mod.default('hello', 8) === ' hello');
|
|
56
|
+
t.section('check cache');
|
|
57
|
+
const [after, err2] = os.readdir(cacheDir);
|
|
58
|
+
assert('cache dir readable', err2 === 0);
|
|
59
|
+
if (err2 !== 0)
|
|
60
|
+
return;
|
|
61
|
+
assert('cache has body files', after.some(function (f) { return f.endsWith('.body'); }));
|
|
62
|
+
assert('cache has meta files', after.some(function (f) { return f.endsWith('.meta'); }));
|
|
63
|
+
for (const f of after) {
|
|
64
|
+
if (f.endsWith('.meta') && (err !== 0 || before.indexOf(f) < 0)) {
|
|
65
|
+
std.printf(' meta: %s\n', f);
|
|
66
|
+
const fp = std.open(cacheDir + '/' + f, 'r');
|
|
67
|
+
if (fp) {
|
|
68
|
+
const metaStr = fp.readAsString();
|
|
69
|
+
fp.close();
|
|
70
|
+
if (metaStr) {
|
|
71
|
+
const ini = parseIni(metaStr);
|
|
72
|
+
std.printf(' storedAt: %s, maxAge: %s\n', ini.meta.storedAt || '?', ini.meta.maxAge || '?');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|