Skip to content

Commit

Permalink
Merge pull request #18 from sodazone/feature-browser-support
Browse files Browse the repository at this point in the history
Improve light client support
  • Loading branch information
mfornos committed May 6, 2024
2 parents e52c170 + 9af50f3 commit bbf65c2
Show file tree
Hide file tree
Showing 19 changed files with 557 additions and 198 deletions.
2 changes: 1 addition & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,5 @@ Blocks: 100 (tx: 364, events: 4931)
These examples serve as practical illustrations of how to utilize the Ocelloids SDK and can be customized to fit your specific use cases.

If you prefer to run TypeScript `main.ts` files directly, either for development or quick testing without the need for building,
you have the option to use either [ts-node-esm](https://github.com/TypeStrong/ts-node) or [Bun](https://bun.sh/).
you have the option to use either [tsx](https://github.com/privatenumber/tsx) or [Bun](https://bun.sh/).

2 changes: 1 addition & 1 deletion examples/filter-extrinsics/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env ts-node-esm
#!/usr/bin/env node

// Copyright 2023-2024 SO/DA zone
// SPDX-License-Identifier: Apache-2.0
Expand Down
2 changes: 1 addition & 1 deletion examples/follow-transfer-events/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env ts-node-esm
#!/usr/bin/env node

// Copyright 2023-2024 SO/DA zone
// SPDX-License-Identifier: Apache-2.0
Expand Down
11 changes: 8 additions & 3 deletions examples/light-client/main.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
#!/usr/bin/env ts-node-esm
#!/usr/bin/env node

// Copyright 2023-2024 SO/DA zone
// SPDX-License-Identifier: Apache-2.0

import { isMainThread } from 'node:worker_threads';

import { ScProvider } from '@polkadot/rpc-provider/substrate-connect';

import { SubstrateApis, Smoldot, blocks } from '@sodazone/ocelloids-sdk';

function watcher() {

async function watcher() {
const provider = new ScProvider(
Smoldot, Smoldot.WellKnownChain.polkadot
);
Expand All @@ -29,5 +32,7 @@ function watcher() {
});
}

watcher();
if (isMainThread) {
watcher().then().catch(console.error);
}

7 changes: 3 additions & 4 deletions examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,14 @@
"yargs": "^17.7.2"
},
"devDependencies": {
"shx": "^0.3.4",
"tsup": "^8.0.2"
"tsup": "^8.0.2",
"tsx": "^4.9.0"
},
"tsup": {
"shims": true
},
"scripts": {
"build": "tsup **/main.ts --clean --minify && yarn sed:shebang",
"sed:shebang": "shx sed -i \"s/\\/env ts-node-esm/\\/env node/g\" dist/**/*.cjs > /dev/null"
"build": "tsup **/main.ts --clean --minify"
},
"stableVersion": "1.2.4"
}
2 changes: 1 addition & 1 deletion examples/simple-fees/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env ts-node-esm
#!/usr/bin/env node

// Copyright 2023-2024 SO/DA zone
// SPDX-License-Identifier: Apache-2.0
Expand Down
2 changes: 1 addition & 1 deletion examples/watch-balances/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env ts-node-esm
#!/usr/bin/env node

// Copyright 2023-2024 SO/DA zone
// SPDX-License-Identifier: Apache-2.0
Expand Down
2 changes: 1 addition & 1 deletion examples/watch-contracts/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env ts-node-esm
#!/usr/bin/env node

// Copyright 2023-2024 SO/DA zone
// SPDX-License-Identifier: Apache-2.0
Expand Down
2 changes: 1 addition & 1 deletion examples/watch-instantiations/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env ts-node-esm
#!/usr/bin/env node

// Copyright 2023-2024 SO/DA zone
// SPDX-License-Identifier: Apache-2.0
Expand Down
2 changes: 1 addition & 1 deletion examples/watch-sovereign-acc/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env ts-node-esm
#!/usr/bin/env node

// Copyright 2023-2024 SO/DA zone
// SPDX-License-Identifier: Apache-2.0
Expand Down
2 changes: 1 addition & 1 deletion examples/watch-transfer-events/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env ts-node-esm
#!/usr/bin/env node

