*Move dns and ssl control to plugin apis
*Add files plugin api *Add logger to api *Add seed getter to api *Add app router to api
This commit is contained in:
parent
8e881a7dc1
commit
08fdc88874
|
@ -33,6 +33,7 @@ config.inject({
|
|||
logLevel: "info",
|
||||
pluginFolder: path.join(configDir, "plugins"),
|
||||
plugins: ["core"],
|
||||
ssl: true,
|
||||
});
|
||||
|
||||
config.load({
|
||||
|
|
60
src/dns.ts
60
src/dns.ts
|
@ -1,14 +1,11 @@
|
|||
import cron from "node-cron";
|
||||
import { get as getDHT } from "./dht.js";
|
||||
import { Buffer } from "buffer";
|
||||
import { Parser } from "xml2js";
|
||||
import { URL } from "url";
|
||||
import { pack } from "msgpackr";
|
||||
import config from "./config.js";
|
||||
import { errorExit } from "./error.js";
|
||||
import log from "loglevel";
|
||||
import { createHash } from "crypto";
|
||||
import { dynImport } from "./util.js";
|
||||
import type { DnsProvider } from "@lumeweb/relay-types";
|
||||
|
||||
let activeIp: string;
|
||||
let fetch: typeof import("node-fetch").default;
|
||||
|
@ -17,6 +14,12 @@ let hashDataKey: typeof import("@lumeweb/kernel-utils").hashDataKey;
|
|||
|
||||
const REGISTRY_NODE_KEY = "lumeweb-dht-node";
|
||||
|
||||
let dnsProvider: DnsProvider = async (ip) => {};
|
||||
|
||||
export function setDnsProvider(provider: DnsProvider) {
|
||||
dnsProvider = provider;
|
||||
}
|
||||
|
||||
async function ipUpdate() {
|
||||
let currentIp = await getCurrentIp();
|
||||
|
||||
|
@ -24,11 +27,7 @@ async function ipUpdate() {
|
|||
return;
|
||||
}
|
||||
|
||||
let domain = await getDomainInfo();
|
||||
|
||||
await fetch(domain.url[0].toString());
|
||||
|
||||
activeIp = domain.address[0];
|
||||
await dnsProvider(currentIp);
|
||||
|
||||
log.info(`Updated DynDNS hostname ${config.str("domain")} to ${activeIp}`);
|
||||
}
|
||||
|
@ -57,49 +56,6 @@ export async function start() {
|
|||
cron.schedule("0 * * * *", ipUpdate);
|
||||
}
|
||||
|
||||
async function getDomainInfo() {
|
||||
const relayDomain = config.str("domain");
|
||||
const parser = new Parser();
|
||||
|
||||
const url = new URL("https://freedns.afraid.org/api/");
|
||||
|
||||
const params = url.searchParams;
|
||||
|
||||
params.append("action", "getdyndns");
|
||||
params.append("v", "2");
|
||||
params.append("style", "xml");
|
||||
|
||||
const hash = createHash("sha1");
|
||||
hash.update(
|
||||
`${config.str("afraid-username")}|${config.str("afraid-password")}`
|
||||
);
|
||||
|
||||
params.append("sha", hash.digest().toString("hex"));
|
||||
|
||||
const response = await (await fetch(url.toString())).text();
|
||||
|
||||
if (/could not authenticate/i.test(response)) {
|
||||
errorExit("Failed to authenticate to afraid.org");
|
||||
}
|
||||
|
||||
const json = await parser.parseStringPromise(response);
|
||||
|
||||
let domain = null;
|
||||
|
||||
for (const item of json.xml.item) {
|
||||
if (item.host[0] === relayDomain) {
|
||||
domain = item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!domain) {
|
||||
errorExit(`Domain ${relayDomain} not found in afraid.org account`);
|
||||
}
|
||||
|
||||
return domain;
|
||||
}
|
||||
|
||||
async function getCurrentIp(): Promise<string> {
|
||||
return await (await fetch("http://ip1.dynupdate.no-ip.com/")).text();
|
||||
}
|
||||
|
|
40
src/file.ts
40
src/file.ts
|
@ -1,45 +1,15 @@
|
|||
import type { Ed25519Keypair, Err, progressiveFetchResult } from "libskynet";
|
||||
import type { Err, progressiveFetchResult } from "libskynet";
|
||||
// @ts-ignore
|
||||
import { SkynetClient } from "@skynetlabs/skynet-nodejs";
|
||||
import { dynImport } from "./util.js";
|
||||
import type {
|
||||
IndependentFileSmall,
|
||||
IndependentFileSmallMetadata,
|
||||
} from "@lumeweb/relay-types";
|
||||
|
||||
const ERR_EXISTS = "exists";
|
||||
const ERR_NOT_EXISTS = "DNE";
|
||||
const STD_FILENAME = "file";
|
||||
|
||||
type OverwriteDataFn = (newData: Uint8Array) => Promise<Err>;
|
||||
|
||||
type ReadDataFn = () => Promise<[Uint8Array, Err]>;
|
||||
|
||||
export interface IndependentFileSmallMetadata {
|
||||
largestHistoricSize: bigint;
|
||||
}
|
||||
|
||||
export interface IndependentFileSmall {
|
||||
dataKey: Uint8Array;
|
||||
fileData: Uint8Array;
|
||||
inode: string;
|
||||
keypair: Ed25519Keypair;
|
||||
metadata: IndependentFileSmallMetadata;
|
||||
revision: bigint;
|
||||
seed: Uint8Array;
|
||||
|
||||
skylink: string;
|
||||
viewKey: string;
|
||||
|
||||
overwriteData: OverwriteDataFn;
|
||||
|
||||
readData: ReadDataFn;
|
||||
}
|
||||
|
||||
interface IndependentFileSmallViewer {
|
||||
fileData: Uint8Array;
|
||||
skylink: string;
|
||||
viewKey: string;
|
||||
|
||||
readData: ReadDataFn;
|
||||
}
|
||||
|
||||
let addContextToErr: typeof import("libskynet").addContextToErr,
|
||||
blake2b: typeof import("libskynet").blake2b,
|
||||
bufToHex: typeof import("libskynet").bufToHex,
|
||||
|
|
|
@ -4,6 +4,23 @@ import type { PluginAPI, RPCMethod, Plugin } from "@lumeweb/relay-types";
|
|||
import slugify from "slugify";
|
||||
import * as fs from "fs";
|
||||
import path from "path";
|
||||
import {
|
||||
getSavedSsl,
|
||||
getSsl,
|
||||
getSslContext,
|
||||
saveSSl,
|
||||
setSsl,
|
||||
setSslContext,
|
||||
} from "./ssl.js";
|
||||
import log from "loglevel";
|
||||
import { getSeed } from "./util.js";
|
||||
import { getRouter, resetRouter, setRouter } from "./relay";
|
||||
import {
|
||||
createIndependentFileSmall,
|
||||
openIndependentFileSmall,
|
||||
overwriteIndependentFileSmall,
|
||||
} from "./file";
|
||||
import { setDnsProvider } from "./dns";
|
||||
|
||||
let pluginApi: PluginApiManager;
|
||||
|
||||
|
@ -64,6 +81,30 @@ export class PluginApiManager {
|
|||
},
|
||||
loadPlugin: getPluginAPI().loadPlugin.bind(getPluginAPI()),
|
||||
getMethods: getRpcServer().getMethods.bind(getRpcServer()),
|
||||
|
||||
ssl: {
|
||||
setContext: setSslContext,
|
||||
getContext: getSslContext,
|
||||
getSaved: getSavedSsl,
|
||||
set: setSsl,
|
||||
get: getSsl,
|
||||
save: saveSSl,
|
||||
},
|
||||
files: {
|
||||
createIndependentFileSmall,
|
||||
openIndependentFileSmall,
|
||||
overwriteIndependentFileSmall,
|
||||
},
|
||||
dns: {
|
||||
setProvider: setDnsProvider,
|
||||
},
|
||||
logger: log,
|
||||
getSeed,
|
||||
appRouter: {
|
||||
get: getRouter,
|
||||
set: setRouter,
|
||||
reset: resetRouter,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
267
src/relay.ts
267
src/relay.ts
|
@ -8,44 +8,31 @@ import express, { Express } from "express";
|
|||
import config from "./config.js";
|
||||
import * as http from "http";
|
||||
import * as https from "https";
|
||||
import * as tls from "tls";
|
||||
import * as acme from "acme-client";
|
||||
import { Buffer } from "buffer";
|
||||
import { intervalToDuration } from "date-fns";
|
||||
import cron from "node-cron";
|
||||
import { get as getDHT } from "./dht.js";
|
||||
import WS from "ws";
|
||||
// @ts-ignore
|
||||
import {
|
||||
createIndependentFileSmall,
|
||||
IndependentFileSmall,
|
||||
openIndependentFileSmall,
|
||||
overwriteIndependentFileSmall,
|
||||
} from "./file.js";
|
||||
import log from "loglevel";
|
||||
import { AddressInfo } from "net";
|
||||
import { sprintf } from "sprintf-js";
|
||||
import { dynImport } from "./util.js";
|
||||
// @ts-ignore
|
||||
import promiseRetry from "promise-retry";
|
||||
|
||||
let sslCtx: tls.SecureContext = tls.createSecureContext();
|
||||
const sslParams: tls.SecureContextOptions = { cert: "", key: "" };
|
||||
|
||||
let acmeClient: acme.Client;
|
||||
import { getSslContext } from "./ssl.js";
|
||||
|
||||
let app: Express;
|
||||
let router = express.Router();
|
||||
|
||||
let seedPhraseToSeed: typeof import("libskynet").seedPhraseToSeed;
|
||||
export function getRouter(): express.Router {
|
||||
return router;
|
||||
}
|
||||
|
||||
const FILE_CERT_NAME = "/lumeweb/relay/ssl.crt";
|
||||
const FILE_KEY_NAME = "/lumeweb/relay/ssl.key";
|
||||
const FILE_ACCOUNT_KEY_NAME = "/lumeweb/relay/account.key";
|
||||
export function setRouter(newRouter: express.Router): void {
|
||||
router = newRouter;
|
||||
}
|
||||
|
||||
export function resetRouter(): void {
|
||||
setRouter(express.Router());
|
||||
}
|
||||
|
||||
export async function start() {
|
||||
seedPhraseToSeed = (await dynImport("libskynet")).seedPhraseToSeed;
|
||||
|
||||
const relayPort = config.uint("port");
|
||||
app = express();
|
||||
app.use(function (req, res, next) {
|
||||
|
@ -82,23 +69,27 @@ export async function start() {
|
|||
});
|
||||
});
|
||||
|
||||
await setupSSl(true);
|
||||
let relayServer: https.Server | http.Server;
|
||||
|
||||
let httpsServer = https.createServer({
|
||||
if (config.bool("ssl")) {
|
||||
relayServer = https.createServer({
|
||||
SNICallback(servername, cb) {
|
||||
cb(null, sslCtx);
|
||||
cb(null, getSslContext());
|
||||
},
|
||||
});
|
||||
} else {
|
||||
relayServer = http.createServer();
|
||||
}
|
||||
|
||||
let wsServer = new WS.Server({ server: httpsServer });
|
||||
let wsServer = new WS.Server({ server: relayServer });
|
||||
|
||||
wsServer.on("connection", (socket: any) => {
|
||||
relay(dht, new Stream(false, socket));
|
||||
});
|
||||
|
||||
await new Promise((resolve) => {
|
||||
httpsServer.listen(relayPort, "0.0.0.0", function () {
|
||||
const address = httpsServer.address() as AddressInfo;
|
||||
relayServer.listen(relayPort, "0.0.0.0", function () {
|
||||
const address = relayServer.address() as AddressInfo;
|
||||
log.info(
|
||||
"DHT Relay Server started on ",
|
||||
`${address.address}:${address.port}`
|
||||
|
@ -106,220 +97,4 @@ export async function start() {
|
|||
resolve(null);
|
||||
});
|
||||
});
|
||||
|
||||
cron.schedule("0 * * * *", setupSSl.bind(null, false));
|
||||
}
|
||||
|
||||
async function setupSSl(bootup: boolean) {
|
||||
let sslCert: IndependentFileSmall | boolean = false;
|
||||
let sslKey: IndependentFileSmall | boolean = false;
|
||||
let certInfo;
|
||||
let exists = false;
|
||||
let domainValid = false;
|
||||
let dateValid = false;
|
||||
let configDomain = config.str("domain");
|
||||
|
||||
let retryOptions = bootup ? {} : { retry: 0 };
|
||||
|
||||
try {
|
||||
await promiseRetry(async (retry: any) => {
|
||||
sslCert = await getSslCert();
|
||||
if (!sslCert) {
|
||||
retry();
|
||||
}
|
||||
}, retryOptions);
|
||||
|
||||
await promiseRetry(async (retry: any) => {
|
||||
sslKey = await getSslKey();
|
||||
if (!sslKey) {
|
||||
retry();
|
||||
}
|
||||
}, retryOptions);
|
||||
} catch {}
|
||||
|
||||
if (sslCert && sslKey) {
|
||||
sslParams.cert = Buffer.from((sslCert as IndependentFileSmall).fileData);
|
||||
sslParams.key = Buffer.from((sslKey as IndependentFileSmall).fileData);
|
||||
certInfo = await getCertInfo();
|
||||
exists = true;
|
||||
}
|
||||
|
||||
if (exists) {
|
||||
const expires = certInfo?.notAfter as Date;
|
||||
let duration = intervalToDuration({ start: new Date(), end: expires });
|
||||
let daysLeft = (duration.months as number) * 30 + (duration.days as number);
|
||||
|
||||
if (daysLeft > 30) {
|
||||
dateValid = true;
|
||||
}
|
||||
|
||||
if (certInfo?.domains.commonName === configDomain) {
|
||||
domainValid = true;
|
||||
}
|
||||
|
||||
if (
|
||||
Boolean(isSSlStaging()) !==
|
||||
Boolean(certInfo?.issuer.commonName.toLowerCase().includes("staging"))
|
||||
) {
|
||||
domainValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (dateValid && domainValid) {
|
||||
if (bootup) {
|
||||
sslCtx = tls.createSecureContext(sslParams);
|
||||
log.info(`Loaded SSL Certificate for ${configDomain}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
await createOrRenewSSl(
|
||||
sslCert as unknown as IndependentFileSmall,
|
||||
sslKey as unknown as IndependentFileSmall
|
||||
);
|
||||
}
|
||||
|
||||
async function createOrRenewSSl(
|
||||
oldCert?: IndependentFileSmall,
|
||||
oldKey?: IndependentFileSmall
|
||||
) {
|
||||
const existing = oldCert && oldKey;
|
||||
|
||||
log.info(
|
||||
sprintf(
|
||||
"%s SSL Certificate for %s",
|
||||
existing ? "Renewing" : "Creating",
|
||||
config.str("domain")
|
||||
)
|
||||
);
|
||||
|
||||
let accountKey: boolean | IndependentFileSmall | Buffer = await getSslFile(
|
||||
FILE_ACCOUNT_KEY_NAME
|
||||
);
|
||||
|
||||
if (accountKey) {
|
||||
accountKey = Buffer.from((accountKey as IndependentFileSmall).fileData);
|
||||
}
|
||||
|
||||
if (!accountKey) {
|
||||
accountKey = await acme.forge.createPrivateKey();
|
||||
await createIndependentFileSmall(
|
||||
getSeed(),
|
||||
FILE_ACCOUNT_KEY_NAME,
|
||||
accountKey
|
||||
);
|
||||
}
|
||||
|
||||
acmeClient = new acme.Client({
|
||||
accountKey: accountKey as Buffer,
|
||||
directoryUrl: isSSlStaging()
|
||||
? acme.directory.letsencrypt.staging
|
||||
: acme.directory.letsencrypt.production,
|
||||
});
|
||||
|
||||
const [certificateKey, certificateRequest] = await acme.forge.createCsr({
|
||||
commonName: config.str("domain"),
|
||||
});
|
||||
try {
|
||||
sslParams.cert = await acmeClient.auto({
|
||||
csr: certificateRequest,
|
||||
termsOfServiceAgreed: true,
|
||||
challengeCreateFn: async (authz, challenge, keyAuthorization) => {
|
||||
router.get(
|
||||
`/.well-known/acme-challenge/${challenge.token}`,
|
||||
(req, res) => {
|
||||
res.send(keyAuthorization);
|
||||
}
|
||||
);
|
||||
},
|
||||
challengeRemoveFn: async () => {
|
||||
router = express.Router();
|
||||
},
|
||||
challengePriority: ["http-01"],
|
||||
});
|
||||
sslParams.cert = Buffer.from(sslParams.cert);
|
||||
} catch (e: any) {
|
||||
console.error((e as Error).message);
|
||||
process.exit(1);
|
||||
}
|
||||
sslParams.key = certificateKey;
|
||||
sslCtx = tls.createSecureContext(sslParams);
|
||||
|
||||
await saveSsl(oldCert, oldKey);
|
||||
}
|
||||
|
||||
async function saveSsl(
|
||||
oldCert?: IndependentFileSmall,
|
||||
oldKey?: IndependentFileSmall
|
||||
): Promise<void> {
|
||||
const seed = getSeed();
|
||||
|
||||
log.info(`Saving SSL Certificate for ${config.str("domain")}`);
|
||||
|
||||
if (oldCert) {
|
||||
await overwriteIndependentFileSmall(
|
||||
oldCert,
|
||||
Buffer.from(sslParams.cert as any)
|
||||
);
|
||||
} else {
|
||||
await createIndependentFileSmall(
|
||||
seed,
|
||||
FILE_CERT_NAME,
|
||||
Buffer.from(sslParams.cert as any)
|
||||
);
|
||||
}
|
||||
|
||||
if (oldKey) {
|
||||
await overwriteIndependentFileSmall(
|
||||
oldKey,
|
||||
Buffer.from(sslParams.key as any)
|
||||
);
|
||||
} else {
|
||||
await createIndependentFileSmall(
|
||||
seed,
|
||||
FILE_KEY_NAME,
|
||||
Buffer.from(sslParams.key as any)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function getCertInfo() {
|
||||
return acme.forge.readCertificateInfo(sslParams.cert as Buffer);
|
||||
}
|
||||
|
||||
async function getSslCert(): Promise<IndependentFileSmall | boolean> {
|
||||
return getSslFile(FILE_CERT_NAME);
|
||||
}
|
||||
|
||||
async function getSslKey(): Promise<IndependentFileSmall | boolean> {
|
||||
return getSslFile(FILE_KEY_NAME);
|
||||
}
|
||||
|
||||
async function getSslFile(
|
||||
name: string
|
||||
): Promise<IndependentFileSmall | boolean> {
|
||||
let seed = getSeed();
|
||||
|
||||
let [file, err] = await openIndependentFileSmall(seed, name);
|
||||
|
||||
if (err) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
function getSeed(): Uint8Array {
|
||||
let [seed, err] = seedPhraseToSeed(config.str("seed"));
|
||||
|
||||
if (err) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
return seed;
|
||||
}
|
||||
|
||||
function isSSlStaging() {
|
||||
return config.str("ssl-mode") === "staging";
|
||||
}
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
import tls from "tls";
|
||||
import {
|
||||
createIndependentFileSmall,
|
||||
openIndependentFileSmall,
|
||||
overwriteIndependentFileSmall,
|
||||
} from "./file.js";
|
||||
// @ts-ignore
|
||||
import promiseRetry from "promise-retry";
|
||||
import config from "./config.js";
|
||||
import log from "loglevel";
|
||||
import { getSeed } from "./util.js";
|
||||
import type {
|
||||
IndependentFileSmall,
|
||||
SavedSslData,
|
||||
SslData,
|
||||
} from "@lumeweb/relay-types";
|
||||
|
||||
let sslCtx: tls.SecureContext = tls.createSecureContext();
|
||||
let sslObject: SslData = {};
|
||||
|
||||
const FILE_CERT_NAME = "/lumeweb/relay/ssl.crt";
|
||||
const FILE_KEY_NAME = "/lumeweb/relay/ssl.key";
|
||||
|
||||
export function setSslContext(context: tls.SecureContext) {
|
||||
sslCtx = context;
|
||||
}
|
||||
|
||||
export function getSslContext(): tls.SecureContext {
|
||||
return sslCtx;
|
||||
}
|
||||
|
||||
export function setSsl(
|
||||
cert: IndependentFileSmall | Uint8Array,
|
||||
key: IndependentFileSmall | Uint8Array
|
||||
): void {
|
||||
cert = (cert as IndependentFileSmall)?.fileData || cert;
|
||||
key = (key as IndependentFileSmall)?.fileData || cert;
|
||||
sslObject.cert = cert as Uint8Array;
|
||||
sslObject.key = key as Uint8Array;
|
||||
setSslContext(
|
||||
tls.createSecureContext({
|
||||
cert: Buffer.from(cert),
|
||||
key: Buffer.from(key),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function getSsl(): SslData {
|
||||
return sslObject;
|
||||
}
|
||||
|
||||
export async function saveSSl(): Promise<void> {
|
||||
const seed = getSeed();
|
||||
|
||||
log.info(`Saving SSL Certificate for ${config.str("domain")}`);
|
||||
|
||||
let oldCert = await getSslCert();
|
||||
let cert: any = getSsl()?.cert;
|
||||
cert = cert?.fileData;
|
||||
if (oldCert) {
|
||||
await overwriteIndependentFileSmall(
|
||||
oldCert as IndependentFileSmall,
|
||||
Buffer.from(cert)
|
||||
);
|
||||
} else {
|
||||
await createIndependentFileSmall(seed, FILE_CERT_NAME, Buffer.from(cert));
|
||||
}
|
||||
|
||||
let oldKey = await getSslKey();
|
||||
let key: any = getSsl()?.cert;
|
||||
key = key?.fileData;
|
||||
|
||||
if (oldKey) {
|
||||
await overwriteIndependentFileSmall(
|
||||
oldKey as IndependentFileSmall,
|
||||
Buffer.from(key)
|
||||
);
|
||||
} else {
|
||||
await createIndependentFileSmall(seed, FILE_KEY_NAME, Buffer.from(key));
|
||||
}
|
||||
}
|
||||
|
||||
export async function getSavedSsl(
|
||||
retry = true
|
||||
): Promise<boolean | SavedSslData> {
|
||||
let retryOptions = retry ? {} : { retry: 0 };
|
||||
let sslCert: IndependentFileSmall | boolean = false;
|
||||
let sslKey: IndependentFileSmall | boolean = false;
|
||||
|
||||
try {
|
||||
await promiseRetry(async (retry: any) => {
|
||||
sslCert = await getSslCert();
|
||||
if (!sslCert) {
|
||||
retry();
|
||||
}
|
||||
}, retryOptions);
|
||||
|
||||
await promiseRetry(async (retry: any) => {
|
||||
sslKey = await getSslKey();
|
||||
if (!sslKey) {
|
||||
retry();
|
||||
}
|
||||
}, retryOptions);
|
||||
} catch {}
|
||||
|
||||
if (!sslCert || !sslKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return {
|
||||
cert: sslCert as IndependentFileSmall,
|
||||
key: sslKey as IndependentFileSmall,
|
||||
};
|
||||
}
|
||||
|
||||
async function getSslCert(): Promise<IndependentFileSmall | boolean> {
|
||||
return getSslFile(FILE_CERT_NAME);
|
||||
}
|
||||
|
||||
async function getSslKey(): Promise<IndependentFileSmall | boolean> {
|
||||
return getSslFile(FILE_KEY_NAME);
|
||||
}
|
||||
|
||||
async function getSslFile(
|
||||
name: string
|
||||
): Promise<IndependentFileSmall | boolean> {
|
||||
let seed = getSeed();
|
||||
|
||||
let [file, err] = await openIndependentFileSmall(seed, name);
|
||||
|
||||
if (err) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
14
src/util.ts
14
src/util.ts
|
@ -1,3 +1,17 @@
|
|||
import config from "./config";
|
||||
import { seedPhraseToSeed } from "libskynet";
|
||||
|
||||
export function dynImport(module: string) {
|
||||
return Function(`return import("${module}")`)() as Promise<any>;
|
||||
}
|
||||
|
||||
export function getSeed(): Uint8Array {
|
||||
let [seed, err] = seedPhraseToSeed(config.str("seed"));
|
||||
|
||||
if (err) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
return seed;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue