@heyputer/puter.js 1.0.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/APACHE_LICENSE.txt +201 -0
- package/README.md +88 -0
- package/doc/devlog.md +49 -0
- package/package.json +31 -0
- package/src/bg.png +0 -0
- package/src/bg.webp +0 -0
- package/src/index.js +745 -0
- package/src/lib/APICallLogger.js +110 -0
- package/src/lib/EventListener.js +51 -0
- package/src/lib/RequestError.js +6 -0
- package/src/lib/filesystem/APIFS.js +73 -0
- package/src/lib/filesystem/CacheFS.js +243 -0
- package/src/lib/filesystem/PostMessageFS.js +40 -0
- package/src/lib/filesystem/definitions.js +39 -0
- package/src/lib/path.js +509 -0
- package/src/lib/polyfills/localStorage.js +92 -0
- package/src/lib/polyfills/xhrshim.js +233 -0
- package/src/lib/socket.io/socket.io.esm.min.js +7 -0
- package/src/lib/socket.io/socket.io.esm.min.js.map +1 -0
- package/src/lib/socket.io/socket.io.js +4385 -0
- package/src/lib/socket.io/socket.io.js.map +1 -0
- package/src/lib/socket.io/socket.io.min.js +7 -0
- package/src/lib/socket.io/socket.io.min.js.map +1 -0
- package/src/lib/socket.io/socket.io.msgpack.min.js +7 -0
- package/src/lib/socket.io/socket.io.msgpack.min.js.map +1 -0
- package/src/lib/utils.js +620 -0
- package/src/lib/xdrpc.js +104 -0
- package/src/modules/AI.js +680 -0
- package/src/modules/Apps.js +215 -0
- package/src/modules/Auth.js +171 -0
- package/src/modules/Debug.js +39 -0
- package/src/modules/Drivers.js +278 -0
- package/src/modules/FSItem.js +139 -0
- package/src/modules/FileSystem/index.js +187 -0
- package/src/modules/FileSystem/operations/copy.js +64 -0
- package/src/modules/FileSystem/operations/deleteFSEntry.js +59 -0
- package/src/modules/FileSystem/operations/getReadUrl.js +42 -0
- package/src/modules/FileSystem/operations/mkdir.js +62 -0
- package/src/modules/FileSystem/operations/move.js +75 -0
- package/src/modules/FileSystem/operations/read.js +46 -0
- package/src/modules/FileSystem/operations/readdir.js +102 -0
- package/src/modules/FileSystem/operations/rename.js +58 -0
- package/src/modules/FileSystem/operations/sign.js +103 -0
- package/src/modules/FileSystem/operations/space.js +40 -0
- package/src/modules/FileSystem/operations/stat.js +95 -0
- package/src/modules/FileSystem/operations/symlink.js +55 -0
- package/src/modules/FileSystem/operations/upload.js +440 -0
- package/src/modules/FileSystem/operations/write.js +65 -0
- package/src/modules/FileSystem/utils/getAbsolutePathForApp.js +21 -0
- package/src/modules/Hosting.js +138 -0
- package/src/modules/KV.js +301 -0
- package/src/modules/OS.js +95 -0
- package/src/modules/Perms.js +109 -0
- package/src/modules/PuterDialog.js +481 -0
- package/src/modules/Threads.js +75 -0
- package/src/modules/UI.js +1555 -0
- package/src/modules/Util.js +38 -0
- package/src/modules/Workers.js +120 -0
- package/src/modules/networking/PSocket.js +87 -0
- package/src/modules/networking/PTLS.js +100 -0
- package/src/modules/networking/PWispHandler.js +89 -0
- package/src/modules/networking/parsers.js +157 -0
- package/src/modules/networking/requests.js +282 -0
- package/src/services/APIAccess.js +46 -0
- package/src/services/FSRelay.js +20 -0
- package/src/services/Filesystem.js +122 -0
- package/src/services/NoPuterYet.js +20 -0
- package/src/services/XDIncoming.js +44 -0
- package/test/ai.test.js +214 -0
- package/test/fs.test.js +798 -0
- package/test/index.html +1183 -0
- package/test/kv.test.js +548 -0
- package/test/txt2speech.test.js +178 -0
- package/webpack.config.js +25 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { $SCOPE, CallbackManager, Dehydrator, Hydrator } from "../lib/xdrpc.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The Util module exposes utilities within puter.js itself.
|
|
5
|
+
* These utilities may be used internally by other modules.
|
|
6
|
+
*/
|
|
7
|
+
export default class Util {
|
|
8
|
+
constructor () {
|
|
9
|
+
// This is in `puter.util.rpc` instead of `puter.rpc` because
|
|
10
|
+
// `puter.rpc` is reserved for an app-to-app RPC interface.
|
|
11
|
+
// This is a lower-level RPC interface used to communicate
|
|
12
|
+
// with iframes.
|
|
13
|
+
this.rpc = new UtilRPC();
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
class UtilRPC {
|
|
18
|
+
constructor () {
|
|
19
|
+
this.callbackManager = new CallbackManager();
|
|
20
|
+
this.callbackManager.attach_to_source(globalThis);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
getDehydrator () {
|
|
24
|
+
return new Dehydrator({ callbackManager: this.callbackManager });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
getHydrator ({ target }) {
|
|
28
|
+
return new Hydrator({ target });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
registerCallback (resolve) {
|
|
32
|
+
return this.callbackManager.register_callback(resolve);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
send (target, id, ...args) {
|
|
36
|
+
target.postMessage({ $SCOPE, id, args }, '*');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import getAbsolutePathForApp from "./FileSystem/utils/getAbsolutePathForApp.js";
|
|
2
|
+
import * as utils from '../lib/utils.js';
|
|
3
|
+
|
|
4
|
+
export class WorkersHandler {
|
|
5
|
+
|
|
6
|
+
constructor(authToken) {
|
|
7
|
+
this.authToken = authToken;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async create(workerName, filePath, appName) {
|
|
11
|
+
if (!puter.authToken && puter.env === 'web') {
|
|
12
|
+
try {
|
|
13
|
+
await puter.ui.authenticateWithPuter();
|
|
14
|
+
} catch (e) {
|
|
15
|
+
// if authentication fails, throw an error
|
|
16
|
+
throw 'Authentication failed.';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let appId;
|
|
21
|
+
if (typeof (appName)=== "string") {
|
|
22
|
+
appId = ((await puter.apps.list()).find(el => el.name === appName)).uid;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
workerName = workerName.toLocaleLowerCase(); // just incase
|
|
26
|
+
let currentWorkers = await puter.kv.get("user-workers");
|
|
27
|
+
if (!currentWorkers) {
|
|
28
|
+
currentWorkers = {};
|
|
29
|
+
}
|
|
30
|
+
filePath = getAbsolutePathForApp(filePath);
|
|
31
|
+
|
|
32
|
+
const driverResult = await utils.make_driver_method(['authorization', 'filePath', 'workerName', 'appId'], 'workers', "worker-service", 'create')(puter.authToken, filePath, workerName, appId);;
|
|
33
|
+
|
|
34
|
+
if (!driverResult.success) {
|
|
35
|
+
throw new Error(driverResult?.errors || "Driver failed to execute, do you have the necessary permissions?");
|
|
36
|
+
}
|
|
37
|
+
currentWorkers[workerName] = { filePath, url: driverResult["url"], deployTime: Date.now(), createTime: Date.now() };
|
|
38
|
+
await puter.kv.set("user-workers", currentWorkers);
|
|
39
|
+
|
|
40
|
+
return driverResult;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async exec(...args) {
|
|
44
|
+
if (!puter.authToken && puter.env === 'web') {
|
|
45
|
+
try {
|
|
46
|
+
await puter.ui.authenticateWithPuter();
|
|
47
|
+
} catch (e) {
|
|
48
|
+
// if authentication fails, throw an error
|
|
49
|
+
throw 'Authentication failed.';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const req = new Request(...args);
|
|
54
|
+
if (!req.headers.get("puter-auth")) {
|
|
55
|
+
req.headers.set("puter-auth", puter.authToken);
|
|
56
|
+
}
|
|
57
|
+
return fetch(req);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async list() {
|
|
61
|
+
if (!puter.authToken && puter.env === 'web') {
|
|
62
|
+
try {
|
|
63
|
+
await puter.ui.authenticateWithPuter();
|
|
64
|
+
} catch (e) {
|
|
65
|
+
// if authentication fails, throw an error
|
|
66
|
+
throw 'Authentication failed.';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const driverCall = await utils.make_driver_method([], 'workers', "worker-service", 'getFilePaths')();
|
|
70
|
+
return driverCall;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async get(workerName) {
|
|
74
|
+
if (!puter.authToken && puter.env === 'web') {
|
|
75
|
+
try {
|
|
76
|
+
await puter.ui.authenticateWithPuter();
|
|
77
|
+
} catch (e) {
|
|
78
|
+
// if authentication fails, throw an error
|
|
79
|
+
throw 'Authentication failed.';
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
workerName = workerName.toLocaleLowerCase(); // just incase
|
|
84
|
+
const driverCall = await utils.make_driver_method(['workerName'], 'workers', "worker-service", 'getFilePaths')(workerName);
|
|
85
|
+
return driverCall[0];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async delete(workerName) {
|
|
89
|
+
if (!puter.authToken && puter.env === 'web') {
|
|
90
|
+
try {
|
|
91
|
+
await puter.ui.authenticateWithPuter();
|
|
92
|
+
} catch (e) {
|
|
93
|
+
// if authentication fails, throw an error
|
|
94
|
+
throw 'Authentication failed.';
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
workerName = workerName.toLocaleLowerCase(); // just incase
|
|
99
|
+
// const driverCall = await puter.drivers.call("workers", "worker-service", "destroy", { authorization: puter.authToken, workerName });
|
|
100
|
+
const driverResult = await utils.make_driver_method(['authorization', 'workerName'], 'workers', "worker-service", 'destroy')(puter.authToken, workerName);;
|
|
101
|
+
|
|
102
|
+
if (!driverResult.result) {
|
|
103
|
+
if (!driverResult.result) {
|
|
104
|
+
new Error("Worker doesn't exist");
|
|
105
|
+
}
|
|
106
|
+
throw new Error(driverResult?.errors || "Driver failed to execute, do you have the necessary permissions?");
|
|
107
|
+
} else {
|
|
108
|
+
let currentWorkers = await puter.kv.get("user-workers");
|
|
109
|
+
|
|
110
|
+
if (!currentWorkers) {
|
|
111
|
+
currentWorkers = {};
|
|
112
|
+
}
|
|
113
|
+
delete currentWorkers[workerName];
|
|
114
|
+
|
|
115
|
+
await puter.kv.set("user-workers", currentWorkers);
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import EventListener from "../../lib/EventListener.js";
|
|
2
|
+
import { errors } from "./parsers.js";
|
|
3
|
+
import { PWispHandler } from "./PWispHandler.js";
|
|
4
|
+
const texten = new TextEncoder();
|
|
5
|
+
const requireAuth = false; // for initial launch
|
|
6
|
+
|
|
7
|
+
export let wispInfo = {
|
|
8
|
+
server: "wss://puter.cafe/", // Unused currently
|
|
9
|
+
handler: undefined
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export class PSocket extends EventListener {
|
|
13
|
+
_events = new Map();
|
|
14
|
+
_streamID;
|
|
15
|
+
constructor(host, port) {
|
|
16
|
+
super(["data", "drain", "open", "error", "close", "tlsdata", "tlsopen", "tlsclose"]);
|
|
17
|
+
|
|
18
|
+
(async () => {
|
|
19
|
+
if(!puter.authToken && puter.env === 'web' && requireAuth){
|
|
20
|
+
try{
|
|
21
|
+
await puter.ui.authenticateWithPuter();
|
|
22
|
+
|
|
23
|
+
}catch(e){
|
|
24
|
+
// if authentication fails, throw an error
|
|
25
|
+
throw (e);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (!wispInfo.handler) {
|
|
29
|
+
// first launch -- lets init the socket
|
|
30
|
+
const { token: wispToken, server: wispServer } = (await (await fetch(puter.APIOrigin + '/wisp/relay-token/create', {
|
|
31
|
+
method: 'POST',
|
|
32
|
+
headers: {
|
|
33
|
+
Authorization: puter.authToken ? `Bearer ${puter.authToken}`:'',
|
|
34
|
+
'Content-Type': 'application/json',
|
|
35
|
+
},
|
|
36
|
+
body: JSON.stringify({}),
|
|
37
|
+
})).json());
|
|
38
|
+
|
|
39
|
+
wispInfo.handler = new PWispHandler(wispServer, wispToken);
|
|
40
|
+
// Wait for websocket to fully open
|
|
41
|
+
await new Promise((res, req) => {
|
|
42
|
+
wispInfo.handler.onReady = res;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
const callbacks = {
|
|
49
|
+
dataCallBack: (data) => {
|
|
50
|
+
this.emit("data", data);
|
|
51
|
+
},
|
|
52
|
+
closeCallBack: (reason) => {
|
|
53
|
+
if (reason !== 0x02) {
|
|
54
|
+
this.emit("error", new Error(errors[reason]));
|
|
55
|
+
this.emit("close", true);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
this.emit("close", false);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
this._streamID = wispInfo.handler.register(host, port, callbacks);
|
|
63
|
+
setTimeout(() => {this.emit("open", undefined)}, 0);
|
|
64
|
+
|
|
65
|
+
})();
|
|
66
|
+
}
|
|
67
|
+
addListener(...args) {
|
|
68
|
+
this.on(...args);
|
|
69
|
+
}
|
|
70
|
+
write(data, callback) {
|
|
71
|
+
if (data.buffer) { // TypedArray
|
|
72
|
+
wispInfo.handler.write(this._streamID, data);
|
|
73
|
+
if (callback) callback();
|
|
74
|
+
} else if (data.resize) { // ArrayBuffer
|
|
75
|
+
data.write(this._streamID, new Uint8Array(data));
|
|
76
|
+
if (callback) callback();
|
|
77
|
+
} else if (typeof(data) === "string") {
|
|
78
|
+
wispInfo.handler.write(this._streamID, texten.encode(data))
|
|
79
|
+
if (callback) callback();
|
|
80
|
+
} else {
|
|
81
|
+
throw new Error("Invalid data type (not TypedArray, ArrayBuffer or String!!)");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
close() {
|
|
85
|
+
wispInfo.handler.close(this._streamID);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file uses https://github.com/MercuryWorkshop/rustls-wasm authored by GitHub:@r58Playz under the MIT License
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { PSocket } from "./PSocket.js";
|
|
6
|
+
|
|
7
|
+
let rustls = undefined;
|
|
8
|
+
|
|
9
|
+
export class PTLSSocket extends PSocket {
|
|
10
|
+
constructor(...args) {
|
|
11
|
+
super(...args);
|
|
12
|
+
super.on("open", (async() => {
|
|
13
|
+
if (!rustls) {
|
|
14
|
+
rustls = (await import( /* webpackIgnore: true */ "https://puter-net.b-cdn.net/rustls.js"))
|
|
15
|
+
await rustls.default("https://puter-net.b-cdn.net/rustls.wasm")
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let cancelled = false;
|
|
19
|
+
const readable = new ReadableStream({
|
|
20
|
+
/**
|
|
21
|
+
*
|
|
22
|
+
* @param {ReadableStreamDefaultController} controller
|
|
23
|
+
*/
|
|
24
|
+
start: (controller) => {
|
|
25
|
+
super.on("data", (data) => {
|
|
26
|
+
controller.enqueue(data.buffer)
|
|
27
|
+
})
|
|
28
|
+
super.on("close", () => {
|
|
29
|
+
if (!cancelled)
|
|
30
|
+
controller.close()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
},
|
|
34
|
+
pull: (controller) => {
|
|
35
|
+
|
|
36
|
+
},
|
|
37
|
+
cancel: () => {
|
|
38
|
+
cancelled = true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
const writable = new WritableStream({
|
|
44
|
+
write: (chunk) => { super.write(chunk); },
|
|
45
|
+
abort: () => { super.close(); },
|
|
46
|
+
close: () => { super.close(); },
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
let read, write;
|
|
50
|
+
try {
|
|
51
|
+
const TLSConnnection = await rustls.connect_tls(readable, writable, args[0])
|
|
52
|
+
read = TLSConnnection.read;
|
|
53
|
+
write = TLSConnnection.write;
|
|
54
|
+
} catch (e) {
|
|
55
|
+
this.emit("error", new Error("TLS Handshake failed: " + e));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
this.writer = write.getWriter();
|
|
61
|
+
// writer.write("GET / HTTP/1.1\r\nHost: google.com\r\n\r\n");
|
|
62
|
+
let reader = read.getReader();
|
|
63
|
+
let done = false;
|
|
64
|
+
this.emit("tlsopen", undefined);
|
|
65
|
+
try {
|
|
66
|
+
while (!done) {
|
|
67
|
+
const {done: readerDone, value} = await reader.read();
|
|
68
|
+
done = readerDone;
|
|
69
|
+
if (!done) {
|
|
70
|
+
this.emit("tlsdata", value);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
this.emit("tlsclose", false);
|
|
74
|
+
} catch (e) {
|
|
75
|
+
this.emit("error", e)
|
|
76
|
+
this.emit("tlsclose", true);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
}));
|
|
80
|
+
}
|
|
81
|
+
on(event, callback) {
|
|
82
|
+
if (event === "data" || event === "open" || event === "close") {
|
|
83
|
+
return super.on("tls" + event, callback)
|
|
84
|
+
} else {
|
|
85
|
+
return super.on(event, callback);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
write(data, callback) {
|
|
89
|
+
if (data.buffer) { // TypedArray
|
|
90
|
+
this.writer.write(data.slice(0).buffer).then(callback);
|
|
91
|
+
} else if (data.resize) { // ArrayBuffer
|
|
92
|
+
this.writer.write(data).then(callback);
|
|
93
|
+
} else if (typeof(data) === "string"){
|
|
94
|
+
this.writer.write(data).then(callback);
|
|
95
|
+
} else {
|
|
96
|
+
throw new Error("Invalid data type (not TypedArray, ArrayBuffer or String!!)");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import {CLOSE, CONNECT, DATA, CONTINUE, INFO, TCP, UDP, createWispPacket, parseIncomingPacket, textde} from "./parsers.js"
|
|
2
|
+
|
|
3
|
+
export class PWispHandler {
|
|
4
|
+
_ws;
|
|
5
|
+
_nextStreamID = 1;
|
|
6
|
+
_bufferMax;
|
|
7
|
+
onReady = undefined;
|
|
8
|
+
streamMap = new Map();
|
|
9
|
+
constructor(wispURL, puterAuth) {
|
|
10
|
+
const setup = () => {
|
|
11
|
+
this._ws = new WebSocket(wispURL);
|
|
12
|
+
this._ws.binaryType = "arraybuffer"
|
|
13
|
+
this._ws.onmessage = (event) => {
|
|
14
|
+
const parsed = parseIncomingPacket(new Uint8Array(event.data));
|
|
15
|
+
switch (parsed.packetType) {
|
|
16
|
+
case DATA:
|
|
17
|
+
this.streamMap.get(parsed.streamID).dataCallBack(parsed.payload.slice(0)) // return a copy for the user to do as they please
|
|
18
|
+
break;
|
|
19
|
+
case CONTINUE:
|
|
20
|
+
if (parsed.streamID === 0) {
|
|
21
|
+
this._bufferMax = parsed.remainingBuffer;
|
|
22
|
+
this._ws.onclose = () => {
|
|
23
|
+
setTimeout(setup(), 1000);
|
|
24
|
+
}
|
|
25
|
+
if (this.onReady) {
|
|
26
|
+
this.onReady();
|
|
27
|
+
}
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
this.streamMap.get(parsed.streamID).buffer = parsed.remainingBuffer;
|
|
31
|
+
this._continue()
|
|
32
|
+
break;
|
|
33
|
+
case CLOSE:
|
|
34
|
+
if (parsed.streamID !== 0)
|
|
35
|
+
this.streamMap.get(parsed.streamID).closeCallBack(parsed.reason);
|
|
36
|
+
break;
|
|
37
|
+
case INFO:
|
|
38
|
+
puterAuth && this._ws.send(createWispPacket({
|
|
39
|
+
packetType: INFO,
|
|
40
|
+
streamID: 0,
|
|
41
|
+
puterAuth
|
|
42
|
+
}))
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
setup();
|
|
48
|
+
}
|
|
49
|
+
_continue(streamID) {
|
|
50
|
+
const queue = this.streamMap.get(streamID).queue;
|
|
51
|
+
for (let i = 0; i < queue.length; i++) {
|
|
52
|
+
this.write(streamID, queue.shift());
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
register(host, port, callbacks) {
|
|
56
|
+
const streamID = this._nextStreamID++;
|
|
57
|
+
this.streamMap.set(streamID, {queue: [], streamID, buffer: this._bufferMax, dataCallBack: callbacks.dataCallBack, closeCallBack: callbacks.closeCallBack});
|
|
58
|
+
this._ws.send(createWispPacket({
|
|
59
|
+
packetType: CONNECT,
|
|
60
|
+
streamType: TCP,
|
|
61
|
+
streamID: streamID,
|
|
62
|
+
hostname: host,
|
|
63
|
+
port: port
|
|
64
|
+
}))
|
|
65
|
+
return streamID;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
write(streamID, data) {
|
|
69
|
+
const streamData = this.streamMap.get(streamID);
|
|
70
|
+
if (streamData.buffer > 0) {
|
|
71
|
+
streamData.buffer--;
|
|
72
|
+
|
|
73
|
+
this._ws.send(createWispPacket({
|
|
74
|
+
packetType: DATA,
|
|
75
|
+
streamID: streamID,
|
|
76
|
+
payload: data
|
|
77
|
+
}))
|
|
78
|
+
} else {
|
|
79
|
+
streamData.queue.push(data)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
close(streamID) {
|
|
83
|
+
this._ws.send(createWispPacket({
|
|
84
|
+
packetType: CLOSE,
|
|
85
|
+
streamID: streamID,
|
|
86
|
+
reason: 0x02
|
|
87
|
+
}))
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/* eslint-disable no-unreachable */
|
|
2
|
+
/* eslint-disable no-case-declarations */
|
|
3
|
+
// PACKET TYPES
|
|
4
|
+
export const CONNECT = 0x01;
|
|
5
|
+
export const DATA = 0x02;
|
|
6
|
+
export const CONTINUE = 0x03;
|
|
7
|
+
export const CLOSE = 0x04;
|
|
8
|
+
export const INFO = 0x05;
|
|
9
|
+
|
|
10
|
+
// STREAM TYPES
|
|
11
|
+
export const TCP = 0x01;
|
|
12
|
+
export const UDP = 0x02;
|
|
13
|
+
|
|
14
|
+
// Frequently used objects
|
|
15
|
+
export const textde = new TextDecoder();
|
|
16
|
+
const texten = new TextEncoder();
|
|
17
|
+
export const errors = {
|
|
18
|
+
0x01: "Reason unspecified or unknown. Returning a more specific reason should be preferred."
|
|
19
|
+
,0x03: "Unexpected stream closure due to a network error."
|
|
20
|
+
,0x41: "Stream creation failed due to invalid information. This could be sent if the destination was a reserved address or the port is invalid."
|
|
21
|
+
,0x42: "Stream creation failed due to an unreachable destination host. This could be sent if the destination is an domain which does not resolve to anything."
|
|
22
|
+
,0x43: "Stream creation timed out due to the destination server not responding."
|
|
23
|
+
,0x44: "Stream creation failed due to the destination server refusing the connection."
|
|
24
|
+
,0x47: "TCP data transfer timed out."
|
|
25
|
+
,0x48: "Stream destination address/domain is intentionally blocked by the proxy server."
|
|
26
|
+
,0x49: "Connection throttled by the server."
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {{packetType: number, streamID: number, streamType?: number, port?: number, hostname?: string, payload?: Uint8Array, reason?: number, remainingBuffer?: number}} ParsedWispPacket
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Parses a wisp packet fully
|
|
35
|
+
*
|
|
36
|
+
* @param {Uint8Array} data
|
|
37
|
+
* @returns {ParsedWispPacket} Packet Info
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
export function parseIncomingPacket(data) {
|
|
41
|
+
const view = new DataView(data.buffer, data.byteOffset);
|
|
42
|
+
const packetType = view.getUint8(0);
|
|
43
|
+
const streamID = view.getUint32(1, true);
|
|
44
|
+
switch (packetType) { // Packet payload starts at Offset 5
|
|
45
|
+
case CONNECT:
|
|
46
|
+
const streamType = view.getUint8(5);
|
|
47
|
+
const port = view.getUint16(6, true);
|
|
48
|
+
const hostname = textde.decode(data.subarray(8, data.length));
|
|
49
|
+
return {packetType, streamID, streamType, port, hostname}
|
|
50
|
+
break;
|
|
51
|
+
case DATA:
|
|
52
|
+
const payload = data.subarray(5, data.length);
|
|
53
|
+
return {packetType, streamID, payload}
|
|
54
|
+
break;
|
|
55
|
+
case CONTINUE:
|
|
56
|
+
const remainingBuffer = view.getUint32(5, true);
|
|
57
|
+
return {packetType, streamID, remainingBuffer}
|
|
58
|
+
break;
|
|
59
|
+
case CLOSE:
|
|
60
|
+
const reason = view.getUint8(5)
|
|
61
|
+
return {packetType, streamID, reason}
|
|
62
|
+
break;
|
|
63
|
+
case INFO:
|
|
64
|
+
const infoObj = {};
|
|
65
|
+
infoObj["version_major"] = view.getUint8(5);
|
|
66
|
+
infoObj["version_minor"] = view.getUint8(6);
|
|
67
|
+
|
|
68
|
+
let ptr = 7;
|
|
69
|
+
while (ptr < data.length) {
|
|
70
|
+
const extType = view.getUint8(ptr);
|
|
71
|
+
const extLength = view.getUint32(ptr + 1, true);
|
|
72
|
+
const payload = data.subarray(ptr + 5, ptr + 5 + extLength);
|
|
73
|
+
infoObj[extType] = payload;
|
|
74
|
+
ptr += 5 + extLength;
|
|
75
|
+
}
|
|
76
|
+
return {packetType, streamID, infoObj}
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* creates a wisp packet fully
|
|
82
|
+
*
|
|
83
|
+
* @param {ParsedWispPacket} instructions
|
|
84
|
+
* @returns {Uint8Array} Constructed Packet
|
|
85
|
+
*/
|
|
86
|
+
|
|
87
|
+
export function createWispPacket(instructions) {
|
|
88
|
+
let size = 5;
|
|
89
|
+
switch (instructions.packetType) { // Pass 1: determine size of packet
|
|
90
|
+
case CONNECT:
|
|
91
|
+
instructions.hostEncoded = texten.encode(instructions.hostname)
|
|
92
|
+
size += 3 + instructions.hostEncoded.length;
|
|
93
|
+
break;
|
|
94
|
+
case DATA:
|
|
95
|
+
size += instructions.payload.byteLength;
|
|
96
|
+
break;
|
|
97
|
+
case CONTINUE:
|
|
98
|
+
size += 4;
|
|
99
|
+
break;
|
|
100
|
+
case CLOSE:
|
|
101
|
+
size += 1;
|
|
102
|
+
break;
|
|
103
|
+
case INFO:
|
|
104
|
+
size += 2;
|
|
105
|
+
if (instructions.password)
|
|
106
|
+
size += 6;
|
|
107
|
+
if (instructions.puterAuth) {
|
|
108
|
+
instructions.passwordEncoded = texten.encode(instructions.puterAuth);
|
|
109
|
+
size += 8 + instructions.passwordEncoded.length;
|
|
110
|
+
}
|
|
111
|
+
break;
|
|
112
|
+
default:
|
|
113
|
+
throw new Error("Not supported")
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let data = new Uint8Array(size);
|
|
117
|
+
const view = new DataView(data.buffer);
|
|
118
|
+
view.setUint8(0, instructions.packetType);
|
|
119
|
+
view.setUint32(1, instructions.streamID, true);
|
|
120
|
+
switch (instructions.packetType) { // Pass 2: fill out packet
|
|
121
|
+
case CONNECT:
|
|
122
|
+
view.setUint8(5, instructions.streamType);
|
|
123
|
+
view.setUint16(6, instructions.port, true);
|
|
124
|
+
data.set(instructions.hostEncoded, 8);
|
|
125
|
+
break;
|
|
126
|
+
case DATA:
|
|
127
|
+
data.set(instructions.payload, 5);
|
|
128
|
+
break;
|
|
129
|
+
case CONTINUE:
|
|
130
|
+
view.setUint32(5, instructions.remainingBuffer, true)
|
|
131
|
+
break;
|
|
132
|
+
case CLOSE:
|
|
133
|
+
view.setUint8(5, instructions.reason)
|
|
134
|
+
break;
|
|
135
|
+
case INFO:
|
|
136
|
+
// WISP 2.0
|
|
137
|
+
view.setUint8(5, 2);
|
|
138
|
+
view.setUint8(6, 0);
|
|
139
|
+
|
|
140
|
+
if (instructions.password) {
|
|
141
|
+
// PASSWORD AUTH REQUIRED
|
|
142
|
+
view.setUint8(7, 0x02); // Protocol ID (Password)
|
|
143
|
+
view.setUint32(8, 1, true);
|
|
144
|
+
view.setUint8(12, 0); // Password required? true
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (instructions.puterAuth) {
|
|
148
|
+
// PASSWORD AUTH REQUIRED
|
|
149
|
+
view.setUint8(7, 0x02); // Protocol ID (Password)
|
|
150
|
+
view.setUint32(8, 5 + instructions.passwordEncoded.length, true);
|
|
151
|
+
view.setUint8(12, 0);
|
|
152
|
+
view.setUint16(13, instructions.passwordEncoded.length, true);
|
|
153
|
+
data.set(instructions.passwordEncoded, 15);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return data;
|
|
157
|
+
}
|