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

Commit 9daaf41

Browse files
authored
feat: Simplify JS Client public API (#257)
1 parent 35dfb1b commit 9daaf41

File tree

24 files changed

+734
-436
lines changed

24 files changed

+734
-436
lines changed
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import "@fluencelabs/registry/resources-api.aqua"
22

3-
func smokeTest(label: string) -> ?string, *string:
3+
service HelloWorld("hello-world"):
4+
hello(str: string) -> string
5+
6+
func smokeTest(label: string) -> ?string, *string, string:
47
res, errors <- createResource(label)
5-
<- res, errors
8+
hello <- HelloWorld.hello("Fluence user")
9+
<- res, errors, hello

packages/@tests/aqua/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
"author": "Fluence Labs",
1818
"license": "Apache-2.0",
1919
"dependencies": {
20-
"@fluencelabs/js-client.api": "workspace:*",
21-
"@fluencelabs/fluence-network-environment": "1.0.13",
20+
"@fluencelabs/js-client.api": "workspace:^",
21+
"@fluencelabs/fluence-network-environment": "1.0.14",
2222
"base64-js": "1.5.1"
2323
},
2424
"devDependencies": {

packages/@tests/aqua/src/_aqua/smoke_test.ts

Lines changed: 170 additions & 122 deletions
Large diffs are not rendered by default.

packages/@tests/aqua/src/index.ts

Lines changed: 49 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,65 @@
11
import { fromByteArray } from 'base64-js';
22
import { Fluence } from '@fluencelabs/js-client.api';
3-
import { krasnodar } from '@fluencelabs/fluence-network-environment';
4-
import { smokeTest } from './_aqua/smoke_test.js';
3+
import { kras, randomKras } from '@fluencelabs/fluence-network-environment';
4+
import { registerHelloWorld, smokeTest } from './_aqua/smoke_test.js';
55

66
// const relay = {
77
// multiaddr: '/ip4/127.0.0.1/tcp/4310/ws/p2p/12D3KooWKEprYXUXqoV5xSBeyqrWLpQLLH4PXfvVkDJtmcqmh5V3',
88
// peerId: '12D3KooWKEprYXUXqoV5xSBeyqrWLpQLLH4PXfvVkDJtmcqmh5V3',
99
// };
1010

11-
const relay = krasnodar[4];
11+
const relay = randomKras();
1212

13-
const rndSk = () => {
14-
// if (getRandomValues) {
15-
// return getRandomValues(new Uint8Array(32));
16-
// }
17-
// @ts-ignore
18-
// return globalThis.crypto.webcrypto.getRandomValues(new Uint8Array(32));
13+
function generateRandomUint8Array() {
14+
const uint8Array = new Uint8Array(32);
15+
for (let i = 0; i < uint8Array.length; i++) {
16+
uint8Array[i] = Math.floor(Math.random() * 256);
17+
}
18+
return uint8Array;
19+
}
20+
21+
const optsWithRandomKeyPair = () => {
22+
return {
23+
keyPair: {
24+
type: 'Ed25519',
25+
source: generateRandomUint8Array(),
26+
},
27+
} as const;
1928
};
2029

2130
export const main = async () => {
22-
console.log('starting fluence...');
23-
await Fluence.start({
24-
relay: relay,
25-
// keyPair: {
26-
// type: 'Ed25519',
27-
// source: rndSk(),
28-
// },
29-
});
30-
31-
console.log('started fluence');
32-
const p = await Fluence.getPeer();
33-
34-
console.log('my peer id: ', p.getStatus().peerId);
35-
console.log('my sk id: ', fromByteArray(p.getSk()));
36-
37-
console.log('running some aqua...');
38-
const [res, errors] = await smokeTest('my_resource');
39-
if (res === null) {
40-
console.log('aqua failed, errors', errors);
41-
} else {
42-
console.log('aqua finished, result', res);
43-
}
31+
try {
32+
Fluence.onConnectionStateChange((state) => console.info('connection state changed: ', state));
33+
34+
console.log('connecting to Fluence Network...');
35+
await Fluence.connect(relay, optsWithRandomKeyPair());
36+
37+
console.log('connected');
4438

45-
console.log('stopping fluence...');
46-
await Fluence.stop();
47-
console.log('stopped fluence...');
39+
await registerHelloWorld({
40+
hello(str) {
41+
return 'Hello, ' + str + '!';
42+
},
43+
});
44+
45+
const client = await Fluence.getClient();
46+
47+
console.log('my peer id: ', client.getPeerId());
48+
console.log('my sk id: ', fromByteArray(client.getPeerSecretKey()));
49+
50+
console.log('running some aqua...');
51+
const [res, errors, hello] = await smokeTest('my_resource');
52+
console.log(hello);
53+
if (res === null) {
54+
console.log('aqua failed, errors', errors);
55+
} else {
56+
console.log('aqua finished, result', res);
57+
}
58+
} finally {
59+
console.log('disconnecting from Fluence Network...');
60+
await Fluence.disconnect();
61+
console.log('disconnected');
62+
}
4863
};
4964

5065
export const runMain = () => {

packages/@tests/frameworks/cra-ts/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
"version": "0.1.0",
44
"private": true,
55
"dependencies": {
6-
"@fluencelabs/js-client.api": "workspace:*",
7-
"@test/aqua_for_test": "workspace:*",
6+
"@fluencelabs/js-client.api": "workspace:^",
7+
"@test/aqua_for_test": "workspace:^",
88
"@testing-library/jest-dom": "5.16.5",
99
"@testing-library/react": "13.4.0",
1010
"@testing-library/user-event": "13.5.0",

packages/@tests/smoke_node/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
"author": "Fluence Labs",
1818
"license": "Apache-2.0",
1919
"dependencies": {
20-
"@fluencelabs/js-client.api": "workspace:*",
21-
"@fluencelabs/js-client.node": "workspace:*",
22-
"@test/aqua_for_test": "workspace:*"
20+
"@fluencelabs/js-client.api": "workspace:^",
21+
"@fluencelabs/js-client.node": "workspace:^",
22+
"@test/aqua_for_test": "workspace:^"
2323
},
2424
"devDependencies": {}
2525
}

packages/@tests/smoke_web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"license": "Apache-2.0",
1919
"dependencies": {},
2020
"devDependencies": {
21-
"@fluencelabs/js-client.web.standalone": "workspace:*",
21+
"@fluencelabs/js-client.web.standalone": "workspace:^",
2222
"http-server": "14.1.1"
2323
}
2424
}

packages/client/api/src/compilerSupport/implementation.ts

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import type { IFluenceClient } from '@fluencelabs/interfaces';
1919
import { getArgumentTypes } from '@fluencelabs/interfaces';
2020
import { isFluencePeer } from '@fluencelabs/interfaces';
2121

22-
import { getDefaultPeer } from '../util.js';
22+
import { getFluenceInterface } from '../util.js';
2323

2424
/**
2525
* Convenience function to support Aqua `func` generation backend
@@ -30,12 +30,20 @@ import { getDefaultPeer } from '../util.js';
3030
* @param script - air script with function execution logic generated by the Aqua compiler
3131
*/
3232
export const callFunction = async (rawFnArgs: Array<any>, def: FunctionCallDef, script: string): Promise<unknown> => {
33-
const { args, peer, config } = await extractFunctionArgs(rawFnArgs, def);
34-
return peer.compilerSupport.callFunction({
33+
const { args, client: peer, config } = await extractFunctionArgs(rawFnArgs, def);
34+
if (peer.internals.getConnectionState() !== 'connected') {
35+
throw new Error(
36+
'Could not call the Aqua function because client is disconnected. Did you forget to call Fluence.connect()?',
37+
);
38+
}
39+
40+
const fluence = await getFluenceInterface();
41+
return fluence.callAquaFunction({
3542
args,
3643
def,
3744
script,
3845
config: config || {},
46+
peer: peer,
3947
});
4048
};
4149

@@ -47,10 +55,21 @@ export const callFunction = async (rawFnArgs: Array<any>, def: FunctionCallDef,
4755
*/
4856
export const registerService = async (args: any[], def: ServiceDef): Promise<unknown> => {
4957
const { peer, service, serviceId } = await extractServiceArgs(args, def.defaultServiceId);
50-
return peer.compilerSupport.registerService({
58+
59+
// TODO: TBH service registration is just putting some stuff into a hashmap
60+
// there should not be such a check at all
61+
if (peer.internals.getConnectionState() !== 'connected') {
62+
throw new Error(
63+
'Could not register Aqua service because the client is disconnected. Did you forget to call Fluence.connect()?',
64+
);
65+
}
66+
67+
const fluence = await getFluenceInterface();
68+
return fluence.registerService({
5169
def,
5270
service,
5371
serviceId,
72+
peer,
5473
});
5574
};
5675

@@ -68,7 +87,7 @@ const extractFunctionArgs = async (
6887
args: any[],
6988
def: FunctionCallDef,
7089
): Promise<{
71-
peer: IFluenceClient;
90+
client: IFluenceClient;
7291
config?: FnConfig;
7392
args: { [key: string]: any };
7493
}> => {
@@ -84,7 +103,8 @@ const extractFunctionArgs = async (
84103
structuredArgs = args.slice(1, numberOfExpectedArgs + 1);
85104
config = args[numberOfExpectedArgs + 1];
86105
} else {
87-
peer = await getDefaultPeer();
106+
const fluence = await getFluenceInterface();
107+
peer = fluence.defaultClient;
88108
structuredArgs = args.slice(0, numberOfExpectedArgs);
89109
config = args[numberOfExpectedArgs];
90110
}
@@ -96,7 +116,7 @@ const extractFunctionArgs = async (
96116
const argsRes = argumentNames.reduce((acc, name, index) => ({ ...acc, [name]: structuredArgs[index] }), {});
97117

98118
return {
99-
peer: peer,
119+
client: peer,
100120
config: config,
101121
args: argsRes,
102122
};
@@ -124,7 +144,8 @@ const extractServiceArgs = async (
124144
if (isFluencePeer(args[0])) {
125145
peer = args[0];
126146
} else {
127-
peer = await getDefaultPeer();
147+
const fluence = await getFluenceInterface();
148+
peer = fluence.defaultClient;
128149
}
129150

130151
if (typeof args[0] === 'string') {

packages/client/api/src/index.ts

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1-
import { getDefaultPeer } from './util.js';
2-
import type { IFluenceClient, ClientOptions } from '@fluencelabs/interfaces';
1+
import { getFluenceInterface, getFluenceInterfaceFromGlobalThis } from './util.js';
2+
import {
3+
IFluenceClient,
4+
ClientOptions,
5+
RelayOptions,
6+
ConnectionState,
7+
ConnectionStates,
8+
} from '@fluencelabs/interfaces';
39
export type { IFluenceClient, ClientOptions, CallParams } from '@fluencelabs/interfaces';
410

511
export {
@@ -30,33 +36,56 @@ export {
3036
} from './compilerSupport/implementation.js';
3137

3238
/**
33-
* Public interface to Fluence JS
39+
* Public interface to Fluence Network
3440
*/
3541
export const Fluence = {
3642
/**
37-
* Initializes the default peer: starts the Aqua VM, initializes the default call service handlers
38-
* and (optionally) connect to the Fluence network
39-
* @param options - object specifying peer configuration
43+
* Connect to the Fluence network
44+
* @param relay - relay node to connect to
45+
* @param options - client options
4046
*/
41-
start: async (options?: ClientOptions): Promise<void> => {
42-
const peer = await getDefaultPeer();
43-
return peer.start(options);
47+
connect: async (relay: RelayOptions, options?: ClientOptions): Promise<void> => {
48+
const fluence = await getFluenceInterface();
49+
return fluence.defaultClient.connect(relay, options);
4450
},
4551

4652
/**
47-
* Un-initializes the default peer: stops all the underlying workflows, stops the Aqua VM
48-
* and disconnects from the Fluence network
53+
* Disconnect from the Fluence network
4954
*/
50-
stop: async (): Promise<void> => {
51-
const peer = await getDefaultPeer();
52-
return peer.stop();
55+
disconnect: async (): Promise<void> => {
56+
const fluence = await getFluenceInterface();
57+
return fluence.defaultClient.disconnect();
5358
},
5459

5560
/**
56-
* Get the default peer instance
57-
* @returns the default peer instance
61+
* Handle connection state changes. Immediately returns the current connection state
5862
*/
59-
getPeer: async (): Promise<IFluenceClient> => {
60-
return getDefaultPeer();
63+
onConnectionStateChange(handler: (state: ConnectionState) => void): ConnectionState {
64+
const optimisticResult = getFluenceInterfaceFromGlobalThis();
65+
if (optimisticResult) {
66+
return optimisticResult.defaultClient.onConnectionStateChange(handler);
67+
}
68+
69+
getFluenceInterface().then((fluence) => fluence.defaultClient.onConnectionStateChange(handler));
70+
71+
return 'disconnected';
6172
},
73+
74+
/**
75+
* Low level API. Get the underlying client instance which holds the connection to the network
76+
* @returns IFluenceClient instance
77+
*/
78+
getClient: async (): Promise<IFluenceClient> => {
79+
const fluence = await getFluenceInterface();
80+
return fluence.defaultClient;
81+
},
82+
};
83+
84+
/**
85+
* Low level API. Generally you need Fluence.connect() instead.
86+
* @returns IFluenceClient instance
87+
*/
88+
export const createClient = async (): Promise<IFluenceClient> => {
89+
const fluence = await getFluenceInterface();
90+
return fluence.clientFactory();
6291
};

packages/client/api/src/util.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,23 @@
1-
import type { IFluenceClient } from '@fluencelabs/interfaces';
1+
import type { CallAquaFunction, IFluenceClient, RegisterService } from '@fluencelabs/interfaces';
22

3-
const getPeerFromGlobalThis = (): IFluenceClient | undefined => {
3+
type PublicFluenceInterface = {
4+
clientFactory: () => IFluenceClient;
5+
defaultClient: IFluenceClient;
6+
callAquaFunction: CallAquaFunction;
7+
registerService: RegisterService;
8+
};
9+
10+
export const getFluenceInterfaceFromGlobalThis = (): PublicFluenceInterface | undefined => {
411
// @ts-ignore
5-
return globalThis.defaultPeer;
12+
return globalThis.fluence;
613
};
714

8-
// TODO: DXJ-271
9-
const REJECT_MESSAGE = 'You probably forgot to add script tag. Read about it here: ';
15+
// TODO: fix link DXJ-271
16+
const REJECT_MESSAGE = `Could not load Fluence JS Client library.
17+
If you are using Node.js that probably means that you forgot in install or import the @fluencelabs/js-client.node package.
18+
If you are using a browser, then you probably forgot to add the <script> tag to your HTML.
19+
Please refer to the documentation page for more details: https://fluence.dev/
20+
`;
1021

1122
// Let's assume that if the library has not been loaded in 5 seconds, then the user has forgotten to add the script tag
1223
const POLL_PEER_TIMEOUT = 5000;
@@ -17,7 +28,13 @@ const POLL_PEER_INTERVAL = 100;
1728
/**
1829
* Wait until the js client script it loaded and return the default peer from globalThis
1930
*/
20-
export const getDefaultPeer = (): Promise<IFluenceClient> => {
31+
export const getFluenceInterface = (): Promise<PublicFluenceInterface> => {
32+
// If the script is already loaded, then return the value immediately
33+
const optimisticResult = getFluenceInterfaceFromGlobalThis();
34+
if (optimisticResult) {
35+
return Promise.resolve(optimisticResult);
36+
}
37+
2138
return new Promise((resolve, reject) => {
2239
// This function is internal
2340
// Make it sure that would be zero way for unnecessary types
@@ -30,7 +47,7 @@ export const getDefaultPeer = (): Promise<IFluenceClient> => {
3047
reject(REJECT_MESSAGE);
3148
}
3249

33-
let res = getPeerFromGlobalThis();
50+
let res = getFluenceInterfaceFromGlobalThis();
3451
if (res) {
3552
clearInterval(interval);
3653
resolve(res);

0 commit comments

Comments
 (0)