Bring in node-signald
This commit is contained in:
parent
26bd446368
commit
41971732d0
13 changed files with 3474 additions and 0 deletions
229
packages/node-signald/util/generate.js
Normal file
229
packages/node-signald/util/generate.js
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
const net = require("net");
|
||||
const fs = require("fs");
|
||||
const process = require("process");
|
||||
const camelCase = require("camelcase");
|
||||
|
||||
const SOCKETFILE =
|
||||
process.env.SIGNALD_SOCKET || "/run/user/1000/signald/signald.sock";
|
||||
const GENERATEDFILE = "./src/generated.ts";
|
||||
const VERSION = "v1";
|
||||
let globalVersion;
|
||||
|
||||
const toJs = (name) => name.replace("-", "_");
|
||||
|
||||
const TYPES = {
|
||||
String: "string",
|
||||
long: "number",
|
||||
int: "number",
|
||||
Integer: "number",
|
||||
Map: "Record<string, string>",
|
||||
boolean: "boolean",
|
||||
Boolean: "boolean",
|
||||
Long: "number",
|
||||
UUID: "string",
|
||||
};
|
||||
|
||||
const convertType = (field) => {
|
||||
let name = "";
|
||||
if (field.version) {
|
||||
name = `${field.type}${field.version}`;
|
||||
} else {
|
||||
if (!TYPES[field.type]) {
|
||||
throw new Error(
|
||||
`unknown type ${field.type} inside ${JSON.stringify(field)}`
|
||||
);
|
||||
}
|
||||
name = TYPES[field.type];
|
||||
}
|
||||
|
||||
if (field.list) {
|
||||
return `${name}[]`;
|
||||
}
|
||||
return name;
|
||||
};
|
||||
const fieldToData = ([rawName, field]) => {
|
||||
const name = toJs(rawName);
|
||||
const type = convertType(field);
|
||||
const requiredStr = field.required ? "" : "?";
|
||||
const example = field.example ? `\n Example: ${field.example}\n` : "\n";
|
||||
const doc = field.doc ? `\n/**\n ${field.doc}.${example} */\n` : "";
|
||||
let code = `${doc}${name}${requiredStr}: ${type};`;
|
||||
return {
|
||||
name,
|
||||
rawName,
|
||||
type,
|
||||
code,
|
||||
doc: field.doc,
|
||||
example: field.example,
|
||||
required: field.required,
|
||||
};
|
||||
};
|
||||
|
||||
const typeToData = (name, version, data) => {
|
||||
const typeName = `${name}${version}`;
|
||||
const doc = data.doc
|
||||
? `\n/**
|
||||
${data.doc}
|
||||
*/\n`
|
||||
: "\n";
|
||||
|
||||
const header = `export type ${typeName} = {
|
||||
`;
|
||||
const dataFields = Object.entries(data.fields).map(fieldToData);
|
||||
const codeFields = dataFields.map((field) => field.code);
|
||||
const body = codeFields.join("\n");
|
||||
const footer = "\n}\n";
|
||||
// ensure required fields are sorted before non-required fields
|
||||
dataFields.sort((a, b) =>
|
||||
a.required === b.required ? 0 : a.required ? -1 : 1
|
||||
);
|
||||
return {
|
||||
name,
|
||||
version,
|
||||
typeName,
|
||||
fields: dataFields,
|
||||
code: doc + header + body + footer,
|
||||
};
|
||||
};
|
||||
|
||||
const actionToCode = (structures, rawName, version, data) => {
|
||||
const name = camelCase(rawName);
|
||||
const { request, response } = data;
|
||||
const requestFields = structures[request].fields;
|
||||
const returnTypeStr = response
|
||||
? `Promise<${response}${version}>`
|
||||
: "Promise<void>";
|
||||
let code = ` async ${name}(`;
|
||||
requestFields.map((field) => {
|
||||
const requiredStr = field.required ? "" : "?";
|
||||
code += `${field.name}${requiredStr}: ${field.type},\n`;
|
||||
});
|
||||
|
||||
code += `): ${returnTypeStr} {\n`;
|
||||
const params = requestFields
|
||||
.map((field) => {
|
||||
return `"${field.rawName}": ${field.name}`;
|
||||
})
|
||||
.join(",\n");
|
||||
const requestStr = `{"type": "${rawName}","version": "${version}", ${params}}`;
|
||||
const responseStr = `this.getResponse(${requestStr})`;
|
||||
if (response) {
|
||||
code += `return ${responseStr} as Promise<${response}${version}>`;
|
||||
} else {
|
||||
code += `await ${responseStr}; return;`;
|
||||
}
|
||||
code += "\n}\n";
|
||||
return code;
|
||||
};
|
||||
|
||||
const saveFile = (code) => {
|
||||
fs.writeFileSync(GENERATEDFILE, code);
|
||||
console.log("generated code at ", GENERATEDFILE);
|
||||
};
|
||||
|
||||
const processProtocolDefinition = (client, def, version) => {
|
||||
console.log("processing protocol definition");
|
||||
const when = new Date(new Date().toUTCString());
|
||||
let contents = `
|
||||
/* Generated by the output of the 'protocol' command of signald
|
||||
Date: ${when}
|
||||
Version: ${globalVersion}
|
||||
*/
|
||||
import {JSONTransport} from "./util";
|
||||
`;
|
||||
|
||||
const structures = {};
|
||||
// generate type definitions
|
||||
for (version in def.types) {
|
||||
Object.entries(def.types[version]).map(([name, data]) => {
|
||||
structures[name] = typeToData(name, version, data);
|
||||
contents += structures[name].code;
|
||||
});
|
||||
}
|
||||
|
||||
// TODO this should be improved once v0 is totally deprecated
|
||||
const typeNames = ["version", "message", "subscribe", "unsubscribe"]
|
||||
.map((name) => `"${name}"`)
|
||||
.join(" | ");
|
||||
contents += `\ndeclare module "./util" {
|
||||
interface EventTypes {
|
||||
/**
|
||||
* The v0 event emitted when a signal message is received
|
||||
* @event
|
||||
*/
|
||||
messagev0(message: JsonMessageEnvelopev1): void;
|
||||
/**
|
||||
* @event
|
||||
*/
|
||||
subscribev0(): void;
|
||||
/**
|
||||
* @event
|
||||
*/
|
||||
unsubscribev0(): void;
|
||||
}
|
||||
}\n`;
|
||||
|
||||
// generate action functions
|
||||
for (version in def.actions) {
|
||||
if (version != VERSION) continue;
|
||||
contents += "export class SignaldGeneratedApi extends JSONTransport{\n";
|
||||
Object.entries(def.actions[version]).map(([name, data]) => {
|
||||
contents += actionToCode(structures, name, version, data);
|
||||
});
|
||||
contents += "\n}\n";
|
||||
}
|
||||
|
||||
saveFile(contents);
|
||||
cleanup();
|
||||
};
|
||||
|
||||
// := Driver
|
||||
|
||||
const getProtocolDefinition = (client) => {
|
||||
const msg = {
|
||||
type: "protocol",
|
||||
};
|
||||
console.log("requesting protocol definition");
|
||||
client.write(JSON.stringify(msg) + "\n");
|
||||
};
|
||||
const client = net.createConnection(SOCKETFILE);
|
||||
let buffer = "";
|
||||
client.setEncoding("utf8");
|
||||
|
||||
function cleanup() {
|
||||
client.end();
|
||||
process.exit(0);
|
||||
}
|
||||
client.on("connect", () => {
|
||||
console.log("connected");
|
||||
buffer = "";
|
||||
});
|
||||
|
||||
client.on("data", (payload) => {
|
||||
buffer += payload;
|
||||
if (!buffer.endsWith("\n")) {
|
||||
return;
|
||||
} else {
|
||||
const data = buffer;
|
||||
buffer = "";
|
||||
data.split("\n").forEach((line) => {
|
||||
if (!line) return;
|
||||
|
||||
const msg = JSON.parse(line);
|
||||
if (msg.type === "version") {
|
||||
getProtocolDefinition(client);
|
||||
globalVersion = msg.data.version;
|
||||
}
|
||||
if (msg.type === "protocol") {
|
||||
// console.log(JSON.stringify(msg.data, null, 2));
|
||||
processProtocolDefinition(client, msg.data, globalVersion);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
client.on("error", (err) => {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
process.on("SIGINT", cleanup);
|
||||
Loading…
Add table
Add a link
Reference in a new issue