@elefunc/send 0.1.21 → 0.1.30
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/package.json +1 -1
- package/src/core/session.ts +39 -10
- package/src/index.ts +13 -6
package/package.json
CHANGED
package/src/core/session.ts
CHANGED
|
@@ -310,11 +310,41 @@ export class SessionAbortedError extends Error {
|
|
|
310
310
|
export const isSessionAbortedError = (error: unknown): error is SessionAbortedError =>
|
|
311
311
|
error instanceof SessionAbortedError || error instanceof Error && error.name === "SessionAbortedError"
|
|
312
312
|
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
if (
|
|
316
|
-
|
|
317
|
-
|
|
313
|
+
const awaitAbortable = async <T>(promise: Promise<T>, signal?: AbortSignal | null) => {
|
|
314
|
+
if (!signal) return promise
|
|
315
|
+
if (signal.aborted) throw new SessionAbortedError()
|
|
316
|
+
return await new Promise<T>((resolve, reject) => {
|
|
317
|
+
const onAbort = () => {
|
|
318
|
+
signal.removeEventListener("abort", onAbort)
|
|
319
|
+
reject(new SessionAbortedError())
|
|
320
|
+
}
|
|
321
|
+
signal.addEventListener("abort", onAbort, { once: true })
|
|
322
|
+
promise.then(
|
|
323
|
+
value => {
|
|
324
|
+
signal.removeEventListener("abort", onAbort)
|
|
325
|
+
resolve(value)
|
|
326
|
+
},
|
|
327
|
+
error => {
|
|
328
|
+
signal.removeEventListener("abort", onAbort)
|
|
329
|
+
reject(error)
|
|
330
|
+
},
|
|
331
|
+
)
|
|
332
|
+
})
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const fetchWithTimeout = async (input: string | URL | Request, init: RequestInit, ms: number, base?: AbortSignal | null) => {
|
|
336
|
+
if (typeof AbortController !== "function") return fetch(input, init)
|
|
337
|
+
const controller = new AbortController()
|
|
338
|
+
const timeout = setTimeout(() => controller.abort(new Error(`timed out after ${ms}ms`)), ms)
|
|
339
|
+
const onAbort = () => controller.abort(new SessionAbortedError())
|
|
340
|
+
if (base?.aborted) onAbort()
|
|
341
|
+
else base?.addEventListener("abort", onAbort, { once: true })
|
|
342
|
+
try {
|
|
343
|
+
return await fetch(input, { ...init, signal: controller.signal })
|
|
344
|
+
} finally {
|
|
345
|
+
clearTimeout(timeout)
|
|
346
|
+
base?.removeEventListener("abort", onAbort)
|
|
347
|
+
}
|
|
318
348
|
}
|
|
319
349
|
|
|
320
350
|
const normalizeCandidateType = (value: unknown) => typeof value === "string" ? value.toLowerCase() : ""
|
|
@@ -490,9 +520,8 @@ export class SendSession {
|
|
|
490
520
|
this.lifecycleAbortController = typeof AbortController === "function" ? new AbortController() : null
|
|
491
521
|
this.startPeerStatsPolling()
|
|
492
522
|
this.startPulsePolling()
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
this.connectSocket()
|
|
523
|
+
await awaitAbortable(Promise.allSettled([this.loadLocalProfile(), this.probePulse()]), this.lifecycleAbortController?.signal) // Avoid the Bun Windows TUI TLS startup bug: https://github.com/oven-sh/bun/issues/28612
|
|
524
|
+
this.connectSocket() // Intentionally defer socket startup until after initial HTTPS calls: https://github.com/oven-sh/bun/issues/28612
|
|
496
525
|
await this.waitFor(() => this.socketState === "open", timeoutMs, this.lifecycleAbortController?.signal)
|
|
497
526
|
}
|
|
498
527
|
|
|
@@ -1176,7 +1205,7 @@ export class SendSession {
|
|
|
1176
1205
|
|
|
1177
1206
|
private async loadLocalProfile() {
|
|
1178
1207
|
try {
|
|
1179
|
-
const response = await
|
|
1208
|
+
const response = await fetchWithTimeout(PROFILE_URL, { cache: "no-store" }, 4000, this.lifecycleAbortController?.signal)
|
|
1180
1209
|
if (!response.ok) throw new Error(`profile ${response.status}`)
|
|
1181
1210
|
const data = await response.json()
|
|
1182
1211
|
if (this.stopped) return
|
|
@@ -1196,7 +1225,7 @@ export class SendSession {
|
|
|
1196
1225
|
this.pulse = { ...this.pulse, state: "checking", error: "" }
|
|
1197
1226
|
this.notify()
|
|
1198
1227
|
try {
|
|
1199
|
-
const response = await
|
|
1228
|
+
const response = await fetchWithTimeout(SIGNAL_PULSE_URL, { cache: "no-store" }, 3500, this.lifecycleAbortController?.signal)
|
|
1200
1229
|
if (!response.ok) throw new Error(`pulse ${response.status}`)
|
|
1201
1230
|
if (this.stopped) return
|
|
1202
1231
|
this.pulse = { state: "open", lastSettledState: "open", at: Date.now(), ms: performance.now() - startedAt, error: "" }
|
package/src/index.ts
CHANGED
|
@@ -68,7 +68,7 @@ const TURN_OPTIONS = [
|
|
|
68
68
|
["--turn-username <value>", "custom TURN username"],
|
|
69
69
|
["--turn-credential <value>", "custom TURN credential"],
|
|
70
70
|
] as const
|
|
71
|
-
const OVERWRITE_OPTION = ["--overwrite", "overwrite same-name saved files instead of creating copies"] as const
|
|
71
|
+
const OVERWRITE_OPTION = ["-o, --overwrite", "overwrite same-name saved files instead of creating copies"] as const
|
|
72
72
|
const SAVE_DIR_OPTION = ["--save-dir <dir>", "save directory", { default: "." }] as const
|
|
73
73
|
const TUI_TOGGLE_OPTIONS = [
|
|
74
74
|
["--clean <0|1>", "show only active peers when 1; show terminal peers too when 0", { default: 1 }],
|
|
@@ -80,6 +80,13 @@ export const ACCEPT_SESSION_DEFAULTS = { autoAcceptIncoming: true, autoSaveIncom
|
|
|
80
80
|
type CliOptionDefinition = readonly [flag: string, description: string, config?: { default?: unknown }]
|
|
81
81
|
const addOptions = (command: CliCommand, definitions: readonly CliOptionDefinition[]) =>
|
|
82
82
|
definitions.reduce((next, [flag, description, config]) => next.option(flag, description, config), command)
|
|
83
|
+
const normalizeCliOptions = (options: Record<string, unknown>) => {
|
|
84
|
+
const normalized = { ...options }
|
|
85
|
+
if (normalized.overwrite == null && normalized.o != null) normalized.overwrite = normalized.o
|
|
86
|
+
delete normalized.h
|
|
87
|
+
delete normalized.o
|
|
88
|
+
return normalized
|
|
89
|
+
}
|
|
83
90
|
const withTrailingHelpLine = <T extends { outputHelp: () => void }>(target: T) => {
|
|
84
91
|
const outputHelp = target.outputHelp.bind(target)
|
|
85
92
|
target.outputHelp = () => {
|
|
@@ -323,7 +330,7 @@ export const createCli = (handlers: CliHandlers = defaultCliHandlers) => {
|
|
|
323
330
|
["--json", "print a json snapshot"],
|
|
324
331
|
SAVE_DIR_OPTION,
|
|
325
332
|
...TURN_OPTIONS,
|
|
326
|
-
])).action(handlers.peers)
|
|
333
|
+
])).action(options => handlers.peers(normalizeCliOptions(options)))
|
|
327
334
|
|
|
328
335
|
withTrailingHelpLine(addOptions(cli.command("offer [...files]", "offer files to browser-compatible peers").ignoreOptionDefaultValue(), [
|
|
329
336
|
...ROOM_SELF_OPTIONS,
|
|
@@ -332,7 +339,7 @@ export const createCli = (handlers: CliHandlers = defaultCliHandlers) => {
|
|
|
332
339
|
["--json", "emit ndjson events"],
|
|
333
340
|
SAVE_DIR_OPTION,
|
|
334
341
|
...TURN_OPTIONS,
|
|
335
|
-
])).action(handlers.offer)
|
|
342
|
+
])).action((files, options) => handlers.offer(files, normalizeCliOptions(options)))
|
|
336
343
|
|
|
337
344
|
withTrailingHelpLine(addOptions(cli.command("accept", "receive and save files").ignoreOptionDefaultValue(), [
|
|
338
345
|
...ROOM_SELF_OPTIONS,
|
|
@@ -341,7 +348,7 @@ export const createCli = (handlers: CliHandlers = defaultCliHandlers) => {
|
|
|
341
348
|
["--once", "exit after the first saved incoming transfer"],
|
|
342
349
|
["--json", "emit ndjson events"],
|
|
343
350
|
...TURN_OPTIONS,
|
|
344
|
-
])).action(handlers.accept)
|
|
351
|
+
])).action(options => handlers.accept(normalizeCliOptions(options)))
|
|
345
352
|
|
|
346
353
|
withTrailingHelpLine(addOptions(cli.command("tui", "launch the interactive terminal UI").ignoreOptionDefaultValue(), [
|
|
347
354
|
...ROOM_SELF_OPTIONS,
|
|
@@ -350,7 +357,7 @@ export const createCli = (handlers: CliHandlers = defaultCliHandlers) => {
|
|
|
350
357
|
SAVE_DIR_OPTION,
|
|
351
358
|
OVERWRITE_OPTION,
|
|
352
359
|
...TURN_OPTIONS,
|
|
353
|
-
])).action(handlers.tui)
|
|
360
|
+
])).action(options => handlers.tui(normalizeCliOptions(options)))
|
|
354
361
|
|
|
355
362
|
cli.help(sections => {
|
|
356
363
|
const usage = sections.find(section => section.title === "Usage:")
|
|
@@ -391,7 +398,7 @@ export const runCli = async (argv = process.argv, handlers: CliHandlers = defaul
|
|
|
391
398
|
printSubcommandHelp(argv, handlers, "tui")
|
|
392
399
|
return
|
|
393
400
|
}
|
|
394
|
-
await handlers.tui(parsed.options)
|
|
401
|
+
await handlers.tui(normalizeCliOptions(parsed.options))
|
|
395
402
|
return
|
|
396
403
|
}
|
|
397
404
|
await cli.runMatchedCommand()
|