@elefunc/send 0.1.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.
@@ -0,0 +1,241 @@
1
+ import type { RTCIceCandidateInit, RTCSessionDescriptionInit } from "werift"
2
+
3
+ export const SIGNAL_WS_URL = "wss://sig.efn.kr/ws"
4
+ export const BASE_ICE_SERVERS = [
5
+ { urls: "stun:stun.cloudflare.com:3478" },
6
+ { urls: "stun:stun.l.google.com:19302" },
7
+ ]
8
+
9
+ export const CHUNK = 64 * 1024
10
+ export const BUFFER_HIGH = 1024 * 1024
11
+ export const FINAL_STATUSES = new Set(["complete", "rejected", "cancelled", "error"] as const)
12
+ export const SENDABLE_STATUSES = new Set(["queued", "offered", "accepted", "sending", "awaiting-done", "cancelling"] as const)
13
+
14
+ export type SocketState = "idle" | "connecting" | "open" | "closed" | "error"
15
+ export type Presence = "active" | "terminal"
16
+ export type Direction = "in" | "out"
17
+ export type TransferStatus =
18
+ | "pending"
19
+ | "queued"
20
+ | "offered"
21
+ | "accepted"
22
+ | "receiving"
23
+ | "sending"
24
+ | "awaiting-done"
25
+ | "cancelling"
26
+ | "complete"
27
+ | "rejected"
28
+ | "cancelled"
29
+ | "error"
30
+
31
+ export interface PeerProfile {
32
+ geo?: {
33
+ city?: string
34
+ region?: string
35
+ country?: string
36
+ timezone?: string
37
+ }
38
+ network?: {
39
+ colo?: string
40
+ asOrganization?: string
41
+ asn?: number
42
+ ip?: string
43
+ }
44
+ ua?: {
45
+ browser?: string
46
+ os?: string
47
+ device?: string
48
+ }
49
+ defaults?: {
50
+ autoAcceptIncoming?: boolean
51
+ autoSaveIncoming?: boolean
52
+ }
53
+ ready?: boolean
54
+ error?: string
55
+ }
56
+
57
+ export interface SignalEnvelope {
58
+ room: string
59
+ from: string
60
+ to: string
61
+ at: string
62
+ }
63
+
64
+ export interface HelloSignal extends SignalEnvelope {
65
+ kind: "hello"
66
+ name: string
67
+ turnAvailable: boolean
68
+ profile?: PeerProfile
69
+ rtcEpoch?: number
70
+ reply?: boolean
71
+ }
72
+
73
+ export interface NameSignal extends SignalEnvelope {
74
+ kind: "name"
75
+ name: string
76
+ }
77
+
78
+ export interface ProfileSignal extends SignalEnvelope {
79
+ kind: "profile"
80
+ name?: string
81
+ turnAvailable?: boolean
82
+ profile?: PeerProfile
83
+ rtcEpoch?: number
84
+ }
85
+
86
+ export interface ByeSignal extends SignalEnvelope {
87
+ kind: "bye"
88
+ }
89
+
90
+ export interface DescriptionSignal extends SignalEnvelope {
91
+ kind: "description"
92
+ name?: string
93
+ turnAvailable?: boolean
94
+ profile?: PeerProfile
95
+ rtcEpoch?: number
96
+ description: RTCSessionDescriptionInit
97
+ }
98
+
99
+ export interface CandidateSignal extends SignalEnvelope {
100
+ kind: "candidate"
101
+ name?: string
102
+ turnAvailable?: boolean
103
+ profile?: PeerProfile
104
+ rtcEpoch?: number
105
+ candidate: RTCIceCandidateInit
106
+ }
107
+
108
+ export type SignalMessage =
109
+ | HelloSignal
110
+ | NameSignal
111
+ | ProfileSignal
112
+ | ByeSignal
113
+ | DescriptionSignal
114
+ | CandidateSignal
115
+
116
+ export interface DataEnvelope {
117
+ room: string
118
+ from: string
119
+ to: string
120
+ at: string
121
+ }
122
+
123
+ export interface FileOfferMessage extends DataEnvelope {
124
+ kind: "file-offer"
125
+ transferId: string
126
+ name: string
127
+ size: number
128
+ type: string
129
+ lastModified: number
130
+ chunkSize: number
131
+ totalChunks: number
132
+ }
133
+
134
+ export interface FileAcceptMessage extends DataEnvelope {
135
+ kind: "file-accept"
136
+ transferId: string
137
+ }
138
+
139
+ export interface FileStartMessage extends DataEnvelope {
140
+ kind: "file-start"
141
+ transferId: string
142
+ }
143
+
144
+ export interface FileEndMessage extends DataEnvelope {
145
+ kind: "file-end"
146
+ transferId: string
147
+ size: number
148
+ totalChunks: number
149
+ }
150
+
151
+ export interface FileDoneMessage extends DataEnvelope {
152
+ kind: "file-done"
153
+ transferId: string
154
+ size: number
155
+ totalChunks: number
156
+ }
157
+
158
+ export interface FileRejectMessage extends DataEnvelope {
159
+ kind: "file-reject"
160
+ transferId: string
161
+ reason: string
162
+ }
163
+
164
+ export interface FileCancelMessage extends DataEnvelope {
165
+ kind: "file-cancel"
166
+ transferId: string
167
+ reason: string
168
+ }
169
+
170
+ export interface FileErrorMessage extends DataEnvelope {
171
+ kind: "file-error"
172
+ transferId: string
173
+ reason: string
174
+ }
175
+
176
+ export type DataMessage =
177
+ | FileOfferMessage
178
+ | FileAcceptMessage
179
+ | FileStartMessage
180
+ | FileEndMessage
181
+ | FileDoneMessage
182
+ | FileRejectMessage
183
+ | FileCancelMessage
184
+ | FileErrorMessage
185
+
186
+ export interface LogEntry {
187
+ id: string
188
+ at: number
189
+ kind: string
190
+ level: "info" | "error"
191
+ payload: unknown
192
+ }
193
+
194
+ export const fallbackName = "user"
195
+ export const turnStateLabel = (hasTurn: boolean) => hasTurn ? "custom-turn" : "stun"
196
+ export const stamp = () => new Date().toISOString()
197
+ export const uid = (n = 8) => [...crypto.getRandomValues(new Uint8Array(n))].map(value => (value % 36).toString(36)).join("")
198
+
199
+ export const cleanText = (value: unknown, max = 72) => `${value ?? ""}`.trim().replace(/\s+/g, " ").slice(0, max)
200
+ export const cleanRoom = (value: unknown) => cleanText(value).toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || uid(8)
201
+ export const cleanName = (value: unknown) => cleanText(value, 24).toLowerCase().replace(/[^a-z0-9]+/g, "").slice(0, 24) || fallbackName
202
+ export const cleanLocalId = (value: unknown) => cleanText(value, 24).toLowerCase().replace(/[^a-z0-9]+/g, "").slice(0, 24) || uid(8)
203
+ export const signalEpoch = (value: unknown) => Number.isSafeInteger(value) && Number(value) > 0 ? Number(value) : 0
204
+
205
+ export const buildCliProfile = (): PeerProfile => ({
206
+ ua: { browser: "send-cli", os: process.platform, device: "desktop" },
207
+ ready: true,
208
+ })
209
+
210
+ export const peerDefaultsToken = (profile?: PeerProfile) => {
211
+ const autoAcceptIncoming = typeof profile?.defaults?.autoAcceptIncoming === "boolean" ? profile.defaults.autoAcceptIncoming : null
212
+ const autoSaveIncoming = typeof profile?.defaults?.autoSaveIncoming === "boolean" ? profile.defaults.autoSaveIncoming : null
213
+ return autoAcceptIncoming === null || autoSaveIncoming === null ? "??" : `${autoAcceptIncoming ? "A" : "a"}${autoSaveIncoming ? "S" : "s"}`
214
+ }
215
+
216
+ export const displayPeerName = (name: string, id: string) => `${cleanName(name)}-${id}`
217
+ export const clamp = (value: number) => Math.max(0, Math.min(100, Number.isFinite(value) ? value : 0))
218
+
219
+ export const formatBytes = (value: number) => {
220
+ const size = Number(value) || 0
221
+ if (!size) return "0 B"
222
+ const units = ["B", "KB", "MB", "GB", "TB"]
223
+ const tier = Math.min(units.length - 1, Math.floor(Math.log(size) / Math.log(1024)))
224
+ const scaled = size / 1024 ** tier
225
+ return `${scaled >= 100 || tier === 0 ? scaled.toFixed(0) : scaled >= 10 ? scaled.toFixed(1) : scaled.toFixed(2)} ${units[tier]}`
226
+ }
227
+
228
+ export const formatRate = (value: number) => value > 0 ? `${formatBytes(value)}/s` : "0 B/s"
229
+ export const formatEta = (value: number) => !Number.isFinite(value) || value < 0 ? "—" : value < 1 ? "<1s" : value < 60 ? `${value.toFixed(0)}s` : `${Math.floor(value / 60)}m ${(value % 60).toFixed(0)}s`
230
+
231
+ export const formatDuration = (value: number) => {
232
+ const ms = Number(value) || 0
233
+ if (!Number.isFinite(ms) || ms <= 0) return "—"
234
+ if (ms < 1000) return "<1s"
235
+ const total = Math.round(ms / 1000)
236
+ if (total < 60) return `${total}s`
237
+ const hours = Math.floor(total / 3600)
238
+ const minutes = Math.floor((total % 3600) / 60)
239
+ const seconds = total % 60
240
+ return hours ? `${hours}h ${`${minutes}`.padStart(2, "0")}m` : `${minutes}m ${`${seconds}`.padStart(2, "0")}s`
241
+ }