// Copyright 2023-2024 SO/DA zone
// SPDX-License-Identifier: Apache-2.0
Expand Down
9 changes: 8 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
],
"moduleNameMapper": {
"^(\\.{1,2}/.*)\\.js$": "$1"
},
"transform": {
"^.+\\.tsx?$": [
"@swc/jest"
]
}
},
"license": "Apache-2.0",
Expand Down Expand Up @@ -49,14 +54,16 @@
"@babel/preset-env": "^7.24.5",
"@babel/preset-typescript": "^7.24.1",
"@eslint/js": "^9.1.1",
"@swc/core": "^1.4.17",
"@swc/jest": "^0.2.36",
"@types/jest": "^29.5.12",
"@types/node": "^20.12.8",
"esbuild": "^0.20.2",
"eslint": "^9.1.1",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"jest": "^29.7.0",
"prettier": "^3.2.5",
"ts-node": "^10.9.2",
"tsup": "^8.0.2",
"typedoc": "^0.25.13",
"typescript": "^5.4.5",
Expand Down
22 changes: 16 additions & 6 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"browser": "./dist/browser/index.js",
"type": "module",
"sideEffects": false,
"author": "soda <projects@soda.zone>",
Expand All @@ -29,7 +30,8 @@
"@polkadot/api": "^11.0.2",
"@substrate/connect-known-chains": "^1.1.4",
"mingo": "^6.4.15",
"rxjs": "^7.8.1"
"rxjs": "^7.8.1",
"web-worker": "^1.3.0"
},
"devDependencies": {
"@sodazone/ocelloids-sdk-test": "1.2.5-dev.0"
Expand All @@ -42,15 +44,23 @@
".": {
"types": "./dist/index.d.ts",
"require": "./dist/index.cjs",
"import": "./dist/index.js"
"import": "./dist/index.js",
"browser": "./dist/browser/index.js"
},
"./worker": {
"import": "./dist/worker/smoldot-worker.js",
"browser": "./dist/worker/smoldot-worker.js"
}
},
"scripts": {
"build": "tsup src/index.ts --format cjs,esm --dts --sourcemap --clean --minify"
},
"tsup": {
"bundle": true,
"skipNodeModulesBundle": true
"skipNodeModulesBundle": true,
"shims": true
},
"scripts": {
"build": "tsup src/index.ts --format cjs,esm --dts --sourcemap --clean --minify && yarn build:worker && yarn build:browser",
"build:worker": "esbuild src/client/worker/smoldot-worker.ts --bundle --packages=external --minify --format=esm --outdir=dist/worker",
"build:browser": "esbuild src/index.ts --define:import.meta=\"{}\" --bundle --packages=external --minify --sourcemap --platform=browser --outdir=dist/browser"
},
"stableVersion": "1.2.4"
}
25 changes: 25 additions & 0 deletions packages/core/src/client/know-chains.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { WellKnownChain } from '@substrate/connect';

async function chainSpecOf(conf: Promise<{ chainSpec: string }>) {
return (await conf).chainSpec;
}

export async function getSpec(chain: string): Promise<string> {
const knownChain = chain as WellKnownChain;
switch (knownChain) {
case WellKnownChain.polkadot: {
return await chainSpecOf(import('@substrate/connect-known-chains/polkadot'));
}
case WellKnownChain.ksmcc3: {
return await chainSpecOf(import('@substrate/connect-known-chains/ksmcc3'));
}
case WellKnownChain.westend2: {
return await chainSpecOf(import('@substrate/connect-known-chains/westend2'));
}
case WellKnownChain.rococo_v2_2: {
return await chainSpecOf(import('@substrate/connect-known-chains/rococo_v2_2'));
}
default:
throw new Error(`Unknown chain ${knownChain}`);
}
}
32 changes: 21 additions & 11 deletions packages/core/src/client/smoldot.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,27 @@

import { createScClient } from './smoldot.js';

