mirror of
https://github.com/HeyPuter/puter.git
synced 2025-02-02 23:28:39 +08:00
networking API beginnings
This commit is contained in:
parent
6d196d59f0
commit
1ed3bccaa5
@ -19,6 +19,8 @@ import { APIAccessService } from './services/APIAccess.js';
|
||||
import { XDIncomingService } from './services/XDIncoming.js';
|
||||
import { NoPuterYetService } from './services/NoPuterYet.js';
|
||||
import { Debug } from './modules/Debug.js';
|
||||
import { PSocket, wispInfo } from './modules/networking/PSocket.js';
|
||||
import { PWispHandler } from './modules/networking/PWispHandler.js';
|
||||
|
||||
// TODO: This is for a safe-guard below; we should check if we can
|
||||
// generalize this behavior rather than hard-coding it.
|
||||
@ -317,6 +319,22 @@ window.puter = (function() {
|
||||
await this.services.wait_for_init(['api-access']);
|
||||
this.p_can_request_rao_.resolve();
|
||||
})();
|
||||
(async () => {
|
||||
const wispToken = (await (await fetch('https://api.puter.com/wisp/relay-token/create', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.authToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({}),
|
||||
})).json())["token"];
|
||||
wispInfo.handler = new PWispHandler(wispInfo.server, wispToken);
|
||||
this.net = {
|
||||
Socket: PSocket
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
44
src/puter-js/src/modules/networking/PSocket.js
Normal file
44
src/puter-js/src/modules/networking/PSocket.js
Normal file
@ -0,0 +1,44 @@
|
||||
import EventListener from "../../lib/EventListener.js";
|
||||
import {PWispHandler} from "./PWispHandler.js"
|
||||
|
||||
|
||||
export let wispInfo = {
|
||||
server: "wss://puter.cafe/",
|
||||
handler: undefined
|
||||
};
|
||||
|
||||
export class PSocket extends EventListener {
|
||||
_events = new Map();
|
||||
_streamID;
|
||||
constructor(host, port) {
|
||||
super(["data", "drain", "open", "close"]);
|
||||
const callbacks = {
|
||||
dataCallBack: (data) => {
|
||||
this.emit("data", data);
|
||||
},
|
||||
closeCallBack: (reason) => {
|
||||
this.emit("close", false); // TODO, report errors
|
||||
},
|
||||
openCallBack: () => {
|
||||
this.emit("open");
|
||||
}
|
||||
}
|
||||
|
||||
this._streamID = wispInfo.handler.register(host, port, callbacks);
|
||||
|
||||
}
|
||||
addListener(...args) {
|
||||
this.on(...args);
|
||||
}
|
||||
write(data, callback) {
|
||||
if (data.buffer) { // typedArray
|
||||
wispInfo.handler.write(this._streamID, data);
|
||||
if (callback) callback();
|
||||
} else if (data.resize) {
|
||||
data.write(this._streamID, new Uint8Array(data));
|
||||
if (callback) callback();
|
||||
} else if (data.arrayBuffer) { // Oh No, a blob, I need to handle this later, maybe with https://gist.github.com/jimmywarting/65c358f878cac8e7f39cfb7d43931f62?
|
||||
|
||||
}
|
||||
}
|
||||
}
|
79
src/puter-js/src/modules/networking/PWispHandler.js
Normal file
79
src/puter-js/src/modules/networking/PWispHandler.js
Normal file
@ -0,0 +1,79 @@
|
||||
import {CLOSE, CONNECT, DATA, CONTINUE, INFO, TCP, UDP, createWispPacket, parseIncomingPacket, textde} from "./parsers.js"
|
||||
|
||||
export class PWispHandler {
|
||||
_ws;
|
||||
_nextStreamID = 1;
|
||||
_bufferMax;
|
||||
streamMap = new Map();
|
||||
constructor(wispURL, puterAuth) {
|
||||
this._ws = new WebSocket(wispURL);
|
||||
this._ws.binaryType = "arraybuffer"
|
||||
this._ws.onmessage = (event) => {
|
||||
const parsed = parseIncomingPacket(new Uint8Array(event.data));
|
||||
switch (parsed.packetType) {
|
||||
case DATA:
|
||||
this.streamMap.get(parsed.streamID).dataCallBack(parsed.payload.slice(0)) // return a copy for the user to do as they please
|
||||
break;
|
||||
case CONTINUE:
|
||||
if (parsed.streamID === 0) {
|
||||
this._bufferMax = parsed.remainingBuffer;
|
||||
return;
|
||||
}
|
||||
this.streamMap.get(parsed.streamID).buffer = parsed.remainingBuffer;
|
||||
this._continue()
|
||||
break;
|
||||
case CLOSE:
|
||||
this.streamMap.get(parsed.streamID).closeCallBack(parsed.reason);
|
||||
break;
|
||||
case INFO:
|
||||
puterAuth && this._ws.send(createWispPacket({
|
||||
packetType: INFO,
|
||||
streamID: 0,
|
||||
puterAuth
|
||||
}))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_continue(streamID) {
|
||||
const queue = this.streamMap.get(streamID).queue;
|
||||
for (let i = 0; i < queue.length; i++) {
|
||||
this.write(streamID, queue.shift());
|
||||
}
|
||||
}
|
||||
register(host, port, callbacks) {
|
||||
const streamID = this._nextStreamID++;
|
||||
this.streamMap.set(streamID, {queue: [], streamID, buffer: this._bufferMax, dataCallBack: callbacks.dataCallBack, closeCallBack: callbacks.closeCallBack, openCallBack: callbacks.openCallBack});
|
||||
this._ws.send(createWispPacket({
|
||||
packetType: CONNECT,
|
||||
streamType: TCP,
|
||||
streamID: streamID,
|
||||
hostname: host,
|
||||
port: port
|
||||
}))
|
||||
|
||||
return streamID;
|
||||
}
|
||||
|
||||
write(streamID, data) {
|
||||
const streamData = this.streamMap.get(streamID);
|
||||
if (streamData.buffer > 0) {
|
||||
streamData.buffer--;
|
||||
|
||||
this._ws.send(createWispPacket({
|
||||
packetType: DATA,
|
||||
streamID: streamID,
|
||||
payload: data
|
||||
}))
|
||||
} else {
|
||||
streamData.queue.push(data)
|
||||
}
|
||||
}
|
||||
close(streamID) {
|
||||
this._ws.send(createWispPacket({
|
||||
packetType: CLOSE,
|
||||
streamID: streamID,
|
||||
reason: 0x02
|
||||
}))
|
||||
}
|
||||
}
|
147
src/puter-js/src/modules/networking/parsers.js
Normal file
147
src/puter-js/src/modules/networking/parsers.js
Normal file
@ -0,0 +1,147 @@
|
||||
/* eslint-disable no-unreachable */
|
||||
/* eslint-disable no-case-declarations */
|
||||
// PACKET TYPES
|
||||
export const CONNECT = 0x01;
|
||||
export const DATA = 0x02;
|
||||
export const CONTINUE = 0x03;
|
||||
export const CLOSE = 0x04;
|
||||
export const INFO = 0x05;
|
||||
|
||||
// STREAM TYPES
|
||||
export const TCP = 0x01;
|
||||
export const UDP = 0x02;
|
||||
|
||||
// Frequently used objects
|
||||
export const textde = new TextDecoder();
|
||||
const texten = new TextEncoder();
|
||||
|
||||
/**
|
||||
* @typedef {{packetType: number, streamID: number, streamType?: number, port?: number, hostname?: string, payload?: Uint8Array, reason?: number, remainingBuffer?: number}} ParsedWispPacket
|
||||
*/
|
||||
|
||||
/**
|
||||
* Parses a wisp packet fully
|
||||
*
|
||||
* @param {Uint8Array} data
|
||||
* @returns {ParsedWispPacket} Packet Info
|
||||
*/
|
||||
|
||||
export function parseIncomingPacket(data) {
|
||||
const view = new DataView(data.buffer, data.byteOffset);
|
||||
const packetType = view.getUint8(0);
|
||||
const streamID = view.getUint32(1, true);
|
||||
switch (packetType) { // Packet payload starts at Offset 5
|
||||
case CONNECT:
|
||||
const streamType = view.getUint8(5);
|
||||
const port = view.getUint16(6, true);
|
||||
const hostname = textde.decode(data.subarray(8, data.length));
|
||||
return {packetType, streamID, streamType, port, hostname}
|
||||
break;
|
||||
case DATA:
|
||||
const payload = data.subarray(5, data.length);
|
||||
return {packetType, streamID, payload}
|
||||
break;
|
||||
case CONTINUE:
|
||||
const remainingBuffer = view.getUint32(5, true);
|
||||
return {packetType, streamID, remainingBuffer}
|
||||
break;
|
||||
case CLOSE:
|
||||
const reason = view.getUint8(5)
|
||||
return {packetType, streamID, reason}
|
||||
break;
|
||||
case INFO:
|
||||
const infoObj = {};
|
||||
infoObj["version_major"] = view.getUint8(5);
|
||||
infoObj["version_minor"] = view.getUint8(6);
|
||||
|
||||
let ptr = 7;
|
||||
while (ptr < data.length) {
|
||||
const extType = view.getUint8(ptr);
|
||||
const extLength = view.getUint32(ptr + 1, true);
|
||||
const payload = data.subarray(ptr + 5, ptr + 5 + extLength);
|
||||
infoObj[extType] = payload;
|
||||
ptr += 5 + extLength;
|
||||
}
|
||||
return {packetType, streamID, infoObj}
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* creates a wisp packet fully
|
||||
*
|
||||
* @param {ParsedWispPacket} instructions
|
||||
* @returns {Uint8Array} Constructed Packet
|
||||
*/
|
||||
|
||||
export function createWispPacket(instructions) {
|
||||
let size = 5;
|
||||
switch (instructions.packetType) { // Pass 1: determine size of packet
|
||||
case CONNECT:
|
||||
instructions.hostEncoded = texten.encode(instructions.hostname)
|
||||
size += 3 + instructions.hostEncoded.length;
|
||||
break;
|
||||
case DATA:
|
||||
size += instructions.payload.byteLength;
|
||||
break;
|
||||
case CONTINUE:
|
||||
size += 4;
|
||||
break;
|
||||
case CLOSE:
|
||||
size += 1;
|
||||
break;
|
||||
case INFO:
|
||||
size += 2;
|
||||
if (instructions.password)
|
||||
size += 6;
|
||||
if (instructions.puterAuth) {
|
||||
instructions.passwordEncoded = texten.encode(instructions.puterAuth);
|
||||
size += 8 + instructions.passwordEncoded.length;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error("Not supported")
|
||||
}
|
||||
|
||||
let data = new Uint8Array(size);
|
||||
const view = new DataView(data.buffer);
|
||||
view.setUint8(0, instructions.packetType);
|
||||
view.setUint32(1, instructions.streamID, true);
|
||||
switch (instructions.packetType) { // Pass 2: fill out packet
|
||||
case CONNECT:
|
||||
view.setUint8(5, instructions.streamType);
|
||||
view.setUint16(6, instructions.port, true);
|
||||
data.set(instructions.hostEncoded, 8);
|
||||
break;
|
||||
case DATA:
|
||||
data.set(instructions.payload, 5);
|
||||
break;
|
||||
case CONTINUE:
|
||||
view.setUint32(5, instructions.remainingBuffer, true)
|
||||
break;
|
||||
case CLOSE:
|
||||
view.setUint8(5, instructions.reason)
|
||||
break;
|
||||
case INFO:
|
||||
// WISP 2.0
|
||||
view.setUint8(5, 2);
|
||||
view.setUint8(6, 0);
|
||||
|
||||
if (instructions.password) {
|
||||
// PASSWORD AUTH REQUIRED
|
||||
view.setUint8(7, 0x02); // Protocol ID (Password)
|
||||
view.setUint32(8, 1, true);
|
||||
view.setUint8(12, 0); // Password required? true
|
||||
}
|
||||
|
||||
if (instructions.puterAuth) {
|
||||
console.log("Puter auth " + instructions.puterAuth)
|
||||
// PASSWORD AUTH REQUIRED
|
||||
view.setUint8(7, 0x02); // Protocol ID (Password)
|
||||
view.setUint32(8, 5 + instructions.passwordEncoded.length, true);
|
||||
view.setUint8(12, 0);
|
||||
view.setUint16(13, instructions.passwordEncoded.length, true);
|
||||
data.set(instructions.passwordEncoded, 15);
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
Loading…
Reference in New Issue
Block a user