uncloud-p2p 1.0.2 → 1.0.4
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 +3 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.js +95 -2
- package/dist/uncloudproto.d.ts +1 -0
- package/dist/uncloudproto.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export interface IceCandidate {
|
|
|
7
7
|
export interface IceExchangePayload {
|
|
8
8
|
sdp: string;
|
|
9
9
|
candidates: IceCandidate[];
|
|
10
|
+
token: string;
|
|
10
11
|
}
|
|
11
12
|
export type ConnectionState = "idle" | "gathering" | "signaling" | "connecting" | "connected" | "disconnected" | "error";
|
|
12
13
|
interface P2POptions {
|
|
@@ -24,6 +25,7 @@ export declare class UncloudP2P {
|
|
|
24
25
|
private options;
|
|
25
26
|
private pendingRequests;
|
|
26
27
|
private pendingDownloads;
|
|
28
|
+
private token;
|
|
27
29
|
constructor(options: P2POptions);
|
|
28
30
|
private log;
|
|
29
31
|
/**
|
|
@@ -47,6 +49,12 @@ export declare class UncloudP2P {
|
|
|
47
49
|
private updateState;
|
|
48
50
|
private handleError;
|
|
49
51
|
disconnect(): void;
|
|
52
|
+
/**
|
|
53
|
+
* Uploads a file, string, or binary buffer to a specific target path on the remote node.
|
|
54
|
+
* @param targetPath The destination path on the node (e.g., "test/output.txt")
|
|
55
|
+
* @param content The file content as a string, Blob, File, or ArrayBuffer
|
|
56
|
+
*/
|
|
57
|
+
uploadFile(targetPath: string, content: string | Blob | File | ArrayBuffer): Promise<void>;
|
|
50
58
|
}
|
|
51
59
|
export * from "./uncloudproto";
|
|
52
60
|
export * from "./files";
|
package/dist/index.js
CHANGED
|
@@ -23,6 +23,7 @@ class UncloudP2P {
|
|
|
23
23
|
this.localCandidates = [];
|
|
24
24
|
this.pendingRequests = new Map();
|
|
25
25
|
this.pendingDownloads = new Map();
|
|
26
|
+
this.token = "";
|
|
26
27
|
this.options = {
|
|
27
28
|
baseUrl: "https://uncloud.mpsoftware.io",
|
|
28
29
|
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
|
|
@@ -70,6 +71,11 @@ class UncloudP2P {
|
|
|
70
71
|
};
|
|
71
72
|
const offer = await this.pc.createOffer();
|
|
72
73
|
await this.pc.setLocalDescription(offer);
|
|
74
|
+
await this.waitForOpen();
|
|
75
|
+
// finally, set our token so the node knows what grants we were given from the p2p public link
|
|
76
|
+
const resp = await this.sendCommand(uncloudproto_1.Actions.SET_TOKEN, {
|
|
77
|
+
token: this.token,
|
|
78
|
+
});
|
|
73
79
|
}
|
|
74
80
|
catch (err) {
|
|
75
81
|
this.handleError(err);
|
|
@@ -85,6 +91,7 @@ class UncloudP2P {
|
|
|
85
91
|
const payload = {
|
|
86
92
|
sdp: this.pc.localDescription.sdp,
|
|
87
93
|
candidates: this.localCandidates,
|
|
94
|
+
token: "", // we get this back from the node
|
|
88
95
|
};
|
|
89
96
|
try {
|
|
90
97
|
const endpoint = `${this.options.baseUrl}/api/public/node/${this.options.nodeId}/exchangeCandidates`;
|
|
@@ -96,6 +103,7 @@ class UncloudP2P {
|
|
|
96
103
|
if (!response.ok)
|
|
97
104
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
98
105
|
const nodeResponse = await response.json();
|
|
106
|
+
this.token = nodeResponse.token;
|
|
99
107
|
await this.pc.setRemoteDescription(new RTCSessionDescription({ type: "answer", sdp: nodeResponse.sdp }));
|
|
100
108
|
for (const cand of nodeResponse.candidates) {
|
|
101
109
|
await this.pc.addIceCandidate(cand);
|
|
@@ -141,7 +149,16 @@ class UncloudP2P {
|
|
|
141
149
|
pending.reject(response.error);
|
|
142
150
|
}
|
|
143
151
|
else {
|
|
144
|
-
|
|
152
|
+
// check if body is 1 field and is an error field
|
|
153
|
+
if (response.body &&
|
|
154
|
+
typeof response.body === "object" &&
|
|
155
|
+
"error" in response.body &&
|
|
156
|
+
Object.keys(response.body).length === 1) {
|
|
157
|
+
pending.reject(response.body.error);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
pending.resolve(response.body);
|
|
161
|
+
}
|
|
145
162
|
}
|
|
146
163
|
return;
|
|
147
164
|
}
|
|
@@ -215,7 +232,7 @@ class UncloudP2P {
|
|
|
215
232
|
this.pendingDownloads.set(fileName, { resolve, reject });
|
|
216
233
|
try {
|
|
217
234
|
// 2. Trigger the request
|
|
218
|
-
await this.sendCommand(uncloudproto_1.Actions.NODE_TO_CLIENT_FILE_TRANSFER, {
|
|
235
|
+
const resp = await this.sendCommand(uncloudproto_1.Actions.NODE_TO_CLIENT_FILE_TRANSFER, {
|
|
219
236
|
name: fileName,
|
|
220
237
|
});
|
|
221
238
|
}
|
|
@@ -267,6 +284,82 @@ class UncloudP2P {
|
|
|
267
284
|
this.localCandidates = [];
|
|
268
285
|
this.updateState("idle");
|
|
269
286
|
}
|
|
287
|
+
/**
|
|
288
|
+
* Uploads a file, string, or binary buffer to a specific target path on the remote node.
|
|
289
|
+
* @param targetPath The destination path on the node (e.g., "test/output.txt")
|
|
290
|
+
* @param content The file content as a string, Blob, File, or ArrayBuffer
|
|
291
|
+
*/
|
|
292
|
+
async uploadFile(targetPath, content) {
|
|
293
|
+
if (!this.dc || this.dc.readyState !== "open" || !this.pc) {
|
|
294
|
+
throw new Error("P2P control channel or peer connection is not established");
|
|
295
|
+
}
|
|
296
|
+
// 1. Normalize data input types into a standardized buffer payload
|
|
297
|
+
let rawDataBuffer;
|
|
298
|
+
if (typeof content === "string") {
|
|
299
|
+
rawDataBuffer = new TextEncoder().encode(content).buffer;
|
|
300
|
+
}
|
|
301
|
+
else if (content instanceof ArrayBuffer) {
|
|
302
|
+
rawDataBuffer = content;
|
|
303
|
+
}
|
|
304
|
+
else if (content instanceof Blob) {
|
|
305
|
+
rawDataBuffer = await content.arrayBuffer();
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
throw new Error("Unsupported upload data payload type configuration.");
|
|
309
|
+
}
|
|
310
|
+
const fileSize = rawDataBuffer.byteLength;
|
|
311
|
+
this.log(`Initiating upload handshake sequence for target destination: ${targetPath} (${fileSize} bytes)`);
|
|
312
|
+
// 2. Alert the control stream channel about our upload metadata context intent
|
|
313
|
+
// Note: We inject the full structured "targetPath" into the metadata "name" field
|
|
314
|
+
const controlHandshake = (0, uncloudproto_1.newMessage)(uncloudproto_1.Actions.CLIENT_TO_NODE_FILE_TRANSFER, {
|
|
315
|
+
name: targetPath,
|
|
316
|
+
size: fileSize,
|
|
317
|
+
});
|
|
318
|
+
this.dc.send(controlHandshake.toJSON());
|
|
319
|
+
// 3. Construct a temporary WebRTC out-of-band data channel pipe for the file stream blocks
|
|
320
|
+
const uploadStreamChannel = this.pc.createDataChannel(uncloudproto_1.StreamNames.CLIENT_TO_NODE_FILE_TRANSFER, { ordered: true });
|
|
321
|
+
// 4. Return an execution promise wrapping the chunk fragmentation loop
|
|
322
|
+
return new Promise((resolve, reject) => {
|
|
323
|
+
uploadStreamChannel.onopen = async () => {
|
|
324
|
+
try {
|
|
325
|
+
this.log(`Streaming channel pipe activated. Processing transfer payload...`);
|
|
326
|
+
// Send data channel sync context header
|
|
327
|
+
const binaryChannelHeader = (0, uncloudproto_1.newMessage)(uncloudproto_1.Actions.CLIENT_TO_NODE_FILE_TRANSFER, {
|
|
328
|
+
name: targetPath,
|
|
329
|
+
size: fileSize,
|
|
330
|
+
});
|
|
331
|
+
uploadStreamChannel.send(binaryChannelHeader.toJSON());
|
|
332
|
+
// Stream the file data in standard WebRTC payload fragment sizes (16KB chunks)
|
|
333
|
+
const CHUNK_SIZE = 16384;
|
|
334
|
+
let currentByteOffset = 0;
|
|
335
|
+
while (currentByteOffset < fileSize) {
|
|
336
|
+
// Guard against overflowing the internal WebRTC browser buffer cache allocations
|
|
337
|
+
if (uploadStreamChannel.bufferedAmount > 1024 * 1024) {
|
|
338
|
+
await new Promise((r) => setTimeout(r, 25));
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
const frameEndIndex = Math.min(currentByteOffset + CHUNK_SIZE, fileSize);
|
|
342
|
+
const dataSlice = rawDataBuffer.slice(currentByteOffset, frameEndIndex);
|
|
343
|
+
uploadStreamChannel.send(dataSlice);
|
|
344
|
+
currentByteOffset = frameEndIndex;
|
|
345
|
+
}
|
|
346
|
+
this.log(`Data payload transfer pipeline complete. Closing stream context map.`, "success");
|
|
347
|
+
// Allow internal socket layer flush buffers to breathe before killing context mapping frame references
|
|
348
|
+
setTimeout(() => {
|
|
349
|
+
uploadStreamChannel.close();
|
|
350
|
+
resolve();
|
|
351
|
+
}, 800);
|
|
352
|
+
}
|
|
353
|
+
catch (streamError) {
|
|
354
|
+
uploadStreamChannel.close();
|
|
355
|
+
reject(streamError);
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
uploadStreamChannel.onerror = (channelErr) => {
|
|
359
|
+
reject(new Error(`Data transfer stream errored mid-flight: ${channelErr}`));
|
|
360
|
+
};
|
|
361
|
+
});
|
|
362
|
+
}
|
|
270
363
|
}
|
|
271
364
|
exports.UncloudP2P = UncloudP2P;
|
|
272
365
|
// src/index.ts
|
package/dist/uncloudproto.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ export declare const Actions: {
|
|
|
24
24
|
CLIENT_TO_NODE_FILE_TRANSFER: string;
|
|
25
25
|
NODE_TO_CLIENT_FILE_TRANSFER: string;
|
|
26
26
|
CLIENT_TO_NODE_LIST_FILES: string;
|
|
27
|
+
SET_TOKEN: string;
|
|
27
28
|
};
|
|
28
29
|
export type Action = (typeof Actions)[keyof typeof Actions];
|
|
29
30
|
export type PendingRequest = {
|
package/dist/uncloudproto.js
CHANGED