Skip to content

Commit

Permalink
Add an adapter extension for geckos.io servers
Browse files Browse the repository at this point in the history
It also works on previews, though that is only guaranteed to work on the latest GDevelop version on windows
  • Loading branch information
arthuro555 committed Sep 20, 2022
1 parent 4fda7ee commit 026d275
Show file tree
Hide file tree
Showing 10 changed files with 234 additions and 24 deletions.
2 changes: 1 addition & 1 deletion code/Adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export abstract class ClientAdapter {

export abstract class ServerAdapter {
/** Ensure returns a promise that resolves once fully connected to a server. */
abstract prepare(): Promise<void>;
abstract prepare(runtimeScene: gdjs.RuntimeScene): Promise<void>;
/** Called when the adapter is no longer needed to gracefully shutdown. */
abstract close(): void;

Expand Down
7 changes: 4 additions & 3 deletions code/adapters/geckos-client.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/// <reference path="../global.d.ts"/>
import { geckos, type ClientChannel } from "@geckos.io/client";

const logger = new gdjs.Logger("THNK - Geckos.io Adapter");

export class GeckosClientAdapter extends THNK.ClientAdapter {
THNK.GeckosClientAdapter = class GeckosClientAdapter extends (
THNK.ClientAdapter
) {
ip: string;
port: number;
connection: ClientChannel | null = null;
Expand Down Expand Up @@ -41,4 +42,4 @@ export class GeckosClientAdapter extends THNK.ClientAdapter {
message.buffer.slice(message.buffer.byteLength - message.byteLength)
);
}
}
};
99 changes: 86 additions & 13 deletions code/adapters/geckos-server.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,87 @@
/// <reference path="../global.d.ts"/>
import {
geckos,
iceServers,
type GeckosServer,
type ServerChannel,
import type {
geckos as GeckosType,
GeckosServer,
ServerChannel,
} from "@geckos.io/server";
import { async as StreamZip } from "node-stream-zip";
import { FollowResponse, wrap } from "follow-redirects";

const getSomeNums = () =>
Math.random()
.toFixed(Math.ceil(Math.random() * 6) + 2)
.slice(2);

export class GeckosClientAdapter extends THNK.ServerAdapter {
THNK.GeckosServerAdapter = class GeckosServerAdapter extends (
THNK.ServerAdapter
) {
ip: string;
port: number;
id = 0;
server: GeckosServer | null = null;
httpServer: import("http").Server | null = null;
channels = new Map<string, ServerChannel>();
constructor(ip: string, port: number) {
super();
this.ip = ip;
this.port = port;
}

async prepare(): Promise<void> {
this.server = geckos({ iceServers, label: "THNK" });
async prepare(runtimeScene: gdjs.RuntimeScene): Promise<void> {
const electronRemote = runtimeScene
.getGame()
.getRenderer()
.getElectronRemote();

if (!electronRemote) {
throw new Error(
"The game does not seem to be running on a desktop, impossible to launch geckos server!"
);
}

const electronRequire = electronRemote.require as (
moduleNameOrPath: string
) => any;

let geckos: typeof GeckosType | undefined;
if (!runtimeScene.getGame().isPreview()) {
geckos = electronRequire("@geckos.io/server").geckos as typeof GeckosType;
} else {
// On previews we need to download a prebuilt version of the module as it is not pre-installed
const fs = electronRequire("fs") as typeof import("fs");
if (!fs.existsSync("./geckos-server")) {
console.info(`Geckos server not found, downloading it now!`);

const {
https: { get },
} = wrap({ https: electronRequire("https") as typeof import("https") });
const { pipeline } = electronRequire(
"stream/promises"
) as typeof import("stream/promises");

const response = (await new Promise((r) =>
get(
"https://s3.arthuro555.com/geckos-server-electron.zip",
(response) => r(response as FollowResponse & NodeJS.ReadStream)
)
)) as NodeJS.ReadStream;

await pipeline(response, fs.createWriteStream("./geckos-server.zip"));

const zip = new StreamZip({ file: "./geckos-server.zip" });

fs.mkdirSync("./geckos-server");
await zip.extract(null, "./geckos-server");

await zip.close();
}
geckos = electronRequire(
process.cwd() + "/geckos-server/index.js"
).geckos;
}
if (!geckos) throw new Error("Geckos not found!");

this.server = geckos({ label: "THNK" });

this.server.onConnection((channel) => {
// Generate a simple ID that is certainly unique,
Expand All @@ -38,17 +95,33 @@ export class GeckosClientAdapter extends THNK.ServerAdapter {
channel.onDisconnect(() => this.onDisconnection(id));
});

this.server.listen(this.port);
this.httpServer = (
electronRequire("http") as typeof import("http")
).createServer();
this.server.addServer(this.httpServer);
this.httpServer.listen(this.port);

// Force close the server when closing the preview window
const close = (e: BeforeUnloadEvent) => {
e.returnValue = "false";
this.close();
window.removeEventListener("beforeunload", close);
window.close();
};
window.addEventListener("beforeunload", close);
}

close() {
if (this.server) {
// Close the HTTP server
this.server.server.close();
if (this.server && this.httpServer) {
this.httpServer.close();
if (this.httpServer.closeAllConnections)
this.httpServer.closeAllConnections();
// Close current connections
for (const connection of this.channels.values()) connection.close();
// Clear everything out for GC
this.channels.clear();
// Close the HTTP server
this.httpServer = null;
this.server = null;
}
}
Expand All @@ -65,4 +138,4 @@ export class GeckosClientAdapter extends THNK.ServerAdapter {
getServerID(): string {
return `${getSomeNums()}-server-${getSomeNums()}`;
}
}
};
7 changes: 7 additions & 0 deletions code/global.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
export * from ".";
import type { ClientAdapter, ServerAdapter } from "./Adapter";
export class GeckosClientAdapter extends ClientAdapter {
constructor(ip: string, port: number) {}
}
export class GeckosServerAdapter extends ServerAdapter {
constructor(ip: string, port: number) {}
}
export as namespace THNK;
2 changes: 1 addition & 1 deletion code/server/StartServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const startServer = async (
// The adapter is responsible for initiating a server on which we can listen to Uint8Arrays from clients.
// Once the promise resolves, we assume we are all set to start receiving clients, and begin listening to
// incoming messages.
await adapter.prepare();
await adapter.prepare(runtimeScene);
} catch (e) {
logger.error("Adapter crashed while starting server! Error: ", e);
// Abort server startup
Expand Down
2 changes: 1 addition & 1 deletion extensions/THNK.json

Large diffs are not rendered by default.

90 changes: 90 additions & 0 deletions extensions/THNK_GeckosServer.json

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,23 @@
"generate-protocol": "node ./scripts/generate-protocol.js",
"build:thnk": "tsup ./code/index.ts --format esm --platform=browser --treeshake smallest --minify && tsup ./dist/index.mjs --format iife --platform=browser --global-name THNK --minify && node -e \"fs.promises.rm('./dist/index.mjs', {force:true})\"",
"build:thnk:dev": "tsup ./code/index.ts --format iife --platform=browser --global-name THNK",
"build:adapters": "tsup ./code/adapters/*.ts --format iife",
"build:adapters": "tsup ./code/adapters/*.ts --format iife && tsup ./code/adapters/geckos-server.ts --minify --format cjs",
"build:extensions": "node ./scripts/generate-extensions.js",
"build:clean": "node -e \"fs.promises.rm('./dist', {recursive:true,force:true})\"",
"build": "npm run build:clean && npm run generate-protocol && npm run build:thnk && npm run build:adapters && npm run build:extensions"
},
"devDependencies": {
"@geckos.io/client": "^2.2.3",
"@geckos.io/server": "^2.2.3",
"@types/follow-redirects": "^1.14.1",
"@types/jest": "^29.0.3",
"@types/node": "^18.0.3",
"@types/node": "^18.7.18",
"@types/pako": "^2.0.0",
"flatbuffers": "^2.0.6",
"follow-redirects": "^1.15.2",
"jest": "^29.0.3",
"msgpackr": "^1.6.1",
"node-stream-zip": "^1.15.0",
"pako": "^2.0.4",
"prettier": "^2.7.1",
"ts-jest": "^29.0.1",
Expand Down
23 changes: 21 additions & 2 deletions scripts/generate-extensions.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ const fs = require("fs");
fs.readFileSync(extensionsPath + "THNK.json").toString()
);

thnkExt.eventsFunctions[0].events[0].inlineCode = `// Load THNK library (https://github.com/arthuro555/THNK)
window.${thnkCode.slice(`"use strict";var `.length)}`;
thnkExt.eventsFunctions[0].events[0].inlineCode = `}// Load THNK library (https://github.com/arthuro555/THNK)
window.${thnkCode.slice(`"use strict";var `.length)}{`;

fs.writeFileSync(
extensionsPath + "THNK.json",
Expand All @@ -35,3 +35,22 @@ window.${thnkCode.slice(`"use strict";var `.length)}`;
JSON.stringify(p2pExt, null, 2)
);
}

{
const geckosServerCode = fs
.readFileSync(distPath + "geckos-server.js")
.toString();
/** @type {{eventsFunctions: { name: string, events: { type: string, inlineCode: string }[] }[]}} */
const geckosServerExt = JSON.parse(
fs.readFileSync(extensionsPath + "THNK_GeckosServer.json").toString()
);

geckosServerExt.eventsFunctions[0].events[0].inlineCode =
`// Load THNK Geckos Server Adapter (https://github.com/arthuro555/THNK)\n` +
geckosServerCode;

fs.writeFileSync(
extensionsPath + "THNK_GeckosServer.json",
JSON.stringify(geckosServerExt, null, 2)
);
}
19 changes: 18 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,13 @@
dependencies:
"@babel/types" "^7.3.0"

"@types/follow-redirects@^1.14.1":
version "1.14.1"
resolved "https://registry.yarnpkg.com/@types/follow-redirects/-/follow-redirects-1.14.1.tgz#c08b173be7517ddc53725d0faf9648d4dc7a9cdb"
integrity sha512-THBEFwqsLuU/K62B5JRwab9NW97cFmL4Iy34NTMX0bMycQVzq2q7PKOkhfivIwxdpa/J72RppgC42vCHfwKJ0Q==
dependencies:
"@types/node" "*"

"@types/graceful-fs@^4.1.3":
version "4.1.5"
resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15"
Expand Down Expand Up @@ -718,7 +725,7 @@
expect "^29.0.0"
pretty-format "^29.0.0"

"@types/node@*", "@types/node@^18.0.3":
"@types/node@*", "@types/node@^18.7.18":
version "18.7.18"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.18.tgz#633184f55c322e4fb08612307c274ee6d5ed3154"
integrity sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==
Expand Down Expand Up @@ -1421,6 +1428,11 @@ flatbuffers@^2.0.6:
resolved "https://registry.yarnpkg.com/flatbuffers/-/flatbuffers-2.0.7.tgz#3e8e2c46930820747578158cdf11c8b80f8abf89"
integrity sha512-5JulPk3a7zTdb2p2ElkAT8hmw4udmSL8GoRKkDa/y9+qFwKbFrRgAbF4VgSt2oIGTEpBqz9CmsvwCstwJ5D9kg==

follow-redirects@^1.15.2:
version "1.15.2"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==

fs-constants@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
Expand Down Expand Up @@ -2264,6 +2276,11 @@ node-releases@^2.0.6:
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503"
integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==

node-stream-zip@^1.15.0:
version "1.15.0"
resolved "https://registry.yarnpkg.com/node-stream-zip/-/node-stream-zip-1.15.0.tgz#158adb88ed8004c6c49a396b50a6a5de3bca33ea"
integrity sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==

normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
Expand Down

0 comments on commit 026d275

Please sign in to comment.