@await-widget/runtime 0.0.19 → 0.0.21
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/bin/await-widget.js +302 -0
- package/package.json +7 -2
- package/types/await.d.ts +9 -0
- package/types/bridge.d.ts +1 -0
- package/types/prop.d.ts +4 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import http from "node:http";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
|
|
8
|
+
const defaultPort = 4343;
|
|
9
|
+
const zipDosTime = 0;
|
|
10
|
+
const zipDosDate = 33;
|
|
11
|
+
const excludedNames = new Set([".git", ".build", "node_modules"]);
|
|
12
|
+
const crcTable = makeCrcTable();
|
|
13
|
+
|
|
14
|
+
function main() {
|
|
15
|
+
const [command, ...args] = process.argv.slice(2);
|
|
16
|
+
if (command === "dev") {
|
|
17
|
+
startDevServer(args);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
printHelp();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function startDevServer(args) {
|
|
24
|
+
const options = parseDevArgs(args);
|
|
25
|
+
const root = path.resolve(options.root);
|
|
26
|
+
const stats = fs.statSync(root);
|
|
27
|
+
if (!stats.isDirectory()) {
|
|
28
|
+
throw new Error(`${root} is not a directory`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const token = crypto.randomBytes(6).toString("hex");
|
|
32
|
+
const server = http.createServer((request, response) => {
|
|
33
|
+
handleRequest({request, response, root, token});
|
|
34
|
+
});
|
|
35
|
+
server.listen(options.port, "0.0.0.0", () => {
|
|
36
|
+
const address = server.address();
|
|
37
|
+
const port = typeof address === "object" && address ? address.port : options.port;
|
|
38
|
+
console.log(`Await dev server serving ${root}`);
|
|
39
|
+
printServerUrls(port, token);
|
|
40
|
+
console.log("Press Ctrl+C to stop.");
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function parseDevArgs(args) {
|
|
45
|
+
let root = ".";
|
|
46
|
+
let port = defaultPort;
|
|
47
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
48
|
+
const arg = args[index];
|
|
49
|
+
if (arg === "--port") {
|
|
50
|
+
port = Number(args[index + 1]);
|
|
51
|
+
index += 1;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (arg === "--help" || arg === "-h") {
|
|
55
|
+
printHelp();
|
|
56
|
+
process.exit(0);
|
|
57
|
+
}
|
|
58
|
+
root = arg;
|
|
59
|
+
}
|
|
60
|
+
return {root, port};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function handleRequest({request, response, root, token}) {
|
|
64
|
+
const url = new URL(request.url ?? "/", "http://localhost");
|
|
65
|
+
if (url.pathname === "/") {
|
|
66
|
+
sendText(response, `Await dev server\n${urlForRequest(request, token)}\n`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (url.searchParams.get("token") !== token) {
|
|
70
|
+
sendText(response, "Forbidden\n", 403);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (url.pathname === "/await-dev/version") {
|
|
74
|
+
sendJson(response, {
|
|
75
|
+
name: path.basename(root),
|
|
76
|
+
version: projectVersion(root),
|
|
77
|
+
});
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (url.pathname === "/await-dev/snapshot.zip") {
|
|
81
|
+
const archive = zipFiles(scanFiles(root));
|
|
82
|
+
response.writeHead(200, {
|
|
83
|
+
"Content-Type": "application/zip",
|
|
84
|
+
"Content-Length": archive.length,
|
|
85
|
+
"Cache-Control": "no-store",
|
|
86
|
+
});
|
|
87
|
+
response.end(archive);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
sendText(response, "Not Found\n", 404);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function projectVersion(root) {
|
|
94
|
+
const hash = crypto.createHash("sha256");
|
|
95
|
+
for (const file of scanFiles(root)) {
|
|
96
|
+
hash.update(file.relativePath);
|
|
97
|
+
hash.update("\0");
|
|
98
|
+
hash.update(String(file.size));
|
|
99
|
+
hash.update("\0");
|
|
100
|
+
hash.update(String(Math.trunc(file.mtimeMs)));
|
|
101
|
+
hash.update("\0");
|
|
102
|
+
}
|
|
103
|
+
return `sha256-${hash.digest("hex")}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function scanFiles(root) {
|
|
107
|
+
const files = [];
|
|
108
|
+
walk(root, "", files);
|
|
109
|
+
return files;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function walk(directory, relativeDirectory, files) {
|
|
113
|
+
const entries = fs.readdirSync(directory, {withFileTypes: true})
|
|
114
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
115
|
+
for (const entry of entries) {
|
|
116
|
+
if (entry.name.startsWith(".") || excludedNames.has(entry.name)) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const relativePath = relativeDirectory ? `${relativeDirectory}/${entry.name}` : entry.name;
|
|
120
|
+
const absolutePath = path.join(directory, entry.name);
|
|
121
|
+
if (entry.isDirectory()) {
|
|
122
|
+
walk(absolutePath, relativePath, files);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (!entry.isFile()) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const stats = fs.statSync(absolutePath);
|
|
129
|
+
files.push({
|
|
130
|
+
absolutePath,
|
|
131
|
+
relativePath,
|
|
132
|
+
size: stats.size,
|
|
133
|
+
mtimeMs: stats.mtimeMs,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function zipFiles(files) {
|
|
139
|
+
const localParts = [];
|
|
140
|
+
const centralParts = [];
|
|
141
|
+
let offset = 0;
|
|
142
|
+
for (const file of files) {
|
|
143
|
+
const name = Buffer.from(file.relativePath, "utf8");
|
|
144
|
+
const data = fs.readFileSync(file.absolutePath);
|
|
145
|
+
const crc = crc32(data);
|
|
146
|
+
const localHeader = Buffer.concat([
|
|
147
|
+
u32(0x04034B50),
|
|
148
|
+
u16(20),
|
|
149
|
+
u16(0x0800),
|
|
150
|
+
u16(0),
|
|
151
|
+
u16(zipDosTime),
|
|
152
|
+
u16(zipDosDate),
|
|
153
|
+
u32(crc),
|
|
154
|
+
u32(data.length),
|
|
155
|
+
u32(data.length),
|
|
156
|
+
u16(name.length),
|
|
157
|
+
u16(0),
|
|
158
|
+
name,
|
|
159
|
+
]);
|
|
160
|
+
localParts.push(localHeader, data);
|
|
161
|
+
centralParts.push(Buffer.concat([
|
|
162
|
+
u32(0x02014B50),
|
|
163
|
+
u16(20),
|
|
164
|
+
u16(20),
|
|
165
|
+
u16(0x0800),
|
|
166
|
+
u16(0),
|
|
167
|
+
u16(zipDosTime),
|
|
168
|
+
u16(zipDosDate),
|
|
169
|
+
u32(crc),
|
|
170
|
+
u32(data.length),
|
|
171
|
+
u32(data.length),
|
|
172
|
+
u16(name.length),
|
|
173
|
+
u16(0),
|
|
174
|
+
u16(0),
|
|
175
|
+
u16(0),
|
|
176
|
+
u16(0),
|
|
177
|
+
u32(0),
|
|
178
|
+
u32(offset),
|
|
179
|
+
name,
|
|
180
|
+
]));
|
|
181
|
+
offset += localHeader.length + data.length;
|
|
182
|
+
}
|
|
183
|
+
const centralDirectory = Buffer.concat(centralParts);
|
|
184
|
+
const end = Buffer.concat([
|
|
185
|
+
u32(0x06054B50),
|
|
186
|
+
u16(0),
|
|
187
|
+
u16(0),
|
|
188
|
+
u16(files.length),
|
|
189
|
+
u16(files.length),
|
|
190
|
+
u32(centralDirectory.length),
|
|
191
|
+
u32(offset),
|
|
192
|
+
u16(0),
|
|
193
|
+
]);
|
|
194
|
+
return Buffer.concat([...localParts, centralDirectory, end]);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function makeCrcTable() {
|
|
198
|
+
const table = new Uint32Array(256);
|
|
199
|
+
for (let index = 0; index < table.length; index += 1) {
|
|
200
|
+
let value = index;
|
|
201
|
+
for (let bit = 0; bit < 8; bit += 1) {
|
|
202
|
+
value = value & 1 ? 0xEDB88320 ^ (value >>> 1) : value >>> 1;
|
|
203
|
+
}
|
|
204
|
+
table[index] = value >>> 0;
|
|
205
|
+
}
|
|
206
|
+
return table;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function crc32(data) {
|
|
210
|
+
let crc = 0xFFFFFFFF;
|
|
211
|
+
for (const byte of data) {
|
|
212
|
+
crc = crcTable[(crc ^ byte) & 0xFF] ^ (crc >>> 8);
|
|
213
|
+
}
|
|
214
|
+
return (crc ^ 0xFFFFFFFF) >>> 0;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function u16(value) {
|
|
218
|
+
const buffer = Buffer.allocUnsafe(2);
|
|
219
|
+
buffer.writeUInt16LE(value);
|
|
220
|
+
return buffer;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function u32(value) {
|
|
224
|
+
const buffer = Buffer.allocUnsafe(4);
|
|
225
|
+
buffer.writeUInt32LE(value);
|
|
226
|
+
return buffer;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function sendJson(response, value) {
|
|
230
|
+
const body = Buffer.from(`${JSON.stringify(value)}\n`);
|
|
231
|
+
response.writeHead(200, {
|
|
232
|
+
"Content-Type": "application/json",
|
|
233
|
+
"Content-Length": body.length,
|
|
234
|
+
"Cache-Control": "no-store",
|
|
235
|
+
});
|
|
236
|
+
response.end(body);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function sendText(response, value, status = 200) {
|
|
240
|
+
response.writeHead(status, {
|
|
241
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
242
|
+
"Cache-Control": "no-store",
|
|
243
|
+
});
|
|
244
|
+
response.end(value);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function printServerUrls(port, token) {
|
|
248
|
+
const networkUrls = localNetworkUrls(port, token);
|
|
249
|
+
const recommended = networkUrls.find(({address}) => isPrivateIPv4(address)) ?? networkUrls[0];
|
|
250
|
+
if (recommended) {
|
|
251
|
+
console.log("Paste this URL into Await on iPhone:");
|
|
252
|
+
console.log(recommended.url);
|
|
253
|
+
}
|
|
254
|
+
const otherUrls = [
|
|
255
|
+
{url: `http://127.0.0.1:${port}?token=${token}`},
|
|
256
|
+
...networkUrls.filter(item => item !== recommended),
|
|
257
|
+
];
|
|
258
|
+
if (otherUrls.length > 0) {
|
|
259
|
+
console.log("Other network interfaces, usually not needed:");
|
|
260
|
+
for (const item of otherUrls) {
|
|
261
|
+
console.log(item.url);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function localNetworkUrls(port, token) {
|
|
267
|
+
const urls = [];
|
|
268
|
+
for (const interfaces of Object.values(os.networkInterfaces())) {
|
|
269
|
+
for (const item of interfaces ?? []) {
|
|
270
|
+
if (item.family === "IPv4" && !item.internal) {
|
|
271
|
+
urls.push({
|
|
272
|
+
address: item.address,
|
|
273
|
+
url: `http://${item.address}:${port}?token=${token}`,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return urls;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function isPrivateIPv4(address) {
|
|
282
|
+
const parts = address.split(".").map(part => Number(part));
|
|
283
|
+
if (parts.length !== 4 || parts.some(part => !Number.isInteger(part))) {
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
return parts[0] === 10 ||
|
|
287
|
+
(parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) ||
|
|
288
|
+
(parts[0] === 192 && parts[1] === 168);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function urlForRequest(request, token) {
|
|
292
|
+
const host = request.headers.host ?? `127.0.0.1:${defaultPort}`;
|
|
293
|
+
return `http://${host}?token=${token}`;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function printHelp() {
|
|
297
|
+
console.log(`Usage:
|
|
298
|
+
await-widget dev [directory] [--port ${defaultPort}]
|
|
299
|
+
`);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@await-widget/runtime",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.21",
|
|
4
4
|
"description": "TypeScript declarations for Await widgets.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "LitoMore",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"access": "public"
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
|
+
"bin/*.js",
|
|
16
17
|
"types/*.d.ts"
|
|
17
18
|
],
|
|
18
19
|
"types": "./types/index.d.ts",
|
|
@@ -24,10 +25,14 @@
|
|
|
24
25
|
"scripts": {
|
|
25
26
|
"format:types": "xo --fix types/*.d.ts",
|
|
26
27
|
"test": "tsc --noEmit -p test/tsconfig.json",
|
|
27
|
-
"pack:dry": "npm --cache .npm-cache pack --dry-run"
|
|
28
|
+
"pack:dry": "npm --cache .npm-cache pack --dry-run",
|
|
29
|
+
"cli:test": "node bin/await-widget.js --help"
|
|
28
30
|
},
|
|
29
31
|
"devDependencies": {
|
|
30
32
|
"typescript": "^6.0.2",
|
|
31
33
|
"xo": "^2.0.2"
|
|
34
|
+
},
|
|
35
|
+
"bin": {
|
|
36
|
+
"await-widget": "bin/await-widget.js"
|
|
32
37
|
}
|
|
33
38
|
}
|
package/types/await.d.ts
CHANGED
|
@@ -227,6 +227,15 @@ declare module 'await' {
|
|
|
227
227
|
children: NativeView[];
|
|
228
228
|
},
|
|
229
229
|
): NativeView;
|
|
230
|
+
/** This is a fallback for Ticker on iOS 18 and earlier. */
|
|
231
|
+
export function ClockSecond(
|
|
232
|
+
props: ClockSecondValue &
|
|
233
|
+
ID &
|
|
234
|
+
Mods & {
|
|
235
|
+
children: NativeView[];
|
|
236
|
+
},
|
|
237
|
+
): NativeView;
|
|
238
|
+
/** Available on iOS 26 and later. */
|
|
230
239
|
export function Ticker(
|
|
231
240
|
props: TickerValue &
|
|
232
241
|
ID &
|
package/types/bridge.d.ts
CHANGED