Skip to content
This repository was archived by the owner on Jul 10, 2025. It is now read-only.

chore(js-client)!: Simplify/optimize js-client and update README [fixes DXJ-490] #366

Merged
merged 25 commits into from
Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ node_modules/
# Build directory
**/dist
**/public
.DS_Store
4 changes: 3 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ pnpm-lock.yaml
**/build
**/public

**/CHANGELOG.md
**/CHANGELOG.md

packages/core/js-client/src/versions.ts
107 changes: 44 additions & 63 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,92 +1,72 @@
# Fluence JS Client

[![npm](https://img.shields.io/npm/v/@fluencelabs/js-client.api?label=@fluencelabs/js-client.api)](https://www.npmjs.com/package/@fluencelabs/js-client.api)
[![npm](https://img.shields.io/npm/v/@fluencelabs/js-client.web.standalone?label=@fluencelabs/js-client.web.standalone)](https://www.npmjs.com/package/@fluencelabs/js-client.web.standalone)
[![npm](https://img.shields.io/npm/v/@fluencelabs/js-client?label=@fluencelabs/js-client)](https://www.npmjs.com/package/@fluencelabs/js-client)

This is the Javascript client for the [Fluence](https://fluence.network) network. The main role of the JS client is to connect to the Fluence Network and allow you to integrate Aqua code into your application.

## Installation

Adding the Fluence JS client for your web application is very easy.
> JS Client only supports the ESM format that means not every Node.js project can install it.
> You can read more [here](https://nodejs.org/api/esm.html)

### Browser-based Apps
1. Install the client:

1. Add a script tag with the JS Client bundle to your `index.html`. The easiest way to do this is using a CDN (like [JSDELIVR](https://www.jsdelivr.com/) or [UNPKG](https://unpkg.com/)). The script is large, thus we highly recommend to use the `async` attribute.

Here is an example using the JSDELIVR CDN:

```html
<head>
<title>Cool App</title>
<script
src="https://cdn.jsdelivr.net/npm/@fluencelabs/[email protected]/dist/js-client.min.js"
async
></script>
</head>
```

If you cannot or don't want to use a CDN, feel free to get the script directly from the [npm package](https://www.npmjs.com/package/@fluencelabs/js-client.web.standalone) and host it yourself. You can find the script in the `/dist` directory of the package. (Note: this option means that developers understand what they are doing and know how to serve this file from their own web server.)

2. Install the following packages:

```
npm i @fluencelabs/js-client.api @fluencelabs/fluence-network-environment
```bash
npm i @fluencelabs/js-client
```

3. Add the following lines at the beginning of your code:
2. Add the following lines at the beginning of your code:

```
import { Fluence } from "@fluencelabs/js-client.api";
import { randomKras } from '@fluencelabs/fluence-network-environment';
```javascript
import { Fluence, randomKras } from "@fluencelabs/js-client";

Fluence.connect(randomKras());
```

### Node.js Apps

**Prerequisites:**
### HTML page

The Fluence JS Client only supports the ESM format. This implies that a few preliminary steps are required if your project is not already using ESM:
Add a script tag with the JS Client bundle to your `index.html`. The easiest way to do this is using a CDN (
like [JSDELIVR](https://www.jsdelivr.com/) or [UNPKG](https://unpkg.com/)).

- Add `"type": "module"` to your package.json.
- Replace `"main": "index.js"` with `"exports": "./index.js"` in your package.json.
- Remove `'use strict';` from all JavaScript files.
- Replace all `require()`/`module.export` with `import`/`export`.
- Use only full relative file paths for imports: `import x from '.';` → `import x from './index.js';`.
Here is an example using the JSDELIVR CDN:

If you are using TypeScript:

- Make sure you are using TypeScript 4.7 or later.
- Add [`"module": "ESNext", "target": "ESNext", "moduleResolution": "nodenext"`](https://www.typescriptlang.org/tsconfig#module) to your tsconfig.json.
- Use only full relative file paths for imports: `import x from '.';` → `import x from './index.js';`.
- Remove `namespace` usage and use `export` instead.
- You must use a `.js` extension in relative imports even though you're importing `.ts` files.
```html
<head>
<title>Cool App</title>
<script src="https://cdn.jsdelivr.net/npm/@fluencelabs/js-client/dist/browser/index.min.js"></script>
</head>
```

**Installation:**
If you cannot or don't want to use a CDN, feel free to get the script directly from
the [npm package](https://www.npmjs.com/package/@fluencelabs/js-client) and host it yourself. You can find the script in
the `/dist` directory of the package. (Note: this option means that developers understand what they are doing and know
how to serve this file from their own web server.)

1. Install the following packages:
After importing JS-client to HTML page the client is available as `window.Fluence` variable.
To get a specific network you can peek at

```
npm i @fluencelabs/js-client.api"@fluencelabs/js-client.node @fluencelabs/fluence-network-environment
```

2. Add the following lines at the beginning of your code:
```
https://cdn.jsdelivr.net/npm/@fluencelabs/js-client/dist/network.js
```

```
import '@fluencelabs/js-client.node';
import { Fluence } from "@fluencelabs/js-client.api";
import { randomKras } from '@fluencelabs/fluence-network-environment';
and hardcode selected network. So initialization would look like this

Fluence.connect(randomKras());
```
```javascript
// Passing 1 kras network config from ./dist/network.js above
window.Fluence.connect({
multiaddr:
"/dns4/0-kras.fluence.dev/tcp/9000/wss/p2p/12D3KooWSD5PToNiLQwKDXsu8JSysCwUt8BVUJEqCHcDe7P5h45e",
peerId: "12D3KooWSD5PToNiLQwKDXsu8JSysCwUt8BVUJEqCHcDe7P5h45e",
});
```

## Usage in an Application

Once you've added the client, you can compile [Aqua](https://github.com/fluencelabs/aqua) and run it in your application. To compile Aqua, use [Fluence CLI](https://github.com/fluencelabs/cli).

1. Install the package:

```
```bash
npm i -D @fluencelabs/cli
```

Expand Down Expand Up @@ -130,7 +110,7 @@ Once you've added the client, you can compile [Aqua](https://github.com/fluencel
6. Now you can import and call Aqua code from your application like
this:

```
```javascript
import { getRelayTime } from "./_aqua/demo";

async function buttonClick() {
Expand Down Expand Up @@ -172,7 +152,7 @@ Star (`*`) character can be used as a wildcard to enable logs for multiple compo

### Enabling logs in Node.js

enable logs, pass the environment variable `DEBUG` with the corresponding log level. For example:
Enable logs by passing the environment variable `DEBUG` with the corresponding log level. For example:

```sh
DEBUG=fluence:* node --loader ts-node/esm ./src/index.ts
Expand All @@ -182,13 +162,14 @@ DEBUG=fluence:* node --loader ts-node/esm ./src/index.ts

To enable logs, set the `localStorage.debug` variable. For example:

```
localStorage.debug = 'fluence:*'
```javascript
localStorage.debug = "fluence:*";
```

**NOTE**

In Chromium-based web browsers (e.g. Brave, Chrome, and Electron), the JavaScript console will—by default—only show messages logged by debug if the "Verbose" log level is enabled.
In Chromium-based web browsers (e.g. Brave, Chrome, and Electron), the JavaScript console will be default—only to show
messages logged by debug if the "Verbose" log level is enabled.

## Development

Expand Down
2 changes: 1 addition & 1 deletion packages/@tests/test-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const startContentServer = (
rewrites: [
{
source: "/js-client.min.js",
destination: "/source/index.umd.cjs",
destination: "/source/index.min.js",
},
// TODO:
// something like this
Expand Down
1 change: 0 additions & 1 deletion packages/core/aqua-to-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
"@fluencelabs/aqua-api": "0.12.0",
"@fluencelabs/aqua-lib": "0.7.3",
"@fluencelabs/interfaces": "workspace:*",
"@fluencelabs/js-client": "workspace:*",
"@fluencelabs/registry": "0.8.7",
"@fluencelabs/spell": "0.5.20",
"@fluencelabs/trust-graph": "0.4.7",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,18 @@ describe("Aqua to js/ts compiler", () => {
},
};

// TODO: see https://github.com/fluencelabs/js-client/pull/366#discussion_r1370567711
// @ts-expect-error don't use compileFromPath directly here
const jsResult = generateSources(res, "js", pkg);
// TODO: see https://github.com/fluencelabs/js-client/pull/366#discussion_r1370567711
// @ts-expect-error don't use compileFromPath directly here
const jsTypes = generateTypes(res, pkg);

expect(jsResult).toMatchSnapshot();
expect(jsTypes).toMatchSnapshot();

// TODO: see https://github.com/fluencelabs/js-client/pull/366#discussion_r1370567711
// @ts-expect-error don't use compileFromPath directly here
const tsResult = generateSources(res, "ts", pkg);

expect(tsResult).toMatchSnapshot();
Expand Down
33 changes: 33 additions & 0 deletions packages/core/js-client-isomorphic/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"type": "module",
"name": "@fluencelabs/js-client-isomorphic",
"version": "1.0.0",
"description": "Isomorphic entities for js-client",
"files": [
"dist"
],
"main": "index.js",
"scripts": {
"build": "tsc"
},
"exports": {
".": "./dist/types.js",
"./fetcher": {
"node": "./dist/fetchers/node.js",
"default": "./dist/fetchers/browser.js"
},
"./worker-resolver": {
"node": "./dist/worker-resolvers/node.js",
"default": "./dist/worker-resolvers/browser.js"
}
},
"dependencies": {
"@fluencelabs/avm": "0.52.0",
"@fluencelabs/marine-js": "0.7.2",
"@fluencelabs/marine-worker": "workspace:*",
"threads": "fluencelabs/threads.js#b00a5342380b0278d3ae56dcfb170effb3cad7cd"
},
"keywords": [],
"author": "Fluence Labs",
"license": "Apache-2.0"
}
38 changes: 38 additions & 0 deletions packages/core/js-client-isomorphic/src/fetchers/browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Copyright 2023 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import type { VersionedPackage } from "../types.js";

/**
* @param pkg name of package with version
* @param assetPath path of required asset in given package
* @param root CDN domain in browser or file system root in node
*/
export async function fetchResource(
pkg: VersionedPackage,
assetPath: string,
root: string,
) {
const refinedAssetPath = assetPath.startsWith("/")
? assetPath.slice(1)
: assetPath;

const url = new URL(`${pkg.name}@${pkg.version}/` + refinedAssetPath, root);

return fetch(url).catch(() => {
throw new Error(`Cannot fetch from ${url.toString()}`);
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,49 +14,41 @@
* limitations under the License.
*/

import fs from "fs";
import module from "module";
import path from "path";
import { readFile } from "fs/promises";
import { createRequire } from "module";
import { sep, posix, join } from "path";

import type { VersionedPackage } from "../types.js";

/**
* @param pkg name of package
* @param pkg name of package with version
* @param assetPath path of required asset in given package
* @param root CDN domain in browser or file system root in node
* @param root CDN domain in browser or js-client itself in node
*/
export async function fetchResource(
pkg: string,
pkg: VersionedPackage,
assetPath: string,
root: string,
) {
// TODO: `root` will be handled somehow in the future. For now, we use filesystem root where js-client is running;
root = "/";
const require = module.createRequire(import.meta.url);
const packagePathIndex = require.resolve(pkg);
const require = createRequire(import.meta.url);
const packagePathIndex = require.resolve(pkg.name);

// Ensure that windows path is converted to posix path. So we can find a package
const posixPath = packagePathIndex.split(path.sep).join(path.posix.sep);
const posixPath = packagePathIndex.split(sep).join(posix.sep);

const matches = new RegExp(`(.+${pkg})`).exec(posixPath);
const matches = new RegExp(`(.+${pkg.name})`).exec(posixPath);

const packagePath = matches?.[0];

if (packagePath == null) {
throw new Error(`Cannot find dependency ${pkg} in path ${posixPath}`);
throw new Error(`Cannot find dependency ${pkg.name} in path ${posixPath}`);
}

const pathToResource = path.join(root, packagePath, assetPath);

const file = await new Promise<ArrayBuffer>((resolve, reject) => {
// Cannot use 'fs/promises' with current vite config. This module is not polyfilled by default.
fs.readFile(pathToResource, (err, data) => {
if (err != null) {
reject(err);
return;
}
const pathToResource = join(root, packagePath, assetPath);

resolve(data);
});
});
const file = await readFile(pathToResource);

return new Response(file, {
headers: {
Expand Down
23 changes: 23 additions & 0 deletions packages/core/js-client-isomorphic/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Copyright 2023 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { Worker } from "threads/master";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import { Worker } from "threads/master";
import type { Worker } from "threads/master";


export type VersionedPackage = { name: string; version: string };
export type GetWorker = (
pkg: VersionedPackage,
CDNUrl: string,
) => Promise<Worker>;
Loading