Compare commits
No commits in common. "develop" and "master" have entirely different histories.
|
@ -1,13 +0,0 @@
|
||||||
name: Build/Publish
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
- develop
|
|
||||||
- develop-*
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
main:
|
|
||||||
uses: lumeweb/github-node-deploy-workflow/.github/workflows/main.yml@master
|
|
||||||
secrets: inherit
|
|
|
@ -1,35 +0,0 @@
|
||||||
{
|
|
||||||
"preset": [
|
|
||||||
"@lumeweb/presetter-kernel-module-preset"
|
|
||||||
],
|
|
||||||
"config": {
|
|
||||||
"browser": true,
|
|
||||||
"vite": {
|
|
||||||
"build": {
|
|
||||||
"copyPublicDir": false,
|
|
||||||
"lib": {
|
|
||||||
"entry": "{source}/tester.js",
|
|
||||||
"fileName": "tester",
|
|
||||||
"formats": {
|
|
||||||
"0": "umd"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"release": {
|
|
||||||
"plugins": {
|
|
||||||
"3": [
|
|
||||||
"@semantic-release/npm",
|
|
||||||
{
|
|
||||||
"npmPublish": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"build:vite": "vite build",
|
|
||||||
"build:bin": "shx mv lib/tester.umd.cjs public/tester.js && shx mkdir -p bin && shx mv build/sandbox* bin/",
|
|
||||||
"build:lib": "shx mv build/* lib/ && shx rm lib/tester*"
|
|
||||||
}
|
|
||||||
}
|
|
31
CHANGELOG.md
31
CHANGELOG.md
|
@ -1,31 +0,0 @@
|
||||||
## [0.0.2-develop.6](https://git.lumeweb.com/LumeWeb/kernel-sandbox/compare/v0.0.2-develop.5...v0.0.2-develop.6) (2023-07-21)
|
|
||||||
|
|
||||||
## [0.0.2-develop.5](https://git.lumeweb.com/LumeWeb/kernel-sandbox/compare/v0.0.2-develop.4...v0.0.2-develop.5) (2023-07-21)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* switch to https ([bb54af0](https://git.lumeweb.com/LumeWeb/kernel-sandbox/commit/bb54af0d37e5bba25190123ec88bd2871cd047c8))
|
|
||||||
|
|
||||||
## [0.0.2-develop.4](https://git.lumeweb.com/LumeWeb/kernel-sandbox/compare/v0.0.2-develop.3...v0.0.2-develop.4) (2023-07-21)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* update import path ([b17db64](https://git.lumeweb.com/LumeWeb/kernel-sandbox/commit/b17db64a70bfe2248faec21b4b67b10772e1b917))
|
|
||||||
|
|
||||||
## [0.0.2-develop.3](https://git.lumeweb.com/LumeWeb/kernel-sandbox/compare/v0.0.2-develop.2...v0.0.2-develop.3) (2023-07-21)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* set the key to "key" ([8e93f8f](https://git.lumeweb.com/LumeWeb/kernel-sandbox/commit/8e93f8f53b643bc069f8fa9db4b74f5b242b9158))
|
|
||||||
|
|
||||||
## [0.0.2-develop.2](https://git.lumeweb.com/LumeWeb/kernel-sandbox/compare/v0.0.2-develop.1...v0.0.2-develop.2) (2023-07-21)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* trigger release ([86727b7](https://git.lumeweb.com/LumeWeb/kernel-sandbox/commit/86727b745a22dbe055afebc7dab144a49721cb1b))
|
|
||||||
|
|
||||||
## [0.0.2-develop.1](https://git.lumeweb.com/LumeWeb/kernel-sandbox/compare/v0.0.1...v0.0.2-develop.1) (2023-07-21)
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
export {};
|
|
@ -0,0 +1,19 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
// @ts-ignore
|
||||||
|
import { loadTester, login } from "../dist/index.js";
|
||||||
|
import puppeteer, { ProtocolError } from "puppeteer";
|
||||||
|
let browser;
|
||||||
|
(async () => {
|
||||||
|
browser = await puppeteer.launch({ headless: false, devtools: true });
|
||||||
|
const page = (await browser.pages()).pop();
|
||||||
|
await login(page);
|
||||||
|
await loadTester(page);
|
||||||
|
})();
|
||||||
|
process.on("SIGTERM", async () => {
|
||||||
|
await browser.close();
|
||||||
|
});
|
||||||
|
process.on("uncaughtException", (e) => {
|
||||||
|
if (!(e instanceof ProtocolError)) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { Page } from "puppeteer";
|
||||||
|
import { errTuple } from "libskynet";
|
||||||
|
export declare const KERNEL_TEST_SUITE = "AQCPJ9WRzMpKQHIsPo8no3XJpUydcDCjw7VJy8lG1MCZ3g";
|
||||||
|
export declare const KERNEL_HELPER_MODULE = "AQCoaLP6JexdZshDDZRQaIwN3B7DqFjlY7byMikR7u1IEA";
|
||||||
|
export declare const TEST_KERNEL_SKLINK = "AQDJDoXMJiiEMBxXodQvUV89qtQHsnXWyV1ViQ9M1pMjUg";
|
||||||
|
export declare function generateSeedPhrase(): string;
|
||||||
|
export declare function login(page: Page, seed?: string): Promise<void>;
|
||||||
|
export declare function loadTester(page: Page, port?: number): Promise<void>;
|
||||||
|
declare class Tester {
|
||||||
|
private page;
|
||||||
|
constructor(page: Page);
|
||||||
|
callModule(id: string, method: string, data?: {}): Promise<errTuple>;
|
||||||
|
}
|
||||||
|
export declare const tester: (page: Page) => Tester;
|
||||||
|
export {};
|
|
@ -0,0 +1,129 @@
|
||||||
|
import { b64ToBuf, bufToHex, deriveChildSeed, dictionary, seedPhraseToSeed, taggedRegistryEntryKeys, } from "libskynet";
|
||||||
|
import { SEED_BYTES, seedToChecksumWords } from "libskynet/dist/seed.js";
|
||||||
|
import { DICTIONARY_UNIQUE_PREFIX } from "libskynet/dist/dictionary.js";
|
||||||
|
import * as path from "path";
|
||||||
|
import { overwriteRegistryEntry } from "libskynetnode";
|
||||||
|
import * as kernel from "libkernel";
|
||||||
|
import { webcrypto } from "crypto";
|
||||||
|
// @ts-ignore
|
||||||
|
import StaticServer from "static-server";
|
||||||
|
import * as url from "url";
|
||||||
|
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
|
||||||
|
export const KERNEL_TEST_SUITE = "AQCPJ9WRzMpKQHIsPo8no3XJpUydcDCjw7VJy8lG1MCZ3g";
|
||||||
|
export const KERNEL_HELPER_MODULE = "AQCoaLP6JexdZshDDZRQaIwN3B7DqFjlY7byMikR7u1IEA";
|
||||||
|
export const TEST_KERNEL_SKLINK = "AQDJDoXMJiiEMBxXodQvUV89qtQHsnXWyV1ViQ9M1pMjUg";
|
||||||
|
const SEED_ENTROPY_WORDS = 13;
|
||||||
|
const crypto = webcrypto;
|
||||||
|
export function generateSeedPhrase() {
|
||||||
|
// Get the random numbers for the seed phrase. Typically, you need to
|
||||||
|
// have code that avoids bias by checking the random results and
|
||||||
|
// re-rolling the random numbers if the result is outside of the range
|
||||||
|
// of numbers that would produce no bias. Because the search space
|
||||||
|
// (1024) evenly divides the random number space (2^16), we can skip
|
||||||
|
// this step and just use a modulus instead. The result will have no
|
||||||
|
// bias, but only because the search space is a power of 2.
|
||||||
|
let randNums = new Uint16Array(SEED_ENTROPY_WORDS);
|
||||||
|
crypto.getRandomValues(randNums);
|
||||||
|
// Generate the seed phrase from the randNums.
|
||||||
|
let seedWords = [];
|
||||||
|
for (let i = 0; i < SEED_ENTROPY_WORDS; i++) {
|
||||||
|
let wordIndex = randNums[i] % dictionary.length;
|
||||||
|
seedWords.push(dictionary[wordIndex]);
|
||||||
|
}
|
||||||
|
// Convert the seedWords to a seed.
|
||||||
|
let [seed] = seedWordsToSeed(seedWords);
|
||||||
|
// Compute the checksum.
|
||||||
|
let [checksumOne, checksumTwo, err2] = seedToChecksumWords(seed);
|
||||||
|
// Assemble the final seed phrase and set the text field.
|
||||||
|
return [...seedWords, checksumOne, checksumTwo].join(" ");
|
||||||
|
}
|
||||||
|
function seedWordsToSeed(seedWords) {
|
||||||
|
// Input checking.
|
||||||
|
if (seedWords.length !== SEED_ENTROPY_WORDS) {
|
||||||
|
return [
|
||||||
|
new Uint8Array(0),
|
||||||
|
`Seed words should have length ${SEED_ENTROPY_WORDS} but has length ${seedWords.length}`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
// We are getting 16 bytes of entropy.
|
||||||
|
let bytes = new Uint8Array(SEED_BYTES);
|
||||||
|
let curByte = 0;
|
||||||
|
let curBit = 0;
|
||||||
|
for (let i = 0; i < SEED_ENTROPY_WORDS; i++) {
|
||||||
|
// Determine which number corresponds to the next word.
|
||||||
|
let word = -1;
|
||||||
|
for (let j = 0; j < dictionary.length; j++) {
|
||||||
|
if (seedWords[i].slice(0, DICTIONARY_UNIQUE_PREFIX) ===
|
||||||
|
dictionary[j].slice(0, DICTIONARY_UNIQUE_PREFIX)) {
|
||||||
|
word = j;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (word === -1) {
|
||||||
|
return [
|
||||||
|
new Uint8Array(0),
|
||||||
|
`word '${seedWords[i]}' at index ${i} not found in dictionary`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
let wordBits = 10;
|
||||||
|
if (i === SEED_ENTROPY_WORDS - 1) {
|
||||||
|
wordBits = 8;
|
||||||
|
}
|
||||||
|
// Iterate over the bits of the 10- or 8-bit word.
|
||||||
|
for (let j = 0; j < wordBits; j++) {
|
||||||
|
let bitSet = (word & (1 << (wordBits - j - 1))) > 0;
|
||||||
|
if (bitSet) {
|
||||||
|
bytes[curByte] |= 1 << (8 - curBit - 1);
|
||||||
|
}
|
||||||
|
curBit += 1;
|
||||||
|
if (curBit >= 8) {
|
||||||
|
// Current byte has 8 bits, go to the next byte.
|
||||||
|
curByte += 1;
|
||||||
|
curBit = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [bytes, null];
|
||||||
|
}
|
||||||
|
export async function login(page, seed = generateSeedPhrase()) {
|
||||||
|
await page.goto("http://skt.us");
|
||||||
|
let userSeed;
|
||||||
|
[userSeed] = seedPhraseToSeed(seed);
|
||||||
|
let seedHex = bufToHex(userSeed);
|
||||||
|
await page.evaluate((seed) => {
|
||||||
|
window.localStorage.setItem("v1-seed", seed);
|
||||||
|
}, seedHex);
|
||||||
|
let kernelEntrySeed = deriveChildSeed(userSeed, "userPreferredKernel2");
|
||||||
|
// Get the registry keys.
|
||||||
|
let [keypair, dataKey] = taggedRegistryEntryKeys(kernelEntrySeed, "user kernel");
|
||||||
|
await overwriteRegistryEntry(keypair, dataKey, b64ToBuf(TEST_KERNEL_SKLINK)[0]);
|
||||||
|
}
|
||||||
|
export async function loadTester(page, port = 8080) {
|
||||||
|
const server = new StaticServer({
|
||||||
|
rootPath: path.resolve(__dirname, "..", "public"),
|
||||||
|
port,
|
||||||
|
host: "localhost",
|
||||||
|
});
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
server.start(resolve);
|
||||||
|
});
|
||||||
|
const stop = () => server.stop();
|
||||||
|
process.on("SIGTERM", stop);
|
||||||
|
page.browser().on("disconnected", stop);
|
||||||
|
await page.goto(`http://localhost:${port}/`);
|
||||||
|
await page.evaluate(() => {
|
||||||
|
return kernel.init();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
class Tester {
|
||||||
|
page;
|
||||||
|
constructor(page) {
|
||||||
|
this.page = page;
|
||||||
|
}
|
||||||
|
async callModule(id, method, data = {}) {
|
||||||
|
return this.page.evaluate(async (id, method, data) => {
|
||||||
|
return kernel.callModule(id, method, data);
|
||||||
|
}, id, method, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const tester = (page) => new Tester(page);
|
File diff suppressed because it is too large
Load Diff
48
package.json
48
package.json
|
@ -1,38 +1,30 @@
|
||||||
{
|
{
|
||||||
"name": "@lumeweb/kernel-sandbox",
|
"name": "@lumeweb/kernel-tester",
|
||||||
"version": "0.0.2-develop.6",
|
"version": "0.1.0",
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "gitea@git.lumeweb.com:LumeWeb/kernel-sandbox.git"
|
|
||||||
},
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepare": "presetter bootstrap",
|
"build": "tsc && rollup -c && mv dist/tester* public && mkdir -p bin && mv build/sandbox* bin/ && mv build/index* dist && rm -f dist/tester.js && rm -rf build/"
|
||||||
"build": "run build build:bin build:lib",
|
|
||||||
"semantic-release": "semantic-release"
|
|
||||||
},
|
},
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": {
|
"dependencies": {
|
||||||
"kernel-sandbox": "bin/sandbox.js"
|
"libkernel": "^0.1.41",
|
||||||
|
"libskynet": "^0.0.48",
|
||||||
|
"libskynetnode": "^0.1.3",
|
||||||
|
"static-server": "^2.2.1",
|
||||||
|
"puppeteer": "^15.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@lumeweb/presetter-kernel-module-preset": "^0.1.0-develop.43",
|
"@rollup/plugin-commonjs": "^22.0.1",
|
||||||
"@rollup/plugin-wasm": "^6.1.3",
|
"@rollup/plugin-json": "^4.1.0",
|
||||||
"presetter": "*"
|
"@rollup/plugin-node-resolve": "^13.3.0",
|
||||||
|
"@types/node": "^18.0.0",
|
||||||
|
"@types/ws": "^8.5.3",
|
||||||
|
"prettier": "^2.7.1",
|
||||||
|
"rollup": "^2.75.7",
|
||||||
|
"rollup-plugin-polyfill-node": "^0.9.0",
|
||||||
|
"typescript": "^4.5"
|
||||||
},
|
},
|
||||||
"readme": "ERROR: No README data found!",
|
"bin": {
|
||||||
"dependencies": {
|
"kernel-sandbox": "bin/sandbox.js"
|
||||||
"@lumeweb/libkernel": "^0.1.0-develop.23",
|
|
||||||
"@noble/ciphers": "^0.1.4",
|
|
||||||
"p-defer": "^4.0.0",
|
|
||||||
"puppeteer": "^20.7.4",
|
|
||||||
"static-server": "^2.2.1"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"public",
|
|
||||||
"lib"
|
|
||||||
],
|
|
||||||
"publishConfig": {
|
|
||||||
"access": "public"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export {};
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,15 @@
|
||||||
|
import resolve from "@rollup/plugin-node-resolve";
|
||||||
|
import commonjs from "@rollup/plugin-commonjs";
|
||||||
|
import json from "@rollup/plugin-json";
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
input: "build/tester.js",
|
||||||
|
output: {
|
||||||
|
file: "dist/tester.js",
|
||||||
|
format: "iife",
|
||||||
|
},
|
||||||
|
plugins: [resolve(), commonjs(), json()],
|
||||||
|
inlineDynamicImports: true,
|
||||||
|
},
|
||||||
|
];
|
157
src/index.ts
157
src/index.ts
|
@ -1,14 +1,138 @@
|
||||||
|
import {
|
||||||
|
b64ToBuf,
|
||||||
|
bufToHex,
|
||||||
|
deriveChildSeed,
|
||||||
|
dictionary,
|
||||||
|
seedPhraseToSeed,
|
||||||
|
taggedRegistryEntryKeys,
|
||||||
|
} from "libskynet";
|
||||||
|
import { SEED_BYTES, seedToChecksumWords } from "libskynet/dist/seed.js";
|
||||||
|
import { DICTIONARY_UNIQUE_PREFIX } from "libskynet/dist/dictionary.js";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import * as kernel from "@lumeweb/libkernel/kernel";
|
import { overwriteRegistryEntry } from "libskynetnode";
|
||||||
|
import * as kernel from "libkernel";
|
||||||
|
import { webcrypto } from "crypto";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import StaticServer from "static-server";
|
import StaticServer from "static-server";
|
||||||
import { Page } from "puppeteer";
|
import { Page } from "puppeteer";
|
||||||
|
import { errTuple } from "libskynet";
|
||||||
|
|
||||||
import * as url from "url";
|
import * as url from "url";
|
||||||
|
|
||||||
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
|
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
|
||||||
|
|
||||||
|
export const KERNEL_TEST_SUITE =
|
||||||
|
"AQCPJ9WRzMpKQHIsPo8no3XJpUydcDCjw7VJy8lG1MCZ3g";
|
||||||
|
export const KERNEL_HELPER_MODULE =
|
||||||
|
"AQCoaLP6JexdZshDDZRQaIwN3B7DqFjlY7byMikR7u1IEA";
|
||||||
|
export const TEST_KERNEL_SKLINK =
|
||||||
|
"AQDJDoXMJiiEMBxXodQvUV89qtQHsnXWyV1ViQ9M1pMjUg";
|
||||||
|
const SEED_ENTROPY_WORDS = 13;
|
||||||
|
const crypto = webcrypto as unknown as Crypto;
|
||||||
|
|
||||||
|
export function generateSeedPhrase() {
|
||||||
|
// Get the random numbers for the seed phrase. Typically, you need to
|
||||||
|
// have code that avoids bias by checking the random results and
|
||||||
|
// re-rolling the random numbers if the result is outside of the range
|
||||||
|
// of numbers that would produce no bias. Because the search space
|
||||||
|
// (1024) evenly divides the random number space (2^16), we can skip
|
||||||
|
// this step and just use a modulus instead. The result will have no
|
||||||
|
// bias, but only because the search space is a power of 2.
|
||||||
|
let randNums = new Uint16Array(SEED_ENTROPY_WORDS);
|
||||||
|
crypto.getRandomValues(randNums);
|
||||||
|
// Generate the seed phrase from the randNums.
|
||||||
|
let seedWords = [];
|
||||||
|
for (let i = 0; i < SEED_ENTROPY_WORDS; i++) {
|
||||||
|
let wordIndex = randNums[i] % dictionary.length;
|
||||||
|
seedWords.push(dictionary[wordIndex]);
|
||||||
|
}
|
||||||
|
// Convert the seedWords to a seed.
|
||||||
|
let [seed] = seedWordsToSeed(seedWords);
|
||||||
|
// Compute the checksum.
|
||||||
|
let [checksumOne, checksumTwo, err2] = seedToChecksumWords(
|
||||||
|
seed as Uint8Array
|
||||||
|
);
|
||||||
|
// Assemble the final seed phrase and set the text field.
|
||||||
|
return [...seedWords, checksumOne, checksumTwo].join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
function seedWordsToSeed(seedWords: string[]) {
|
||||||
|
// Input checking.
|
||||||
|
if (seedWords.length !== SEED_ENTROPY_WORDS) {
|
||||||
|
return [
|
||||||
|
new Uint8Array(0),
|
||||||
|
`Seed words should have length ${SEED_ENTROPY_WORDS} but has length ${seedWords.length}`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
// We are getting 16 bytes of entropy.
|
||||||
|
let bytes = new Uint8Array(SEED_BYTES);
|
||||||
|
let curByte = 0;
|
||||||
|
let curBit = 0;
|
||||||
|
for (let i = 0; i < SEED_ENTROPY_WORDS; i++) {
|
||||||
|
// Determine which number corresponds to the next word.
|
||||||
|
let word = -1;
|
||||||
|
for (let j = 0; j < dictionary.length; j++) {
|
||||||
|
if (
|
||||||
|
seedWords[i].slice(0, DICTIONARY_UNIQUE_PREFIX) ===
|
||||||
|
dictionary[j].slice(0, DICTIONARY_UNIQUE_PREFIX)
|
||||||
|
) {
|
||||||
|
word = j;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (word === -1) {
|
||||||
|
return [
|
||||||
|
new Uint8Array(0),
|
||||||
|
`word '${seedWords[i]}' at index ${i} not found in dictionary`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
let wordBits = 10;
|
||||||
|
if (i === SEED_ENTROPY_WORDS - 1) {
|
||||||
|
wordBits = 8;
|
||||||
|
}
|
||||||
|
// Iterate over the bits of the 10- or 8-bit word.
|
||||||
|
for (let j = 0; j < wordBits; j++) {
|
||||||
|
let bitSet = (word & (1 << (wordBits - j - 1))) > 0;
|
||||||
|
if (bitSet) {
|
||||||
|
bytes[curByte] |= 1 << (8 - curBit - 1);
|
||||||
|
}
|
||||||
|
curBit += 1;
|
||||||
|
if (curBit >= 8) {
|
||||||
|
// Current byte has 8 bits, go to the next byte.
|
||||||
|
curByte += 1;
|
||||||
|
curBit = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [bytes, null];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function login(page: Page, seed = generateSeedPhrase()) {
|
||||||
|
await page.goto("http://skt.us");
|
||||||
|
|
||||||
|
let userSeed: Uint8Array;
|
||||||
|
|
||||||
|
[userSeed] = seedPhraseToSeed(seed);
|
||||||
|
let seedHex = bufToHex(userSeed);
|
||||||
|
|
||||||
|
await page.evaluate((seed: string) => {
|
||||||
|
window.localStorage.setItem("v1-seed", seed);
|
||||||
|
}, seedHex);
|
||||||
|
|
||||||
|
let kernelEntrySeed = deriveChildSeed(userSeed, "userPreferredKernel2");
|
||||||
|
|
||||||
|
// Get the registry keys.
|
||||||
|
let [keypair, dataKey] = taggedRegistryEntryKeys(
|
||||||
|
kernelEntrySeed,
|
||||||
|
"user kernel"
|
||||||
|
);
|
||||||
|
|
||||||
|
await overwriteRegistryEntry(
|
||||||
|
keypair,
|
||||||
|
dataKey,
|
||||||
|
b64ToBuf(TEST_KERNEL_SKLINK)[0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export async function loadTester(page: Page, port = 8080) {
|
export async function loadTester(page: Page, port = 8080) {
|
||||||
const server = new StaticServer({
|
const server = new StaticServer({
|
||||||
rootPath: path.resolve(__dirname, "..", "public"),
|
rootPath: path.resolve(__dirname, "..", "public"),
|
||||||
|
@ -27,14 +151,25 @@ export async function loadTester(page: Page, port = 8080) {
|
||||||
await page.evaluate(() => {
|
await page.evaluate(() => {
|
||||||
return kernel.init();
|
return kernel.init();
|
||||||
});
|
});
|
||||||
await page.evaluate(() => {
|
|
||||||
// @ts-ignore
|
|
||||||
return window.main.loginRandom();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
declare function loginRandom(): Promise<any>;
|
|
||||||
declare global {
|
class Tester {
|
||||||
interface Window {
|
private page: Page;
|
||||||
loginRandom: typeof loginRandom;
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
this.page = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
async callModule(id: string, method: string, data = {}): Promise<errTuple> {
|
||||||
|
return this.page.evaluate(
|
||||||
|
async (id, method, data) => {
|
||||||
|
return kernel.callModule(id, method, data);
|
||||||
|
},
|
||||||
|
id,
|
||||||
|
method,
|
||||||
|
data
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const tester = (page: Page) => new Tester(page);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { loadTester } from "../lib/index.js";
|
import { loadTester, login } from "../dist/index.js";
|
||||||
|
|
||||||
import puppeteer, { Browser, Page, ProtocolError } from "puppeteer";
|
import puppeteer, { Browser, Page, ProtocolError } from "puppeteer";
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ let browser: Browser;
|
||||||
browser = await puppeteer.launch({ headless: false, devtools: true });
|
browser = await puppeteer.launch({ headless: false, devtools: true });
|
||||||
|
|
||||||
const page = (await browser.pages()).pop() as Page;
|
const page = (await browser.pages()).pop() as Page;
|
||||||
|
await login(page);
|
||||||
await loadTester(page);
|
await loadTester(page);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
150
src/tester.ts
150
src/tester.ts
|
@ -1,147 +1,7 @@
|
||||||
import * as kernel from "@lumeweb/libkernel/kernel";
|
import * as kernel from "libkernel";
|
||||||
import { x25519 } from "@noble/curves/ed25519";
|
import * as skynet from "libskynet";
|
||||||
import { bytesToHex, hexToBytes } from "@noble/curves/abstract/utils";
|
|
||||||
import defer from "p-defer";
|
|
||||||
import { randomBytes } from "@noble/hashes/utils";
|
|
||||||
import { secretbox } from "@noble/ciphers/salsa";
|
|
||||||
import {
|
|
||||||
addQuery,
|
|
||||||
deleteQuery,
|
|
||||||
getAuthStatus,
|
|
||||||
getAuthStatusDefer,
|
|
||||||
getAuthStatusKnown,
|
|
||||||
getLoggedInDefer,
|
|
||||||
getQueries,
|
|
||||||
getQueriesNonce,
|
|
||||||
getQuery,
|
|
||||||
increaseQueriesNonce,
|
|
||||||
resetLoggedInDefer,
|
|
||||||
setAuthStatus,
|
|
||||||
setAuthStatusKnown,
|
|
||||||
} from "./vars.js";
|
|
||||||
import { ed25519 } from "@lumeweb/libkernel";
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface Window {
|
|
||||||
kernel: typeof kernel;
|
|
||||||
login: typeof login;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
window.kernel = kernel;
|
window.kernel = kernel;
|
||||||
window.login = login;
|
// @ts-ignore
|
||||||
|
window.skynet = skynet;
|
||||||
window.addEventListener("message", (event) => {
|
|
||||||
const data = event.data?.data;
|
|
||||||
if (event.data.method === "log") {
|
|
||||||
if (data?.isErr === false) {
|
|
||||||
console.log(data.message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.error(data.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.data.method === "kernelAuthStatus") {
|
|
||||||
setAuthStatus(data);
|
|
||||||
if (!getAuthStatusKnown()) {
|
|
||||||
getAuthStatusDefer().resolve();
|
|
||||||
setAuthStatusKnown(true);
|
|
||||||
console.log("bootloader is now initialized");
|
|
||||||
if (!getAuthStatus().loginComplete) {
|
|
||||||
console.log("user is not logged in: waiting until login is confirmed");
|
|
||||||
} else {
|
|
||||||
getLoggedInDefer().resolve();
|
|
||||||
}
|
|
||||||
if (getAuthStatus().logoutComplete) {
|
|
||||||
resetLoggedInDefer();
|
|
||||||
setAuthStatusKnown(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(event.data.nonce in getQueries())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let receiveResult = getQuery(event.data.nonce);
|
|
||||||
if (event.data.method === "response") {
|
|
||||||
deleteQuery(event.data.nonce);
|
|
||||||
}
|
|
||||||
|
|
||||||
receiveResult(event.data);
|
|
||||||
});
|
|
||||||
|
|
||||||
function getKernelIframe() {
|
|
||||||
const iframes = Array.from(document.getElementsByTagName("iframe"));
|
|
||||||
|
|
||||||
if (!iframes.length) {
|
|
||||||
console.error("could not find kernel iframe");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return iframes[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function loginRandom() {
|
|
||||||
return login(ed25519.utils.randomPrivateKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function login(key: Uint8Array) {
|
|
||||||
let privKey = x25519.utils.randomPrivateKey();
|
|
||||||
|
|
||||||
const iframe = getKernelIframe();
|
|
||||||
|
|
||||||
if (!iframe) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let pubKey: string | Uint8Array = await queryKernel({
|
|
||||||
method: "exchangeCommunicationKeys",
|
|
||||||
data: bytesToHex(x25519.getPublicKey(privKey)),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!pubKey) {
|
|
||||||
alert(`Failed to login: could not get communication key`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pubKey = hexToBytes(pubKey as string);
|
|
||||||
|
|
||||||
const secret = x25519.getSharedSecret(privKey, pubKey);
|
|
||||||
const nonce = randomBytes(24);
|
|
||||||
const box = secretbox(secret, nonce);
|
|
||||||
const ciphertext = box.seal(key);
|
|
||||||
|
|
||||||
await queryKernel({
|
|
||||||
method: "setLoginKey",
|
|
||||||
data: {
|
|
||||||
data: bytesToHex(ciphertext),
|
|
||||||
nonce: bytesToHex(nonce),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function queryKernel(query: any): Promise<any> {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
let receiveResponse = function (data: any) {
|
|
||||||
resolve(data.data);
|
|
||||||
};
|
|
||||||
|
|
||||||
getAuthStatusDefer().promise.then(() => {
|
|
||||||
let nonce = getQueriesNonce();
|
|
||||||
increaseQueriesNonce();
|
|
||||||
query.nonce = nonce;
|
|
||||||
addQuery(nonce, receiveResponse);
|
|
||||||
if (getKernelIframe()?.contentWindow !== null) {
|
|
||||||
getKernelIframe()?.contentWindow?.postMessage(
|
|
||||||
query,
|
|
||||||
"https://kernel.lumeweb.com",
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
console.error(
|
|
||||||
"kernelFrame.contentWindow was null, cannot send message!",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
53
src/vars.ts
53
src/vars.ts
|
@ -1,53 +0,0 @@
|
||||||
import defer from "p-defer";
|
|
||||||
import { KernelAuthStatus } from "@lumeweb/libweb";
|
|
||||||
|
|
||||||
let authStatus: KernelAuthStatus;
|
|
||||||
let authStatusKnown = false;
|
|
||||||
let authStatusDefer = defer();
|
|
||||||
let queriesNonce = 1;
|
|
||||||
let queries: any = {};
|
|
||||||
let loggedInDefer = defer();
|
|
||||||
|
|
||||||
export function getAuthStatusKnown() {
|
|
||||||
return authStatusKnown;
|
|
||||||
}
|
|
||||||
export function setAuthStatusKnown(status: boolean) {
|
|
||||||
authStatusKnown = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getAuthStatus(): KernelAuthStatus {
|
|
||||||
return authStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setAuthStatus(status: KernelAuthStatus) {
|
|
||||||
authStatus = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getAuthStatusDefer() {
|
|
||||||
return authStatusDefer;
|
|
||||||
}
|
|
||||||
export function getQueriesNonce(): number {
|
|
||||||
return queriesNonce;
|
|
||||||
}
|
|
||||||
export function getQueries() {
|
|
||||||
return queries;
|
|
||||||
}
|
|
||||||
export function deleteQuery(nonce: any) {
|
|
||||||
delete queries[nonce];
|
|
||||||
}
|
|
||||||
export function getQuery(nonce: any) {
|
|
||||||
return queries[nonce];
|
|
||||||
}
|
|
||||||
export function increaseQueriesNonce() {
|
|
||||||
queriesNonce++;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function addQuery(nonce: any, func: Function) {
|
|
||||||
queries[nonce] = func;
|
|
||||||
}
|
|
||||||
export function getLoggedInDefer() {
|
|
||||||
return loggedInDefer;
|
|
||||||
}
|
|
||||||
export function resetLoggedInDefer() {
|
|
||||||
loggedInDefer = defer();
|
|
||||||
}
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "esnext",
|
||||||
|
"module": "esnext",
|
||||||
|
"declaration": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"outDir": "./build",
|
||||||
|
"strict": true,
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src",
|
||||||
|
],
|
||||||
|
"exclude": ["node_modules", "**/__tests__/*"]
|
||||||
|
}
|
Loading…
Reference in New Issue