cloud-game/web/js/api.js
2026-06-04 22:36:30 +03:00

372 lines
8.9 KiB
JavaScript

import { log } from "log";
const endpoints = {
LATENCY_CHECK: 3,
INIT: 4,
INIT_WEBRTC: 100,
OFFER: 101,
ANSWER: 102,
ICE_CANDIDATE: 103,
GAME_START: 104,
GAME_QUIT: 105,
GAME_SAVE: 106,
GAME_LOAD: 107,
GAME_SET_PLAYER_INDEX: 108,
GAME_RECORDING: 110,
GET_WORKER_LIST: 111,
GAME_ERROR_NO_FREE_SLOTS: 112,
GAME_RESET: 113,
APP_VIDEO_CHANGE: 150,
};
const endpointName = Object.fromEntries(
Object.entries(endpoints).map(([k, v]) => [v, k]),
);
let transport = {
send: (packet) => {
log.warn(
"Default transport is used! Change it with the api.transport variable.",
packet,
);
},
keyboard: (packet) => {
log.warn(
"Default transport is used! Change it with the api.transport variable.",
packet,
);
},
mouse: (packet) => {
log.warn(
"Default transport is used! Change it with the api.transport variable.",
packet,
);
},
};
const packet = (type, payload, id) => {
const packet = { t: type };
if (id !== undefined) packet.id = id;
if (payload !== undefined) packet.p = payload;
transport.send(packet);
};
const decodeBytes = (b) => String.fromCharCode.apply(null, new Uint8Array(b));
const keyboardPress = (() => {
// 0 1 2 3 4 5 6
// [CODE ] P MOD
const buffer = new ArrayBuffer(7);
const dv = new DataView(buffer);
return (pressed = false, e) => {
if (e.repeat) return; // skip pressed key events
const key = libretro.mod;
let code = libretro.map("", e.code);
let shift = e.shiftKey;
// a special Esc for &$&!& Firefox
if (shift && code === 96) {
code = 27;
shift = false;
}
const mod =
0 |
(e.altKey && key.ALT) |
(e.ctrlKey && key.CTRL) |
(e.metaKey && key.META) |
(shift && key.SHIFT) |
(e.getModifierState("NumLock") && key.NUMLOCK) |
(e.getModifierState("CapsLock") && key.CAPSLOCK) |
(e.getModifierState("ScrollLock") && key.SCROLLOCK);
dv.setUint32(0, code);
dv.setUint8(4, +pressed);
dv.setUint16(5, mod);
transport.keyboard(buffer);
};
})();
const mouse = {
MOVEMENT: 0,
BUTTONS: 1,
};
const mouseMove = (() => {
// 0 1 2 3 4
// T DX DY
const buffer = new ArrayBuffer(5);
const dv = new DataView(buffer);
return (dx = 0, dy = 0) => {
dv.setUint8(0, mouse.MOVEMENT);
dv.setInt16(1, dx);
dv.setInt16(3, dy);
transport.mouse(buffer);
};
})();
const mousePress = (() => {
// 0 1
// T B
const buffer = new ArrayBuffer(2);
const dv = new DataView(buffer);
// 0: Main button pressed, usually the left button or the un-initialized state
// 1: Auxiliary button pressed, usually the wheel button or the middle button (if present)
// 2: Secondary button pressed, usually the right button
// 3: Fourth button, typically the Browser Back button
// 4: Fifth button, typically the Browser Forward button
const b2r = [1, 4, 2, 0, 0]; // browser mouse button to retro button
// assumed that only one button pressed / released
return (button = 0, pressed = false) => {
dv.setUint8(0, mouse.BUTTONS);
dv.setUint8(1, pressed ? b2r[button] : 0);
transport.mouse(buffer);
};
})();
const libretro = (function () {
// RETRO_KEYBOARD
const retro = {
"": 0,
Unidentified: 0,
Unknown: 0, // ???
First: 0, // ???
Backspace: 8,
Tab: 9,
Clear: 12,
Enter: 13,
Return: 13,
Pause: 19,
Escape: 27,
Space: 32,
Exclaim: 33,
Quotedbl: 34,
Hash: 35,
Dollar: 36,
Ampersand: 38,
Quote: 39,
Leftparen: 40,
"(": 40,
Rightparen: 41,
")": 41,
Asterisk: 42,
Plus: 43,
Comma: 44,
Minus: 45,
Period: 46,
Slash: 47,
Digit0: 48,
Digit1: 49,
Digit2: 50,
Digit3: 51,
Digit4: 52,
Digit5: 53,
Digit6: 54,
Digit7: 55,
Digit8: 56,
Digit9: 57,
Colon: 58,
":": 58,
Semicolon: 59,
";": 59,
Less: 60,
"<": 60,
Equal: 61,
"=": 61,
Greater: 62,
">": 62,
Question: 63,
"?": 63,
// RETROK_AT = 64,
BracketLeft: 91,
"[": 91,
Backslash: 92,
"\\": 92,
BracketRight: 93,
"]": 93,
// RETROK_CARET = 94,
// RETROK_UNDERSCORE = 95,
Backquote: 96,
"`": 96,
KeyA: 97,
KeyB: 98,
KeyC: 99,
KeyD: 100,
KeyE: 101,
KeyF: 102,
KeyG: 103,
KeyH: 104,
KeyI: 105,
KeyJ: 106,
KeyK: 107,
KeyL: 108,
KeyM: 109,
KeyN: 110,
KeyO: 111,
KeyP: 112,
KeyQ: 113,
KeyR: 114,
KeyS: 115,
KeyT: 116,
KeyU: 117,
KeyV: 118,
KeyW: 119,
KeyX: 120,
KeyY: 121,
KeyZ: 122,
"{": 123,
"|": 124,
"}": 125,
Tilde: 126,
"~": 126,
Delete: 127,
Numpad0: 256,
Numpad1: 257,
Numpad2: 258,
Numpad3: 259,
Numpad4: 260,
Numpad5: 261,
Numpad6: 262,
Numpad7: 263,
Numpad8: 264,
Numpad9: 265,
NumpadDecimal: 266,
NumpadDivide: 267,
NumpadMultiply: 268,
NumpadSubtract: 269,
NumpadAdd: 270,
NumpadEnter: 271,
NumpadEqual: 272,
ArrowUp: 273,
ArrowDown: 274,
ArrowRight: 275,
ArrowLeft: 276,
Insert: 277,
Home: 278,
End: 279,
PageUp: 280,
PageDown: 281,
F1: 282,
F2: 283,
F3: 284,
F4: 285,
F5: 286,
F6: 287,
F7: 288,
F8: 289,
F9: 290,
F10: 291,
F11: 292,
F12: 293,
F13: 294,
F14: 295,
F15: 296,
NumLock: 300,
CapsLock: 301,
ScrollLock: 302,
ShiftRight: 303,
ShiftLeft: 304,
ControlRight: 305,
ControlLeft: 306,
AltRight: 307,
AltLeft: 308,
MetaRight: 309,
MetaLeft: 310,
// RETROK_LSUPER = 311,
// RETROK_RSUPER = 312,
// RETROK_MODE = 313,
// RETROK_COMPOSE = 314,
// RETROK_HELP = 315,
// RETROK_PRINT = 316,
// RETROK_SYSREQ = 317,
// RETROK_BREAK = 318,
// RETROK_MENU = 319,
Power: 320,
// RETROK_EURO = 321,
// RETROK_UNDO = 322,
// RETROK_OEM_102 = 323,
};
const retroMod = {
NONE: 0x0000,
SHIFT: 0x01,
CTRL: 0x02,
ALT: 0x04,
META: 0x08,
NUMLOCK: 0x10,
CAPSLOCK: 0x20,
SCROLLOCK: 0x40,
};
const _map = (key = "", code = "") => {
return retro[code] || retro[key] || 0;
};
return {
map: _map,
mod: retroMod,
};
})();
/**
* Server API.
*
* Requires the actual api.transport implementation.
*/
export const api = {
set transport(t) {
transport = t;
},
endpoint: endpoints,
endpointName: endpointName,
decode: (b) => JSON.parse(decodeBytes(b)),
decodeB64: (b64) => JSON.parse(atob(b64)),
server: {
initWebrtc: () => packet(endpoints.INIT_WEBRTC),
sendIceCandidate: (candidate) =>
packet(endpoints.ICE_CANDIDATE, btoa(JSON.stringify(candidate))),
sendSdp: (sdp) => packet(endpoints.ANSWER, btoa(JSON.stringify(sdp))),
latencyCheck: (id, list) => packet(endpoints.LATENCY_CHECK, list, id),
getWorkerList: () => packet(endpoints.GET_WORKER_LIST),
},
game: {
input: {
keyboard: {
press: keyboardPress,
},
mouse: {
move: mouseMove,
press: mousePress,
},
},
load: () => packet(endpoints.GAME_LOAD),
reset: (roomId) => packet(endpoints.GAME_RESET, { room_id: roomId }),
save: () => packet(endpoints.GAME_SAVE),
setPlayerIndex: (i) => packet(endpoints.GAME_SET_PLAYER_INDEX, i),
start: (game, roomId, record, recordUser, player) =>
packet(endpoints.GAME_START, {
game_name: game,
room_id: roomId,
player_index: player,
record: record,
record_user: recordUser,
}),
toggleRecording: (active = false, userName = "") =>
packet(endpoints.GAME_RECORDING, {
active: active,
user: userName,
}),
quit: (roomId) => packet(endpoints.GAME_QUIT, { room_id: roomId }),
},
};