filepizza-client 2.0.1 → 2.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.
- package/README.md +50 -4
- package/dist/filepizza-downloader.d.ts +18 -100
- package/dist/filepizza-downloader.js +88 -215
- package/dist/filepizza-downloader.js.map +1 -1
- package/dist/filepizza-uploader.d.ts +14 -95
- package/dist/filepizza-uploader.js +79 -249
- package/dist/filepizza-uploader.js.map +1 -1
- package/dist/peerjs.d.ts +12 -0
- package/dist/peerjs.js +85 -0
- package/dist/peerjs.js.map +1 -0
- package/dist/types.d.ts +6 -2
- package/dist/types.js +4 -2
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/filepizza-downloader.ts +373 -459
- package/src/filepizza-uploader.ts +237 -391
- package/src/peerjs.ts +125 -0
- package/src/types.ts +37 -28
|
@@ -1,210 +1,161 @@
|
|
|
1
1
|
// src/filepizza-uploader.ts
|
|
2
2
|
import Peer, { DataConnection } from 'peerjs'
|
|
3
3
|
import { EventEmitter } from './event-emitter'
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
FileInfo,
|
|
6
|
+
ProgressInfo,
|
|
7
|
+
ConnectionInfo,
|
|
8
|
+
ConnectionStatus,
|
|
9
|
+
MessageType,
|
|
10
|
+
PeerJSSignalingServer,
|
|
11
|
+
} from './types'
|
|
12
|
+
import { createPeer } from './peerjs'
|
|
13
|
+
|
|
14
|
+
type ChannelInfo = {
|
|
15
|
+
longSlug: string
|
|
16
|
+
shortSlug: string
|
|
17
|
+
secret?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type UploaderOptions = {
|
|
21
|
+
filePizzaServerUrl?: string
|
|
22
|
+
password?: string
|
|
23
|
+
sharedSlug?: string
|
|
24
|
+
peerJSSignalingServer?: PeerJSSignalingServer
|
|
25
|
+
discoverPeerJSSignalingServer?: boolean
|
|
26
|
+
}
|
|
5
27
|
|
|
6
28
|
/**
|
|
7
|
-
* FilePizza Uploader - connects to the FilePizza server and uploads files
|
|
29
|
+
* FilePizza Uploader - connects to the FilePizza server and uploads files.
|
|
8
30
|
*/
|
|
9
31
|
export class FilePizzaUploader extends EventEmitter {
|
|
10
|
-
private peer?: Peer
|
|
11
|
-
private connections: Map<string, any> = new Map()
|
|
12
|
-
private connectionInfoMap = new Map<string, any>()
|
|
13
|
-
private files: File[] = []
|
|
14
|
-
private password?: string
|
|
15
|
-
private filePizzaServerUrl: string
|
|
16
|
-
private channelInfo?:
|
|
17
|
-
private sharedSlug?: string
|
|
18
|
-
private
|
|
19
|
-
private
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
super();
|
|
31
|
-
this.filePizzaServerUrl = options.filePizzaServerUrl || 'http://localhost:8081';
|
|
32
|
-
this.password = options.password;
|
|
33
|
-
this.sharedSlug = options.sharedSlug;
|
|
32
|
+
private peer?: Peer
|
|
33
|
+
private connections: Map<string, any> = new Map()
|
|
34
|
+
private connectionInfoMap = new Map<string, any>()
|
|
35
|
+
private files: File[] = []
|
|
36
|
+
private password?: string
|
|
37
|
+
private filePizzaServerUrl: string
|
|
38
|
+
private channelInfo?: ChannelInfo
|
|
39
|
+
private sharedSlug?: string
|
|
40
|
+
private renewalTimer?: NodeJS.Timeout
|
|
41
|
+
private peerJSSignalingServer?: PeerJSSignalingServer
|
|
42
|
+
private discoverPeerJSSignalingServer: boolean
|
|
43
|
+
|
|
44
|
+
constructor(options: UploaderOptions = {}) {
|
|
45
|
+
super()
|
|
46
|
+
this.filePizzaServerUrl = options.filePizzaServerUrl || 'http://localhost:8081'
|
|
47
|
+
this.password = options.password
|
|
48
|
+
this.sharedSlug = options.sharedSlug
|
|
49
|
+
this.peerJSSignalingServer = options.peerJSSignalingServer
|
|
50
|
+
this.discoverPeerJSSignalingServer =
|
|
51
|
+
options.discoverPeerJSSignalingServer ?? true
|
|
34
52
|
}
|
|
35
53
|
|
|
36
|
-
/**
|
|
37
|
-
* Initialize the uploader
|
|
38
|
-
*/
|
|
39
54
|
async initialize(): Promise<void> {
|
|
40
55
|
if (this.peer) {
|
|
41
|
-
return
|
|
56
|
+
return
|
|
42
57
|
}
|
|
43
58
|
|
|
44
|
-
|
|
45
|
-
|
|
59
|
+
this.peer = await createPeer({
|
|
60
|
+
filePizzaServerUrl: this.filePizzaServerUrl,
|
|
61
|
+
peerJSSignalingServer: this.peerJSSignalingServer,
|
|
62
|
+
discoverPeerJSSignalingServer: this.discoverPeerJSSignalingServer,
|
|
63
|
+
})
|
|
46
64
|
|
|
47
|
-
|
|
48
|
-
this.peer = new Peer({
|
|
49
|
-
config: {
|
|
50
|
-
iceServers: this.iceServers || [{ urls: 'stun:stun.l.google.com:19302' }],
|
|
51
|
-
},
|
|
52
|
-
debug: 2,
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
// Wait for peer to be ready
|
|
56
|
-
if (!this.peer.id) {
|
|
57
|
-
await new Promise<void>((resolve) => {
|
|
58
|
-
const onOpen = () => {
|
|
59
|
-
this.peer?.off('open', onOpen);
|
|
60
|
-
resolve();
|
|
61
|
-
};
|
|
62
|
-
this.peer?.on('open', onOpen);
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
+
this.peer.on('connection', this.handleConnection.bind(this))
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
this.
|
|
68
|
-
|
|
69
|
-
// Create channel
|
|
70
|
-
if (this.peer.id) {
|
|
71
|
-
await this.createChannel(this.peer.id, this.sharedSlug || undefined);
|
|
72
|
-
this.startChannelRenewal();
|
|
73
|
-
}
|
|
67
|
+
await this.createChannel(this.peer.id, this.sharedSlug || undefined)
|
|
68
|
+
this.startChannelRenewal()
|
|
74
69
|
}
|
|
75
70
|
|
|
76
71
|
setPassword(password: string): void {
|
|
77
72
|
this.password = password
|
|
78
73
|
}
|
|
79
74
|
|
|
80
|
-
/**
|
|
81
|
-
* Set files to be shared
|
|
82
|
-
*/
|
|
83
75
|
setFiles(files: File[]): void {
|
|
84
|
-
this.files = Array.from(files)
|
|
76
|
+
this.files = Array.from(files)
|
|
85
77
|
|
|
86
|
-
// Update file info for existing connections
|
|
87
78
|
if (this.files.length > 0) {
|
|
88
|
-
for (const [
|
|
79
|
+
for (const [, connection] of this.connections.entries()) {
|
|
89
80
|
if (connection.status === ConnectionStatus.Ready) {
|
|
90
81
|
connection.dataConnection.send({
|
|
91
82
|
type: MessageType.Info,
|
|
92
83
|
files: this.getFileInfo(),
|
|
93
|
-
})
|
|
84
|
+
})
|
|
94
85
|
}
|
|
95
86
|
}
|
|
96
87
|
}
|
|
97
88
|
}
|
|
98
89
|
|
|
99
|
-
/**
|
|
100
|
-
* Get shareable links for the current channel
|
|
101
|
-
*/
|
|
102
90
|
getShareableLinks(): { long: string; short: string } | null {
|
|
103
91
|
if (!this.channelInfo) {
|
|
104
|
-
return null
|
|
92
|
+
return null
|
|
105
93
|
}
|
|
106
94
|
|
|
107
95
|
return {
|
|
108
96
|
long: `${this.filePizzaServerUrl}/download/${this.channelInfo.longSlug}`,
|
|
109
97
|
short: `${this.filePizzaServerUrl}/download/${this.channelInfo.shortSlug}`,
|
|
110
|
-
}
|
|
98
|
+
}
|
|
111
99
|
}
|
|
112
100
|
|
|
113
|
-
/**
|
|
114
|
-
* Stop sharing and clean up
|
|
115
|
-
*/
|
|
116
101
|
async stop(): Promise<void> {
|
|
117
|
-
// Stop channel renewal
|
|
118
102
|
if (this.renewalTimer) {
|
|
119
|
-
clearTimeout(this.renewalTimer)
|
|
120
|
-
this.renewalTimer = undefined
|
|
103
|
+
clearTimeout(this.renewalTimer)
|
|
104
|
+
this.renewalTimer = undefined
|
|
121
105
|
}
|
|
122
106
|
|
|
123
|
-
// Destroy channel if we have one
|
|
124
107
|
if (this.channelInfo) {
|
|
125
108
|
try {
|
|
126
|
-
await this.destroyChannel(this.channelInfo.shortSlug)
|
|
109
|
+
await this.destroyChannel(this.channelInfo.shortSlug)
|
|
127
110
|
} catch (error) {
|
|
128
|
-
console.error('Error destroying channel:', error)
|
|
111
|
+
console.error('Error destroying channel:', error)
|
|
129
112
|
}
|
|
130
113
|
}
|
|
131
114
|
|
|
132
|
-
|
|
133
|
-
for (const [_, connection] of this.connections.entries()) {
|
|
115
|
+
for (const [, connection] of this.connections.entries()) {
|
|
134
116
|
if (connection.dataConnection.open) {
|
|
135
|
-
connection.dataConnection.close()
|
|
117
|
+
connection.dataConnection.close()
|
|
136
118
|
}
|
|
137
119
|
}
|
|
138
120
|
|
|
139
|
-
|
|
140
|
-
this.connections.clear();
|
|
121
|
+
this.connections.clear()
|
|
141
122
|
|
|
142
|
-
// Destroy peer
|
|
143
123
|
if (this.peer) {
|
|
144
|
-
this.peer.destroy()
|
|
145
|
-
this.peer = undefined
|
|
124
|
+
this.peer.destroy()
|
|
125
|
+
this.peer = undefined
|
|
146
126
|
}
|
|
147
127
|
|
|
148
|
-
|
|
149
|
-
this.channelInfo = undefined;
|
|
128
|
+
this.channelInfo = undefined
|
|
150
129
|
}
|
|
151
130
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
method: 'POST',
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
if (!response.ok) {
|
|
162
|
-
throw new Error(`Failed to get ICE servers: ${response.status}`);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const data = await response.json();
|
|
166
|
-
this.iceServers = data.iceServers;
|
|
167
|
-
return data.iceServers;
|
|
168
|
-
} catch (error) {
|
|
169
|
-
console.error('Error getting ICE servers:', error);
|
|
170
|
-
return [{ urls: 'stun:stun.l.google.com:19302' }];
|
|
131
|
+
private async createChannel(
|
|
132
|
+
uploaderPeerID: string,
|
|
133
|
+
sharedSlug?: string,
|
|
134
|
+
): Promise<void> {
|
|
135
|
+
const payload: { uploaderPeerID: string; sharedSlug?: string } = {
|
|
136
|
+
uploaderPeerID,
|
|
171
137
|
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Create a new channel on the FilePizza server
|
|
176
|
-
*/
|
|
177
|
-
private async createChannel(uploaderPeerID: string, sharedSlug?: string): Promise<void> {
|
|
178
|
-
try {
|
|
179
|
-
const payload: { uploaderPeerID: string; sharedSlug?: string } = { uploaderPeerID };
|
|
180
138
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const response = await fetch(`${this.filePizzaServerUrl}/api/create`, {
|
|
186
|
-
method: 'POST',
|
|
187
|
-
headers: { 'Content-Type': 'application/json' },
|
|
188
|
-
body: JSON.stringify(payload),
|
|
189
|
-
});
|
|
139
|
+
if (sharedSlug) {
|
|
140
|
+
payload.sharedSlug = sharedSlug
|
|
141
|
+
}
|
|
190
142
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
143
|
+
const response = await fetch(`${this.filePizzaServerUrl}/api/create`, {
|
|
144
|
+
method: 'POST',
|
|
145
|
+
headers: { 'Content-Type': 'application/json' },
|
|
146
|
+
body: JSON.stringify(payload),
|
|
147
|
+
})
|
|
194
148
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
console.error('Error creating channel:', error);
|
|
198
|
-
throw error;
|
|
149
|
+
if (!response.ok) {
|
|
150
|
+
throw new Error(`Failed to create channel: ${response.status}`)
|
|
199
151
|
}
|
|
152
|
+
|
|
153
|
+
this.channelInfo = await response.json()
|
|
200
154
|
}
|
|
201
155
|
|
|
202
|
-
/**
|
|
203
|
-
* Renew the channel to keep it alive
|
|
204
|
-
*/
|
|
205
156
|
private async renewChannel(): Promise<void> {
|
|
206
157
|
if (!this.channelInfo || !this.channelInfo.secret) {
|
|
207
|
-
return
|
|
158
|
+
return
|
|
208
159
|
}
|
|
209
160
|
|
|
210
161
|
try {
|
|
@@ -215,63 +166,51 @@ export class FilePizzaUploader extends EventEmitter {
|
|
|
215
166
|
slug: this.channelInfo.shortSlug,
|
|
216
167
|
secret: this.channelInfo.secret,
|
|
217
168
|
}),
|
|
218
|
-
})
|
|
169
|
+
})
|
|
219
170
|
|
|
220
171
|
if (!response.ok) {
|
|
221
|
-
throw new Error(`Failed to renew channel: ${response.status}`)
|
|
172
|
+
throw new Error(`Failed to renew channel: ${response.status}`)
|
|
222
173
|
}
|
|
223
174
|
} catch (error) {
|
|
224
|
-
console.error('Error renewing channel:', error)
|
|
175
|
+
console.error('Error renewing channel:', error)
|
|
225
176
|
}
|
|
226
177
|
}
|
|
227
178
|
|
|
228
|
-
/**
|
|
229
|
-
* Destroy a channel
|
|
230
|
-
*/
|
|
231
179
|
private async destroyChannel(slug: string): Promise<void> {
|
|
232
180
|
try {
|
|
233
181
|
const response = await fetch(`${this.filePizzaServerUrl}/api/destroy`, {
|
|
234
182
|
method: 'POST',
|
|
235
183
|
headers: { 'Content-Type': 'application/json' },
|
|
236
184
|
body: JSON.stringify({ slug }),
|
|
237
|
-
})
|
|
185
|
+
})
|
|
238
186
|
|
|
239
187
|
if (!response.ok) {
|
|
240
|
-
throw new Error(`Failed to destroy channel: ${response.status}`)
|
|
188
|
+
throw new Error(`Failed to destroy channel: ${response.status}`)
|
|
241
189
|
}
|
|
242
190
|
} catch (error) {
|
|
243
|
-
console.error('Error destroying channel:', error)
|
|
191
|
+
console.error('Error destroying channel:', error)
|
|
244
192
|
}
|
|
245
193
|
}
|
|
246
194
|
|
|
247
|
-
/**
|
|
248
|
-
* Start channel renewal
|
|
249
|
-
*/
|
|
250
195
|
private startChannelRenewal(): void {
|
|
251
196
|
if (!this.channelInfo || !this.channelInfo.secret) {
|
|
252
|
-
return
|
|
197
|
+
return
|
|
253
198
|
}
|
|
254
199
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
200
|
+
this.renewalTimer = setInterval(
|
|
201
|
+
() => {
|
|
202
|
+
this.renewChannel()
|
|
203
|
+
},
|
|
204
|
+
30 * 60 * 1000,
|
|
205
|
+
)
|
|
261
206
|
}
|
|
262
207
|
|
|
263
|
-
/**
|
|
264
|
-
* Handle new connection
|
|
265
|
-
*/
|
|
266
208
|
private handleConnection(conn: DataConnection): void {
|
|
267
|
-
// Ignore connections for reporting (handled separately)
|
|
268
209
|
if (conn.metadata?.type === 'report') {
|
|
269
|
-
this.emit('report', conn.peer)
|
|
270
|
-
return
|
|
210
|
+
this.emit('report', conn.peer)
|
|
211
|
+
return
|
|
271
212
|
}
|
|
272
213
|
|
|
273
|
-
console.log(`[FilePizzaUploader] New connection from ${conn.peer}`);
|
|
274
|
-
|
|
275
214
|
const connectionContext = {
|
|
276
215
|
status: ConnectionStatus.Pending,
|
|
277
216
|
dataConnection: conn,
|
|
@@ -281,105 +220,81 @@ export class FilePizzaUploader extends EventEmitter {
|
|
|
281
220
|
bytesTransferred: 0,
|
|
282
221
|
totalBytes: this.getTotalBytes(),
|
|
283
222
|
currentFileProgress: 0,
|
|
284
|
-
|
|
223
|
+
acknowledgedBytes: 0,
|
|
224
|
+
}
|
|
285
225
|
|
|
286
|
-
this.connections.set(conn.peer, connectionContext)
|
|
226
|
+
this.connections.set(conn.peer, connectionContext)
|
|
287
227
|
|
|
288
|
-
|
|
289
|
-
conn.on('
|
|
290
|
-
conn.on('
|
|
291
|
-
conn.on('error', (error) => this.handleError(conn, error));
|
|
228
|
+
conn.on('data', (data) => this.handleData(conn, data))
|
|
229
|
+
conn.on('close', () => this.handleClose(conn))
|
|
230
|
+
conn.on('error', (error) => this.handleError(conn, error))
|
|
292
231
|
|
|
293
|
-
|
|
294
|
-
this.emit('connection', this.getConnectionInfo(conn.peer));
|
|
232
|
+
this.emit('connection', this.getConnectionInfo(conn.peer))
|
|
295
233
|
}
|
|
296
234
|
|
|
297
|
-
/**
|
|
298
|
-
* Handle data messages from connection
|
|
299
|
-
*/
|
|
300
235
|
private handleData(conn: DataConnection, data: unknown): void {
|
|
301
|
-
const context = this.connections.get(conn.peer)
|
|
302
|
-
if (!context) return
|
|
236
|
+
const context = this.connections.get(conn.peer)
|
|
237
|
+
if (!context) return
|
|
303
238
|
|
|
304
239
|
try {
|
|
305
|
-
|
|
306
|
-
const message = data as any;
|
|
240
|
+
const message = data as any
|
|
307
241
|
|
|
308
242
|
switch (message.type) {
|
|
309
243
|
case MessageType.RequestInfo:
|
|
310
|
-
this.handleRequestInfo(conn, context, message)
|
|
311
|
-
break
|
|
244
|
+
this.handleRequestInfo(conn, context, message)
|
|
245
|
+
break
|
|
312
246
|
|
|
313
247
|
case MessageType.UsePassword:
|
|
314
|
-
this.handleUsePassword(conn, context, message)
|
|
315
|
-
break
|
|
248
|
+
this.handleUsePassword(conn, context, message)
|
|
249
|
+
break
|
|
316
250
|
|
|
317
251
|
case MessageType.Start:
|
|
318
|
-
this.handleStart(conn, context, message)
|
|
319
|
-
break
|
|
252
|
+
this.handleStart(conn, context, message)
|
|
253
|
+
break
|
|
320
254
|
|
|
321
255
|
case MessageType.Pause:
|
|
322
|
-
this.handlePause(conn, context)
|
|
323
|
-
break
|
|
256
|
+
this.handlePause(conn, context)
|
|
257
|
+
break
|
|
324
258
|
|
|
325
|
-
case MessageType.
|
|
326
|
-
this.
|
|
327
|
-
break
|
|
259
|
+
case MessageType.ChunkAck:
|
|
260
|
+
this.handleChunkAck(conn, context, message)
|
|
261
|
+
break
|
|
328
262
|
|
|
329
263
|
case MessageType.Done:
|
|
330
|
-
this.handleDone(conn, context)
|
|
331
|
-
break
|
|
264
|
+
this.handleDone(conn, context)
|
|
265
|
+
break
|
|
332
266
|
}
|
|
333
267
|
} catch (error) {
|
|
334
|
-
console.error('[FilePizzaUploader] Error handling message:', error)
|
|
268
|
+
console.error('[FilePizzaUploader] Error handling message:', error)
|
|
335
269
|
conn.send({
|
|
336
270
|
type: MessageType.Error,
|
|
337
271
|
error: 'Failed to process message',
|
|
338
|
-
})
|
|
272
|
+
})
|
|
339
273
|
}
|
|
340
274
|
}
|
|
341
275
|
|
|
342
|
-
/**
|
|
343
|
-
* Handle connection close
|
|
344
|
-
*/
|
|
345
276
|
private handleClose(conn: DataConnection): void {
|
|
346
|
-
const context = this.connections.get(conn.peer)
|
|
347
|
-
if (!context) return
|
|
277
|
+
const context = this.connections.get(conn.peer)
|
|
278
|
+
if (!context) return
|
|
348
279
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
// Emit connection closed event
|
|
353
|
-
this.emit('disconnection', conn.peer);
|
|
354
|
-
|
|
355
|
-
// Remove connection
|
|
356
|
-
this.connections.delete(conn.peer);
|
|
280
|
+
context.status = ConnectionStatus.Closed
|
|
281
|
+
this.emit('disconnection', conn.peer)
|
|
282
|
+
this.connections.delete(conn.peer)
|
|
357
283
|
}
|
|
358
284
|
|
|
359
|
-
/**
|
|
360
|
-
* Handle connection error
|
|
361
|
-
*/
|
|
362
285
|
private handleError(conn: DataConnection, error: Error): void {
|
|
363
|
-
const context = this.connections.get(conn.peer)
|
|
364
|
-
if (!context) return
|
|
365
|
-
|
|
366
|
-
// Update connection status
|
|
367
|
-
context.status = ConnectionStatus.Error;
|
|
286
|
+
const context = this.connections.get(conn.peer)
|
|
287
|
+
if (!context) return
|
|
368
288
|
|
|
369
|
-
|
|
370
|
-
this.emit('error', { connectionId: conn.peer, error })
|
|
289
|
+
context.status = ConnectionStatus.Error
|
|
290
|
+
this.emit('error', { connectionId: conn.peer, error })
|
|
371
291
|
|
|
372
|
-
// Close connection
|
|
373
292
|
if (conn.open) {
|
|
374
|
-
conn.close()
|
|
293
|
+
conn.close()
|
|
375
294
|
}
|
|
376
295
|
}
|
|
377
296
|
|
|
378
|
-
/**
|
|
379
|
-
* Handle RequestInfo message
|
|
380
|
-
*/
|
|
381
297
|
private handleRequestInfo(conn: DataConnection, context: any, message: any): void {
|
|
382
|
-
// Store browser info in connection metadata
|
|
383
298
|
this.connectionInfoMap.set(conn.connectionId, {
|
|
384
299
|
browserName: message.browserName,
|
|
385
300
|
browserVersion: message.browserVersion,
|
|
@@ -387,273 +302,204 @@ export class FilePizzaUploader extends EventEmitter {
|
|
|
387
302
|
osVersion: message.osVersion,
|
|
388
303
|
mobileVendor: message.mobileVendor,
|
|
389
304
|
mobileModel: message.mobileModel,
|
|
390
|
-
})
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
const connectionInfo = {
|
|
308
|
+
browserName: message.browserName,
|
|
309
|
+
browserVersion: message.browserVersion,
|
|
310
|
+
osName: message.osName,
|
|
311
|
+
osVersion: message.osVersion,
|
|
312
|
+
mobileVendor: message.mobileVendor,
|
|
313
|
+
mobileModel: message.mobileModel,
|
|
314
|
+
}
|
|
391
315
|
|
|
392
|
-
// Check if password is required
|
|
393
316
|
if (this.password) {
|
|
394
317
|
conn.send({
|
|
395
318
|
type: MessageType.PasswordRequired,
|
|
396
|
-
})
|
|
319
|
+
})
|
|
397
320
|
|
|
398
|
-
context.status = ConnectionStatus.Authenticating
|
|
399
|
-
|
|
400
|
-
// Send file info
|
|
401
|
-
conn.send({
|
|
402
|
-
type: MessageType.Info,
|
|
403
|
-
files: context.filesInfo,
|
|
404
|
-
});
|
|
321
|
+
context.status = ConnectionStatus.Authenticating
|
|
322
|
+
Object.assign(context, connectionInfo)
|
|
405
323
|
|
|
406
|
-
|
|
324
|
+
this.emit('connectionUpdate', this.getConnectionInfo(conn.peer))
|
|
325
|
+
return
|
|
407
326
|
}
|
|
408
327
|
|
|
409
|
-
|
|
410
|
-
|
|
328
|
+
conn.send({
|
|
329
|
+
type: MessageType.Info,
|
|
330
|
+
files: context.filesInfo,
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
context.status = ConnectionStatus.Ready
|
|
334
|
+
Object.assign(context, connectionInfo)
|
|
335
|
+
|
|
336
|
+
this.emit('connectionUpdate', this.getConnectionInfo(conn.peer))
|
|
411
337
|
}
|
|
412
338
|
|
|
413
|
-
/**
|
|
414
|
-
* Handle UsePassword message
|
|
415
|
-
*/
|
|
416
339
|
private handleUsePassword(conn: DataConnection, context: any, message: any): void {
|
|
417
|
-
// Check password
|
|
418
340
|
if (message.password === this.password) {
|
|
419
|
-
// Password correct, send file info
|
|
420
341
|
conn.send({
|
|
421
342
|
type: MessageType.Info,
|
|
422
343
|
files: context.filesInfo,
|
|
423
|
-
})
|
|
344
|
+
})
|
|
424
345
|
|
|
425
|
-
context.status = ConnectionStatus.Ready
|
|
346
|
+
context.status = ConnectionStatus.Ready
|
|
426
347
|
} else {
|
|
427
|
-
// Password incorrect
|
|
428
348
|
conn.send({
|
|
429
349
|
type: MessageType.PasswordRequired,
|
|
430
|
-
errorMessage: '
|
|
431
|
-
})
|
|
350
|
+
errorMessage: 'Invalid password',
|
|
351
|
+
})
|
|
432
352
|
|
|
433
|
-
context.status = ConnectionStatus.InvalidPassword
|
|
353
|
+
context.status = ConnectionStatus.InvalidPassword
|
|
434
354
|
}
|
|
435
355
|
|
|
436
|
-
|
|
437
|
-
this.emit('connectionUpdate', this.getConnectionInfo(conn.peer));
|
|
356
|
+
this.emit('connectionUpdate', this.getConnectionInfo(conn.peer))
|
|
438
357
|
}
|
|
439
358
|
|
|
440
|
-
/**
|
|
441
|
-
* Handle Start message
|
|
442
|
-
*/
|
|
443
359
|
private handleStart(conn: DataConnection, context: any, message: any): void {
|
|
444
|
-
|
|
445
|
-
const
|
|
446
|
-
const offset = message.offset;
|
|
360
|
+
const fileName = message.fileName
|
|
361
|
+
const offset = message.offset
|
|
447
362
|
|
|
448
|
-
const file = this.findFile(fileName)
|
|
449
|
-
if (!file) {
|
|
363
|
+
const file = this.findFile(fileName)
|
|
364
|
+
if (!file || offset > file.size) {
|
|
450
365
|
conn.send({
|
|
451
366
|
type: MessageType.Error,
|
|
452
|
-
error: `
|
|
453
|
-
})
|
|
454
|
-
return
|
|
367
|
+
error: `Invalid file or offset: ${fileName}`,
|
|
368
|
+
})
|
|
369
|
+
return
|
|
455
370
|
}
|
|
456
371
|
|
|
457
|
-
|
|
458
|
-
context.
|
|
459
|
-
context.
|
|
460
|
-
context.
|
|
461
|
-
|
|
462
|
-
// Emit status update
|
|
463
|
-
this.emit('connectionUpdate', this.getConnectionInfo(conn.peer));
|
|
372
|
+
context.status = ConnectionStatus.Uploading
|
|
373
|
+
context.uploadingFileName = fileName
|
|
374
|
+
context.uploadingOffset = offset
|
|
375
|
+
context.acknowledgedBytes = 0
|
|
376
|
+
context.currentFileProgress = 0
|
|
464
377
|
|
|
465
|
-
|
|
466
|
-
this.sendFileChunks(conn, context, file, offset)
|
|
378
|
+
this.emit('connectionUpdate', this.getConnectionInfo(conn.peer))
|
|
379
|
+
this.sendFileChunks(conn, context, file, offset)
|
|
467
380
|
}
|
|
468
381
|
|
|
469
|
-
/**
|
|
470
|
-
* Handle Pause message
|
|
471
|
-
*/
|
|
472
382
|
private handlePause(conn: DataConnection, context: any): void {
|
|
473
|
-
context.status = ConnectionStatus.Paused
|
|
474
|
-
this.emit('connectionUpdate', this.getConnectionInfo(conn.peer))
|
|
383
|
+
context.status = ConnectionStatus.Paused
|
|
384
|
+
this.emit('connectionUpdate', this.getConnectionInfo(conn.peer))
|
|
475
385
|
}
|
|
476
386
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
private handleResume(conn: DataConnection, context: any, message: any): void {
|
|
481
|
-
const fileName = message.fileName;
|
|
482
|
-
const offset = message.offset;
|
|
387
|
+
private handleChunkAck(conn: DataConnection, context: any, message: any): void {
|
|
388
|
+
context.acknowledgedBytes =
|
|
389
|
+
(context.acknowledgedBytes || 0) + message.bytesReceived
|
|
483
390
|
|
|
484
|
-
const file = this.findFile(fileName)
|
|
485
|
-
if (
|
|
486
|
-
|
|
487
|
-
type: MessageType.Error,
|
|
488
|
-
error: `File not found: ${fileName}`,
|
|
489
|
-
});
|
|
490
|
-
return;
|
|
391
|
+
const file = this.findFile(message.fileName)
|
|
392
|
+
if (file) {
|
|
393
|
+
context.currentFileProgress = context.acknowledgedBytes / file.size
|
|
491
394
|
}
|
|
492
395
|
|
|
493
|
-
|
|
494
|
-
context.uploadingFileName = fileName;
|
|
495
|
-
context.uploadingOffset = offset;
|
|
496
|
-
|
|
497
|
-
this.emit('connectionUpdate', this.getConnectionInfo(conn.peer));
|
|
498
|
-
|
|
499
|
-
this.sendFileChunks(conn, context, file, offset);
|
|
396
|
+
this.emit('progress', this.getProgressInfo(conn.peer))
|
|
500
397
|
}
|
|
501
398
|
|
|
502
|
-
/**
|
|
503
|
-
* Handle Done message
|
|
504
|
-
*/
|
|
505
399
|
private handleDone(conn: DataConnection, context: any): void {
|
|
506
|
-
context.status = ConnectionStatus.Done
|
|
507
|
-
this.emit('connectionUpdate', this.getConnectionInfo(conn.peer))
|
|
508
|
-
conn.close()
|
|
400
|
+
context.status = ConnectionStatus.Done
|
|
401
|
+
this.emit('connectionUpdate', this.getConnectionInfo(conn.peer))
|
|
402
|
+
conn.close()
|
|
509
403
|
}
|
|
510
404
|
|
|
511
|
-
/**
|
|
512
|
-
* Send file chunks to the downloader
|
|
513
|
-
*/
|
|
514
405
|
private sendFileChunks(
|
|
515
406
|
conn: DataConnection,
|
|
516
407
|
context: any,
|
|
517
408
|
file: File,
|
|
518
|
-
startOffset: number
|
|
409
|
+
startOffset: number,
|
|
519
410
|
): void {
|
|
520
|
-
let offset = startOffset
|
|
521
|
-
const
|
|
411
|
+
let offset = startOffset
|
|
412
|
+
const chunkSize = 256 * 1024
|
|
522
413
|
|
|
523
414
|
const sendNextChunk = () => {
|
|
524
|
-
// Check if connection is still open and in uploading state
|
|
525
415
|
if (!conn.open || context.status !== ConnectionStatus.Uploading) {
|
|
526
|
-
return
|
|
416
|
+
return
|
|
527
417
|
}
|
|
528
418
|
|
|
529
|
-
const end = Math.min(file.size, offset +
|
|
530
|
-
const
|
|
531
|
-
const
|
|
419
|
+
const end = Math.min(file.size, offset + chunkSize)
|
|
420
|
+
const final = end >= file.size
|
|
421
|
+
const sentBytes = end - offset
|
|
532
422
|
|
|
533
|
-
// Create chunk
|
|
534
|
-
const chunk = file.slice(offset, end);
|
|
535
|
-
|
|
536
|
-
// Send chunk
|
|
537
423
|
conn.send({
|
|
538
424
|
type: MessageType.Chunk,
|
|
539
425
|
fileName: file.name,
|
|
540
426
|
offset,
|
|
541
|
-
bytes:
|
|
427
|
+
bytes: file.slice(offset, end),
|
|
542
428
|
final,
|
|
543
|
-
})
|
|
544
|
-
|
|
545
|
-
// Update progress
|
|
546
|
-
offset = end;
|
|
547
|
-
context.uploadingOffset = offset;
|
|
548
|
-
context.currentFileProgress = offset / file.size;
|
|
549
|
-
context.bytesTransferred += chunkSize;
|
|
429
|
+
})
|
|
550
430
|
|
|
551
|
-
|
|
552
|
-
|
|
431
|
+
offset = end
|
|
432
|
+
context.uploadingOffset = offset
|
|
433
|
+
context.bytesTransferred += sentBytes
|
|
553
434
|
|
|
554
|
-
// If this was the final chunk
|
|
555
435
|
if (final) {
|
|
556
436
|
if (context.fileIndex < context.totalFiles - 1) {
|
|
557
|
-
|
|
558
|
-
context.
|
|
559
|
-
context.
|
|
560
|
-
context.status = ConnectionStatus.Ready;
|
|
561
|
-
|
|
562
|
-
// Emit update
|
|
563
|
-
this.emit('connectionUpdate', this.getConnectionInfo(conn.peer));
|
|
437
|
+
context.fileIndex += 1
|
|
438
|
+
context.currentFileProgress = 0
|
|
439
|
+
context.status = ConnectionStatus.Ready
|
|
564
440
|
} else {
|
|
565
|
-
|
|
566
|
-
context.
|
|
567
|
-
context.
|
|
568
|
-
context.status = ConnectionStatus.Done;
|
|
569
|
-
|
|
570
|
-
// Emit update
|
|
571
|
-
this.emit('connectionUpdate', this.getConnectionInfo(conn.peer));
|
|
441
|
+
context.fileIndex = context.totalFiles
|
|
442
|
+
context.currentFileProgress = 1
|
|
443
|
+
context.status = ConnectionStatus.Ready
|
|
572
444
|
}
|
|
445
|
+
|
|
446
|
+
this.emit('connectionUpdate', this.getConnectionInfo(conn.peer))
|
|
573
447
|
} else {
|
|
574
|
-
|
|
575
|
-
setTimeout(sendNextChunk, 0);
|
|
448
|
+
setTimeout(sendNextChunk, 0)
|
|
576
449
|
}
|
|
577
|
-
}
|
|
450
|
+
}
|
|
578
451
|
|
|
579
|
-
|
|
580
|
-
sendNextChunk();
|
|
452
|
+
sendNextChunk()
|
|
581
453
|
}
|
|
582
454
|
|
|
583
|
-
/**
|
|
584
|
-
* Find a file by name
|
|
585
|
-
*/
|
|
586
455
|
private findFile(fileName: string): File | undefined {
|
|
587
|
-
return this.files.find(file => file.name === fileName)
|
|
456
|
+
return this.files.find((file) => file.name === fileName)
|
|
588
457
|
}
|
|
589
458
|
|
|
590
|
-
/**
|
|
591
|
-
* Get file info for all files
|
|
592
|
-
*/
|
|
593
459
|
private getFileInfo(): FileInfo[] {
|
|
594
|
-
return this.files.map(file => ({
|
|
460
|
+
return this.files.map((file) => ({
|
|
595
461
|
fileName: file.name,
|
|
596
462
|
size: file.size,
|
|
597
463
|
type: file.type,
|
|
598
|
-
}))
|
|
464
|
+
}))
|
|
599
465
|
}
|
|
600
466
|
|
|
601
|
-
/**
|
|
602
|
-
* Get connection info for a specific connection
|
|
603
|
-
*/
|
|
604
467
|
private getConnectionInfo(peerId: string): ConnectionInfo {
|
|
605
|
-
const context = this.connections.get(peerId)
|
|
468
|
+
const context = this.connections.get(peerId)
|
|
606
469
|
if (!context) {
|
|
607
|
-
throw new Error(`Connection not found: ${peerId}`)
|
|
470
|
+
throw new Error(`Connection not found: ${peerId}`)
|
|
608
471
|
}
|
|
609
472
|
|
|
610
473
|
return {
|
|
611
474
|
id: peerId,
|
|
612
475
|
status: context.status,
|
|
613
|
-
browserName: context.
|
|
614
|
-
browserVersion: context.
|
|
615
|
-
osName: context.
|
|
616
|
-
osVersion: context.
|
|
617
|
-
mobileVendor: context.
|
|
618
|
-
mobileModel: context.
|
|
619
|
-
}
|
|
476
|
+
browserName: context.browserName,
|
|
477
|
+
browserVersion: context.browserVersion,
|
|
478
|
+
osName: context.osName,
|
|
479
|
+
osVersion: context.osVersion,
|
|
480
|
+
mobileVendor: context.mobileVendor,
|
|
481
|
+
mobileModel: context.mobileModel,
|
|
482
|
+
}
|
|
620
483
|
}
|
|
621
484
|
|
|
622
|
-
/**
|
|
623
|
-
* Get connection info for all connections
|
|
624
|
-
*/
|
|
625
485
|
public getConnectionInfoAll(): ConnectionInfo[] {
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
const connectionInfo = this.getConnectionInfo(peerId);
|
|
630
|
-
connectionInfos.push(connectionInfo);
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
return connectionInfos;
|
|
486
|
+
return Array.from(this.connections.keys()).map((peerId) =>
|
|
487
|
+
this.getConnectionInfo(peerId),
|
|
488
|
+
)
|
|
634
489
|
}
|
|
635
490
|
|
|
636
|
-
/**
|
|
637
|
-
* Remove connection info
|
|
638
|
-
*/
|
|
639
491
|
public removeConnectionInfo(connectionId: string): void {
|
|
640
|
-
this.connectionInfoMap.delete(connectionId)
|
|
492
|
+
this.connectionInfoMap.delete(connectionId)
|
|
641
493
|
}
|
|
642
494
|
|
|
643
|
-
/**
|
|
644
|
-
* Get total bytes for all files
|
|
645
|
-
*/
|
|
646
495
|
private getTotalBytes(): number {
|
|
647
|
-
return this.files.reduce((total, file) => total + file.size, 0)
|
|
496
|
+
return this.files.reduce((total, file) => total + file.size, 0)
|
|
648
497
|
}
|
|
649
498
|
|
|
650
|
-
/**
|
|
651
|
-
* Get progress info for a specific connection
|
|
652
|
-
*/
|
|
653
499
|
private getProgressInfo(peerId: string): ProgressInfo {
|
|
654
|
-
const context = this.connections.get(peerId)
|
|
500
|
+
const context = this.connections.get(peerId)
|
|
655
501
|
if (!context) {
|
|
656
|
-
throw new Error(`Connection not found: ${peerId}`)
|
|
502
|
+
throw new Error(`Connection not found: ${peerId}`)
|
|
657
503
|
}
|
|
658
504
|
|
|
659
505
|
return {
|
|
@@ -661,9 +507,9 @@ export class FilePizzaUploader extends EventEmitter {
|
|
|
661
507
|
fileName: context.uploadingFileName || '',
|
|
662
508
|
totalFiles: context.totalFiles,
|
|
663
509
|
currentFileProgress: context.currentFileProgress,
|
|
664
|
-
overallProgress: context.bytesTransferred / context.totalBytes,
|
|
510
|
+
overallProgress: context.bytesTransferred / (context.totalBytes || 1),
|
|
665
511
|
bytesTransferred: context.bytesTransferred,
|
|
666
512
|
totalBytes: context.totalBytes,
|
|
667
|
-
}
|
|
513
|
+
}
|
|
668
514
|
}
|
|
669
515
|
}
|