jest.mock('node:worker_threads', () => {
return {
Worker: jest.fn().mockImplementation(() => ({
postMessage: jest.fn(),
})),
MessagePort: jest.fn(),
};
import Worker from 'web-worker';

function workerFactory(): Worker {
// eslint-disable-next-line
// @ts-ignore
return new Worker('test');
}

jest.mock('web-worker', () => {
return jest.fn().mockImplementation(() => ({
postMessage: jest.fn(),
}));
});

const mockAddChain = jest.fn();
const mockTerminate = jest.fn();
const options = {
embeddedNodeConfig: {
workerFactory,
},
};

jest.mock('smoldot', () => {
const original = jest.requireActual('smoldot');
Expand All @@ -33,12 +43,12 @@ describe('smoldot provider', () => {
});

it('should create the client', () => {
const client = createScClient();
const client = createScClient(options);
expect(client).toBeDefined();
});
it('should add a chain', async () => {
const rpc = jest.fn();
const client = createScClient();
const client = createScClient(options);
const chain = await client.addChain('{"id":"xyz"}', rpc);
expect(chain).toBeDefined();
expect(mockAddChain).toHaveBeenCalledTimes(1);
Expand All @@ -47,7 +57,7 @@ describe('smoldot provider', () => {
mockAddChain.mockReturnValueOnce(1).mockReturnValueOnce(2).mockReturnValueOnce(3).mockReturnValueOnce(4);

const rpc = jest.fn();
const client = createScClient();
const client = createScClient(options);

await client.addChain('{"id":"abc"}', rpc);
await client.addChain('{"id":"def"}', rpc);
Expand All @@ -68,7 +78,7 @@ describe('smoldot provider', () => {
});

const rpc = jest.fn();
const client = createScClient();
const client = createScClient(options);
const chains = [];

const first = await client.addChain('{"id":"abc"}', rpc);
Expand Down
53 changes: 18 additions & 35 deletions packages/core/src/client/smoldot.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
// Copyright 2023-2024 SO/DA zone
// SPDX-License-Identifier: Apache-2.0

import { Worker, MessagePort } from 'node:worker_threads';

import { logger } from '@polkadot/util';

import Worker from 'web-worker';

import { type Client, type ClientOptions, type Chain, QueueFullError, start } from 'smoldot';

import type { ScClient, AddChain, Chain as ScChain, Config as ScConfig, WellKnownChain } from '@substrate/connect';
import { getSpec } from './know-chains.js';

const l = logger('oc-smoldot-worker');

function defaultFactory(): Worker {
const ctor = typeof Worker === 'function' ? Worker : Worker.default;
return new ctor(new URL('./worker/smoldot-worker.js', import.meta.url), {
name: 'oc-smoldot-worker',
type: 'module',
});
}

const defaultLogger = (level: number, target: string, message: string) => {
if (level === 1) {
l.error(`[${target}] ${message}`);
Expand Down Expand Up @@ -51,34 +60,12 @@ function getChainId(json: string): string {
/**
* Create a worker thread and start the Smoldot client.
*
* @param workerFactory - The worker factory function.
* @param options - Options for initializing the Smoldot client.
* @returns A Smoldot {@link Client}.
*/
function startSmoldot(options?: ClientOptions): Client {
const worker = new Worker(
`
// from "@substrate/connect/worker"
const { parentPort } = require('node:worker_threads');
const smoldot = require('smoldot/worker');
const { compileBytecode } = require('smoldot/bytecode');
compileBytecode().then(bytecode => parentPort.postMessage(bytecode));
parentPort.once('message', data => {
smoldot
.run(data)
.catch(error => console.error('[smoldot-worker]', error))
.finally(() => process.exit());
});
`,
{
name: 'smoldot-worker',
eval: true,
}
);

l.debug('resource limits:', worker.resourceLimits);

function startSmoldot(workerFactory: () => Worker, options?: ClientOptions): Client {
const worker = workerFactory();
const { port1, port2 } = new MessageChannel();
worker.postMessage(port1, [port1 as unknown as MessagePort]);

Expand Down Expand Up @@ -126,7 +113,8 @@ export const createScClient = (config?: ExtConfig): ScClient => {
logCallback: defaultLogger,
};

const client = startSmoldot(clientOptions);
const workerFactory = config?.embeddedNodeConfig?.workerFactory ?? defaultFactory;
const client = startSmoldot(workerFactory, clientOptions);

const chains = new Map<string, Chain>();

Expand Down Expand Up @@ -190,13 +178,8 @@ export const createScClient = (config?: ExtConfig): ScClient => {
// Return the Substrate Connect client
return {
addChain,
addWellKnownChain: async (
wellKnownChain: WellKnownChain,
jsonRpcCallback?: JsonRpcCallback,
databaseContent?: string
) => {
const spec = await import('@substrate/connect-known-chains/' + wellKnownChain);
return await addChain(await spec.chainSpec, jsonRpcCallback, databaseContent);
addWellKnownChain: async (id: WellKnownChain, jsonRpcCallback?: JsonRpcCallback, databaseContent?: string) => {
return addChain(await getSpec(id), jsonRpcCallback, databaseContent);
},
};
};
16 changes: 16 additions & 0 deletions packages/core/src/client/worker/smoldot-worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import * as smoldot from 'smoldot/worker';
import { compileBytecode } from 'smoldot/bytecode';

compileBytecode().then((bytecode) => postMessage(bytecode));

addEventListener(
'message',
(event) => {
smoldot
.run(event.data)
.finally(() => close());
},
{
once: true,
}
);
3 changes: 2 additions & 1 deletion packages/core/src/operators/extract.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ describe('extractors over extended signed blocks', () => {
testPipe.subscribe({
next: (event: EventWithId) => {
expect(event).toBeDefined();
expect(event.toHuman()).not.toBeNull();
// TODO new captures
// expect(event.toHuman()).not.toBeNull();
expect(event.method).toEqual(testEventRecords[index].event.method);
expect(event.data.toString()).toEqual(testEventRecords[index].event.data.toString());
expect(event.blockHash).toBeDefined();
Expand Down
Loading

0 comments on commit bbf65c2

Please sign in to comment.