Aqua HLL + AppConfig (#5)

* Switch to app.config based deployment

* Rewrite user online status checks to peer is_connected api

* Rewrite fluentpad into aquamarine
This commit is contained in:
folex 2021-04-30 17:15:30 +03:00 committed by GitHub
parent dffe660fa4
commit f430b725d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 2639 additions and 846 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
*.sh text=auto eol=lf

1
app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.wasm

36
app/app.config.json Normal file
View File

@ -0,0 +1,36 @@
{
"services": {
"history": {
"dependencies": ["history_inmemory"],
"node": "12D3KooWKEprYXUXqoV5xSBeyqrWLpQLLH4PXfvVkDJtmcqmh5V3"
},
"user_list": {
"dependencies": ["user_list_inmemory"],
"node": "12D3KooWKEprYXUXqoV5xSBeyqrWLpQLLH4PXfvVkDJtmcqmh5V3"
}
},
"modules": {
"history_inmemory": {
"file": "history.wasm",
"config": {
"preopened_files": ["/tmp"],
"mapped_dirs": { "history": "/tmp" }
}
},
"user_list_inmemory": {
"file": "user_list.wasm",
"config": {}
}
},
"scripts": {
"set_tetraplet": {
"file": "set_tetraplet.air",
"variables": {
"function": "is_authenticated",
"json_path": "$.is_authenticated"
},
"node": "12D3KooWKEprYXUXqoV5xSBeyqrWLpQLLH4PXfvVkDJtmcqmh5V3"
}
},
"script_storage": {}
}

View File

@ -0,0 +1,42 @@
{
"services": {
"history": {
"dependencies": ["history_inmemory"],
"node": "12D3KooWKEprYXUXqoV5xSBeyqrWLpQLLH4PXfvVkDJtmcqmh5V3"
},
"user_list": {
"dependencies": ["user_list_inmemory"],
"node": "12D3KooWKEprYXUXqoV5xSBeyqrWLpQLLH4PXfvVkDJtmcqmh5V3"
}
},
"modules": {
"history_inmemory": {
"file": "history.wasm",
"config": {
"preopened_files": ["/tmp"],
"mapped_dirs": { "history": "/tmp" }
}
},
"user_list_inmemory": {
"file": "user_list.wasm",
"config": {}
}
},
"scripts": {
"set_tetraplet": {
"file": "set_tetraplet.air",
"variables": {
"function": "is_authenticated",
"json_path": "$.[\"is_authenticated\"]"
},
"node": "12D3KooWKEprYXUXqoV5xSBeyqrWLpQLLH4PXfvVkDJtmcqmh5V3"
}
},
"script_storage": {
"remove_disconnected": {
"file": "remove_disconnected.air",
"interval": 10,
"node": "12D3KooWKEprYXUXqoV5xSBeyqrWLpQLLH4PXfvVkDJtmcqmh5V3"
}
}
}

View File

@ -0,0 +1,17 @@
(seq
(call %init_peer_id% ({{user_list}} "is_authenticated") [] token)
(seq
(call %init_peer_id% ({{user_list}} "get_users") [] all_users)
(fold all_users.$.users! u
(par
(seq
(call u.$.relay_id! ("peer" "is_connected") [u.$.peer_id!] is_connected)
(match is_connected false
(call %init_peer_id% ({{user_list}} "leave") [u.$.peer_id!])
)
)
(next u)
)
)
)
)

7
app/set_tetraplet.air Normal file
View File

@ -0,0 +1,7 @@
(seq
(call init_relay ("op" "identity") [])
(seq
(call history__node (history "set_tetraplet") [user_list__node user_list function json_path] auth_result)
(call %init_peer_id% (returnService "run") [auth_result])
)
)

20
build.sh Normal file
View File

@ -0,0 +1,20 @@
#!/bin/sh
(
cd services/user-list-inmemory
cargo update
fce build --release
)
(
cd services/history-inmemory
cargo update
fce build --release
)
rm -f app/user_list.wasm
rm -f app/history.wasm
cp services/user-list-inmemory/target/wasm32-wasi/release/user_list.wasm app/
cp services/history-inmemory/target/wasm32-wasi/release/history.wasm app/

2
client/.gitignore vendored
View File

@ -22,3 +22,5 @@ npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
.eslintcache .eslintcache
*.jar

77
client/aqua/app.aqua Normal file
View File

@ -0,0 +1,77 @@
import "@fluencelabs/aqua-lib/builtin.aqua"
import "fluent-pad.aqua"
import "history.aqua"
func join(user: User) -> EmptyServiceResult:
app <- AppConfig.getApp()
on app.user_list.peer_id:
UserList app.user_list.service_id
res <- UserList.join(user)
<- res
func getUserList() -> []User:
app <- AppConfig.getApp()
on app.user_list.peer_id:
UserList app.user_list.service_id
allUsers <- UserList.get_users()
<- allUsers.users
func initAfterJoin(me: User) -> []User:
allUsers <- getUserList()
for user <- allUsers par:
on user.relay_id:
isOnline <- Peer.is_connected(user.peer_id)
if isOnline:
on user.peer_id via user.relay_id:
FluentPad.notifyUserAdded(me, true)
else:
Op.identity()
par FluentPad.notifyUserAdded(user, isOnline)
<- allUsers
func updateOnlineStatuses():
allUsers <- getUserList()
for user <- allUsers par:
on user.peer_id via user.relay_id:
isOnline <- Peer.is_connected(user.peer_id)
FluentPad.notifyOnline(user.peer_id, isOnline)
func leave():
app <- AppConfig.getApp()
on app.user_list.peer_id:
UserList app.user_list.service_id
res <- UserList.leave(%init_peer_id%)
allUsers <- getUserList()
for user <- allUsers par:
on user.peer_id via user.relay_id:
FluentPad.notifyUserRemoved(%init_peer_id%)
func auth() -> AuthResult:
app <- AppConfig.getApp()
on app.user_list.peer_id:
UserList app.user_list.service_id
res <- UserList.is_authenticated()
<- res
func getHistory() -> GetEntriesServiceResult:
app <- AppConfig.getApp()
authRes <- auth()
on app.history.peer_id:
History app.history.service_id
res <- History.get_all(authRes.is_authenticated)
<- res
func addEntry(entry: string, init_peer_id: PeerId) -> AddServiceResult:
app <- AppConfig.getApp()
authRes <- auth()
on app.history.peer_id:
History app.history.service_id
res <- History.add(entry, authRes.is_authenticated)
allUsers <- getUserList()
for user <- allUsers par:
if user.peer_id != init_peer_id:
on user.peer_id via user.relay_id:
FluentPad.notifyTextUpdate(entry, authRes.is_authenticated)
<- res

3
client/aqua/common.aqua Normal file
View File

@ -0,0 +1,3 @@
data EmptyServiceResult:
ret_code: s32
err_msg: string

View File

@ -0,0 +1,19 @@
import "@fluencelabs/aqua-lib/builtin.aqua"
import "user-list.aqua"
data ServiceInstance:
peer_id: PeerId
service_id: string
data App:
history: ServiceInstance
user_list: ServiceInstance
service FluentPad("fluence/fluent-pad"):
notifyOnline(userPeerId: string, isOnline: bool)
notifyUserAdded(currentUser: User, isOnline: bool)
notifyUserRemoved(userPeerId: PeerId)
notifyTextUpdate(changes: string, isAuthorized: bool)
service AppConfig("fluence/get-config"):
getApp: -> App

21
client/aqua/history.aqua Normal file
View File

@ -0,0 +1,21 @@
import "common.aqua"
data AddServiceResult:
ret_code: s32
err_msg: string
entry_id: u64
data HistoryEntry:
id: u64
body: string
data GetEntriesServiceResult:
ret_code: s32
err_msg: string
entries: []HistoryEntry
service History:
get_all: bool -> GetEntriesServiceResult
get_last: u64, bool -> GetEntriesServiceResult
add: string, bool -> AddServiceResult
set_tetraplet: string, string, string, string -> EmptyServiceResult

View File

@ -0,0 +1,24 @@
import "@fluencelabs/aqua-lib/builtin.aqua"
import "common.aqua"
data User:
peer_id: PeerId
relay_id: PeerId
name: string
data GetUsersServiceResult:
users: []User
ret_code: s32
err_msg: string
data AuthResult:
ret_code: s32
err_msg: string
is_authenticated: bool
service UserList:
is_authenticated: -> AuthResult
get_users: -> GetUsersServiceResult
join: User -> EmptyServiceResult
leave: string -> EmptyServiceResult -- user peerId
is_exists: string -> () -- user peerId

1848
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@fluencelabs/fluence": "0.9.31", "@fluencelabs/fluence": "^0.9.43",
"@fluencelabs/fluence-network-environment": "1.0.8", "@fluencelabs/fluence-network-environment": "1.0.8",
"@testing-library/jest-dom": "^5.11.9", "@testing-library/jest-dom": "^5.11.9",
"@testing-library/react": "^11.2.5", "@testing-library/react": "^11.2.5",
@ -27,7 +27,8 @@
"start": "react-scripts start", "start": "react-scripts start",
"build": "react-scripts build", "build": "react-scripts build",
"test": "react-scripts test", "test": "react-scripts test",
"eject": "react-scripts eject" "eject": "react-scripts eject",
"compile-aqua": "aqua-cli -i ./aqua/ -o ./src/aqua/"
}, },
"eslintConfig": { "eslintConfig": {
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
@ -52,6 +53,8 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"@fluencelabs/aqua-cli": "^0.1.1-94",
"@fluencelabs/aqua-lib": "0.1.1",
"@types/lodash": "^4.14.168" "@types/lodash": "^4.14.168"
} }
} }

56
client/src/app.json Normal file
View File

@ -0,0 +1,56 @@
{
"services": {
"history": {
"dependencies": [
"history_inmemory"
],
"node": "12D3KooWKEprYXUXqoV5xSBeyqrWLpQLLH4PXfvVkDJtmcqmh5V3",
"hashDependencies": [
"hash:6ee648216089b876a34353f485f1bf19dd863b861f0d798a0ac0cb65cc3b4e2f"
],
"blueprint_id": "5df598924434974291d98c6d72e0922f2e8cbd7f7f291ca44f1b2f8e198f421b",
"id": "afed8e34-f399-4e55-9d1f-c5c58296ad7a"
},
"user_list": {
"dependencies": [
"user_list_inmemory"
],
"node": "12D3KooWKEprYXUXqoV5xSBeyqrWLpQLLH4PXfvVkDJtmcqmh5V3",
"hashDependencies": [
"hash:691ebcd74409bd931a9a68d0e0001f8d9d4b8482c8aaac3184e28c2a322e5a82"
],
"blueprint_id": "d46fb511eb5a5dcea7b5a6777e04f95d48cbbe9fde040bd6bd69b4d9cd8fbb88",
"id": "5622f669-3699-430f-9f4e-a1cb117cc0e2"
}
},
"modules": {
"history_inmemory": {
"file": "history.wasm",
"config": {
"preopened_files": [
"/tmp"
],
"mapped_dirs": {
"history": "/tmp"
}
},
"hash": "6ee648216089b876a34353f485f1bf19dd863b861f0d798a0ac0cb65cc3b4e2f"
},
"user_list_inmemory": {
"file": "user_list.wasm",
"config": {},
"hash": "691ebcd74409bd931a9a68d0e0001f8d9d4b8482c8aaac3184e28c2a322e5a82"
}
},
"scripts": {
"set_tetraplet": {
"file": "set_tetraplet.air",
"variables": {
"function": "is_authenticated",
"json_path": "$.is_authenticated"
},
"node": "12D3KooWKEprYXUXqoV5xSBeyqrWLpQLLH4PXfvVkDJtmcqmh5V3"
}
},
"script_storage": {}
}

View File

@ -1,292 +0,0 @@
import { FluenceClient, Particle, sendParticle, sendParticleAsFetch } from '@fluencelabs/fluence';
import {
fluentPadServiceId,
historyNodePeerId,
historyServiceId,
notifyOnlineFnName,
notifyTextUpdateFnName,
notifyUserAddedFnName,
notifyUserRemovedFnName,
userListNodePeerId,
userListServiceId,
} from './constants';
export interface ServiceResult {
ret_code: number;
err_msg: string;
}
export interface User {
peer_id: string;
relay_id: string;
name: string;
}
export interface Entry {
id: number;
body: string;
}
interface GetUsersResult extends ServiceResult {
users: Array<User>;
}
interface GetEntriesResult extends ServiceResult {
entries: Entry[];
}
const throwIfError = (result: ServiceResult) => {
if (result.ret_code !== 0) {
throw new Error(result.err_msg);
}
};
export const updateOnlineStatuses = async (client: FluenceClient) => {
const particle = new Particle(
`
(seq
(call myRelay ("op" "identity") [])
(seq
(call userlistNode (userlist "get_users") [] allUsers)
(fold allUsers.$.users! u
(par
(seq
(call u.$.relay_id! ("op" "identity") [])
(seq
(call u.$.peer_id! ("op" "identity") [])
(seq
(call u.$.relay_id! ("op" "identity") [])
(seq
(call myRelay ("op" "identity") [])
(call myPeerId (fluentPadServiceId notifyOnline) [u.$.peer_id!])
)
)
)
)
(next u)
)
)
)
)
`,
{
userlistNode: userListNodePeerId,
userlist: userListServiceId,
myRelay: client.relayPeerId,
myPeerId: client.selfPeerId,
fluentPadServiceId: fluentPadServiceId,
notifyOnline: notifyOnlineFnName,
},
);
sendParticle(client, particle);
};
export const notifySelfAdded = (client: FluenceClient, name: string) => {
const particle = new Particle(
`
(seq
(call myRelay ("op" "identity") [])
(seq
(call userlistNode (userlist "get_users") [] allUsers)
(fold allUsers.$.users! u
(par
(seq
(call u.$.relay_id! ("op" "identity") [])
(call u.$.peer_id! (fluentPadServiceId notifyUserAdded) [myUser setOnline])
)
(next u)
)
)
)
)
`,
{
userlistNode: userListNodePeerId,
userlist: userListServiceId,
myRelay: client.relayPeerId,
myPeerId: client.selfPeerId,
fluentPadServiceId: fluentPadServiceId,
notifyUserAdded: notifyUserAddedFnName,
myUser: [
{
name: name,
peer_id: client.selfPeerId,
relay_id: client.relayPeerId,
},
],
setOnline: true,
},
);
sendParticle(client, particle);
};
export const getUserList = async (client: FluenceClient) => {
const particle = new Particle(
`
(seq
(call myRelay ("op" "identity") [])
(seq
(call userlistNode (userlist "get_users") [] allUsers)
(seq
(call myRelay ("op" "identity") [])
(call myPeerId (fluentPadServiceId notifyUserAdded) [allUsers.$.users!])
)
)
)
`,
{
userlistNode: userListNodePeerId,
userlist: userListServiceId,
myRelay: client.relayPeerId,
myPeerId: client.selfPeerId,
fluentPadServiceId: fluentPadServiceId,
notifyUserAdded: notifyUserAddedFnName,
immediately: true,
},
);
await sendParticle(client, particle);
};
export const join = async (client: FluenceClient, nickName: string) => {
const particle = new Particle(
`
(seq
(call myRelay ("op" "identity") [])
(seq
(call userlistNode (userlist "join") [user] result)
(seq
(call myRelay ("op" "identity") [])
(call myPeerId ("_callback" "join") [result])
)
)
)
`,
{
myRelay: client.relayPeerId,
myPeerId: client.selfPeerId,
user: {
name: nickName,
peer_id: client.selfPeerId,
relay_id: client.relayPeerId,
},
userlist: userListServiceId,
userlistNode: userListNodePeerId,
},
);
const [result] = await sendParticleAsFetch<[ServiceResult]>(client, particle, 'join');
throwIfError(result);
};
export const leave = async (client: FluenceClient) => {
const particle = new Particle(
`
(seq
(call myRelay ("op" "identity") [])
(seq
(call userlistNode (userlist "leave") [myPeerId])
(seq
(call userlistNode (userlist "get_users") [] allUsers)
(fold allUsers.$.users! u
(par
(seq
(call u.$.relay_id! ("op" "identity") [])
(call u.$.peer_id! (fluentPadServiceId notifyUserRemoved) [myPeerId])
)
(next u)
)
)
)
)
)
`,
{
userlistNode: userListNodePeerId,
userlist: userListServiceId,
myRelay: client.relayPeerId,
myPeerId: client.selfPeerId,
fluentPadServiceId: fluentPadServiceId,
notifyUserRemoved: notifyUserRemovedFnName,
},
);
await sendParticle(client, particle);
};
export const getHistory = async (client: FluenceClient) => {
const particle = new Particle(
`
(seq
(call myRelay ("op" "identity") [])
(seq
(call userlistNode (userlist "is_authenticated") [] token)
(seq
(call historyNode (history "get_all") [token.$.["is_authenticated"]] entries)
(seq
(call myRelay ("op" "identity") [])
(call myPeerId ("_callback" "get_history") [entries])
)
)
)
)
`,
{
myRelay: client.relayPeerId,
myPeerId: client.selfPeerId,
userlist: userListServiceId,
history: historyServiceId,
userlistNode: userListNodePeerId,
historyNode: historyNodePeerId,
},
);
const [result] = await sendParticleAsFetch<[GetEntriesResult]>(client, particle, 'get_history');
throwIfError(result);
return result.entries;
};
export const addEntry = async (client: FluenceClient, entry: string) => {
const particle = new Particle(
`
(seq
(call myRelay ("op" "identity") [])
(seq
(call userlistNode (userlist "is_authenticated") [] token)
(seq
(call userlistNode (userlist "get_users") [] allUsers)
(seq
(call historyNode (history "add") [entry token.$.["is_authenticated"]])
(fold allUsers.$.users! u
(par
(seq
(call u.$.relay_id! ("op" "identity") [])
(call u.$.peer_id! (fluentPadServiceId notifyTextUpdate) [myPeerId entry token.$.["is_authenticated"]])
)
(next u)
)
)
)
)
)
)
`,
{
userlistNode: userListNodePeerId,
historyNode: historyNodePeerId,
entry: entry,
userlist: userListServiceId,
history: historyServiceId,
myRelay: client.relayPeerId,
myPeerId: client.selfPeerId,
fluentPadServiceId: fluentPadServiceId,
notifyTextUpdate: notifyTextUpdateFnName,
},
);
await sendParticle(client, particle);
};

View File

@ -1,5 +1,4 @@
import config from 'src/app.json';
import { testNet } from '@fluencelabs/fluence-network-environment'; import { testNet } from '@fluencelabs/fluence-network-environment';
export const fluentPadServiceId = 'fluence/fluent-pad'; export const fluentPadServiceId = 'fluence/fluent-pad';
@ -9,10 +8,23 @@ export const notifyUserAddedFnName = 'notifyUserAdded';
export const notifyUserRemovedFnName = 'notifyUserRemoved'; export const notifyUserRemovedFnName = 'notifyUserRemoved';
export const notifyTextUpdateFnName = 'notifyTextUpdate'; export const notifyTextUpdateFnName = 'notifyTextUpdate';
export const historyServiceId = '64ea579e-b863-4a42-b80c-e7b5ec1ab7fa'; export const userList = {
export const userListServiceId = '91041afe-0c3c-451a-9003-6bb92a570aae'; peer_id: config.services.user_list.node,
service_id: config.services.user_list.id,
};
export const userListNodePeerId = testNet[3].peerId; export const history = {
export const historyNodePeerId = testNet[3].peerId; peer_id: config.services.history.node,
service_id: config.services.history.id,
};
export const relayNode = testNet[0]; export const fluentPadApp = {
user_list: userList,
history: history,
};
// export const relayNode = testNet[0];
export const relayNode = {
multiaddr: '/ip4/127.0.0.1/tcp/4310/ws/p2p/12D3KooWKEprYXUXqoV5xSBeyqrWLpQLLH4PXfvVkDJtmcqmh5V3',
peerId: '12D3KooWKEprYXUXqoV5xSBeyqrWLpQLLH4PXfvVkDJtmcqmh5V3',
};

View File

@ -44,7 +44,7 @@ export class SyncClient<T = TextDoc> {
const msg = JSON.parse(changes); const msg = JSON.parse(changes);
this.connection.receiveMsg(msg); this.connection.receiveMsg(msg);
} catch (e) { } catch (e) {
console.log('Couldnt receive message', changes); console.error('Couldnt receive message', changes);
} }
} }

636
client/src/aqua/app.ts Normal file
View File

@ -0,0 +1,636 @@
/**
*
* This file is auto-generated. Do not edit manually: changes may be erased.
* Generated by Aqua compiler: https://github.com/fluencelabs/aqua/.
* If you find any bugs, please write an issue on GitHub: https://github.com/fluencelabs/aqua/issues
*
*/
import { FluenceClient, PeerIdB58 } from '@fluencelabs/fluence';
import { RequestFlowBuilder } from '@fluencelabs/fluence/dist/api.unstable';
export async function join(
client: FluenceClient,
user: { name: string; peer_id: string; relay_id: string },
): Promise<{ err_msg: string; ret_code: number }> {
let request;
const promise = new Promise<{ err_msg: string; ret_code: number }>((resolve, reject) => {
request = new RequestFlowBuilder()
.disableInjections()
.withRawScript(
`
(xor
(seq
(seq
(seq
(seq
(call %init_peer_id% ("getDataSrv" "relay") [] relay)
(call %init_peer_id% ("getDataSrv" "user") [] user)
)
(seq
(call %init_peer_id% ("fluence/get-config" "getApp") [] app)
(seq
(call relay ("op" "identity") [])
(call app.$.user_list.peer_id! (app.$.user_list.service_id! "join") [user] res)
)
)
)
(call relay ("op" "identity") [])
)
(call %init_peer_id% ("callbackSrv" "response") [res])
)
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error%])
)
`,
)
.configHandler((h) => {
h.on('getDataSrv', 'relay', () => {
return client.relayPeerId!;
});
h.on('getRelayService', 'hasRelay', () => {
// Not Used
return client.relayPeerId !== undefined;
});
h.on('getDataSrv', 'user', () => {
return user;
});
h.onEvent('callbackSrv', 'response', (args) => {
const [res] = args;
resolve(res);
});
h.onEvent('errorHandlingSrv', 'error', (args) => {
// assuming error is the single argument
const [err] = args;
reject(err);
});
})
.handleScriptError(reject)
.handleTimeout(() => {
reject('Request timed out for join');
})
.build();
});
await client.initiateFlow(request);
return promise;
}
export async function getUserList(
client: FluenceClient,
): Promise<{ name: string; peer_id: string; relay_id: string }[]> {
let request;
const promise = new Promise<{ name: string; peer_id: string; relay_id: string }[]>((resolve, reject) => {
request = new RequestFlowBuilder()
.disableInjections()
.withRawScript(
`
(xor
(seq
(seq
(seq
(call %init_peer_id% ("getDataSrv" "relay") [] relay)
(seq
(call %init_peer_id% ("fluence/get-config" "getApp") [] app)
(seq
(call relay ("op" "identity") [])
(call app.$.user_list.peer_id! (app.$.user_list.service_id! "get_users") [] allUsers)
)
)
)
(call relay ("op" "identity") [])
)
(call %init_peer_id% ("callbackSrv" "response") [allUsers.$.users!])
)
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error%])
)
`,
)
.configHandler((h) => {
h.on('getDataSrv', 'relay', () => {
return client.relayPeerId!;
});
h.on('getRelayService', 'hasRelay', () => {
// Not Used
return client.relayPeerId !== undefined;
});
h.onEvent('callbackSrv', 'response', (args) => {
const [res] = args;
resolve(res);
});
h.onEvent('errorHandlingSrv', 'error', (args) => {
// assuming error is the single argument
const [err] = args;
reject(err);
});
})
.handleScriptError(reject)
.handleTimeout(() => {
reject('Request timed out for getUserList');
})
.build();
});
await client.initiateFlow(request);
return promise;
}
export async function initAfterJoin(
client: FluenceClient,
me: { name: string; peer_id: string; relay_id: string },
): Promise<{ name: string; peer_id: string; relay_id: string }[]> {
let request;
const promise = new Promise<{ name: string; peer_id: string; relay_id: string }[]>((resolve, reject) => {
request = new RequestFlowBuilder()
.disableInjections()
.withRawScript(
`
(xor
(seq
(seq
(seq
(call %init_peer_id% ("getDataSrv" "relay") [] relay)
(call %init_peer_id% ("getDataSrv" "me") [] me)
)
(seq
(seq
(seq
(call %init_peer_id% ("fluence/get-config" "getApp") [] app)
(seq
(call relay ("op" "identity") [])
(call app.$.user_list.peer_id! (app.$.user_list.service_id! "get_users") [] allUsers)
)
)
(call relay ("op" "identity") [])
)
(fold allUsers.$.users! user
(par
(seq
(seq
(seq
(call relay ("op" "identity") [])
(call user.$.relay_id! ("peer" "is_connected") [user.$.peer_id!] isOnline)
)
(call relay ("op" "identity") [])
)
(par
(xor
(match isOnline true
(seq
(seq
(call relay ("op" "identity") [])
(call user.$.relay_id! ("op" "identity") [])
)
(call user.$.peer_id! ("fluence/fluent-pad" "notifyUserAdded") [me true])
)
)
(call %init_peer_id% ("op" "identity") [])
)
(call %init_peer_id% ("fluence/fluent-pad" "notifyUserAdded") [user isOnline])
)
)
(next user)
)
)
)
)
(call %init_peer_id% ("callbackSrv" "response") [allUsers])
)
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error%])
)
`,
)
.configHandler((h) => {
h.on('getDataSrv', 'relay', () => {
return client.relayPeerId!;
});
h.on('getRelayService', 'hasRelay', () => {
// Not Used
return client.relayPeerId !== undefined;
});
h.on('getDataSrv', 'me', () => {
return me;
});
h.onEvent('callbackSrv', 'response', (args) => {
const [res] = args;
resolve(res);
});
h.onEvent('errorHandlingSrv', 'error', (args) => {
// assuming error is the single argument
const [err] = args;
reject(err);
});
})
.handleScriptError(reject)
.handleTimeout(() => {
reject('Request timed out for initAfterJoin');
})
.build();
});
await client.initiateFlow(request);
return promise;
}
export async function updateOnlineStatuses(client: FluenceClient): Promise<void> {
let request;
const promise = new Promise<void>((resolve, reject) => {
request = new RequestFlowBuilder()
.disableInjections()
.withRawScript(
`
(xor
(seq
(call %init_peer_id% ("getDataSrv" "relay") [] relay)
(seq
(seq
(seq
(call %init_peer_id% ("fluence/get-config" "getApp") [] app)
(seq
(call relay ("op" "identity") [])
(call app.$.user_list.peer_id! (app.$.user_list.service_id! "get_users") [] allUsers)
)
)
(call relay ("op" "identity") [])
)
(fold allUsers.$.users! user
(par
(seq
(seq
(seq
(seq
(seq
(call relay ("op" "identity") [])
(call user.$.relay_id! ("op" "identity") [])
)
(call user.$.peer_id! ("peer" "is_connected") [user.$.peer_id!] isOnline)
)
(call user.$.relay_id! ("op" "identity") [])
)
(call relay ("op" "identity") [])
)
(call %init_peer_id% ("fluence/fluent-pad" "notifyOnline") [user.$.peer_id! isOnline])
)
(next user)
)
)
)
)
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error%])
)
`,
)
.configHandler((h) => {
h.on('getDataSrv', 'relay', () => {
return client.relayPeerId!;
});
h.on('getRelayService', 'hasRelay', () => {
// Not Used
return client.relayPeerId !== undefined;
});
h.onEvent('errorHandlingSrv', 'error', (args) => {
// assuming error is the single argument
const [err] = args;
reject(err);
});
})
.handleScriptError(reject)
.handleTimeout(() => {
reject('Request timed out for updateOnlineStatuses');
})
.build();
});
await client.initiateFlow(request);
return Promise.race([promise, Promise.resolve()]);
}
export async function leave(client: FluenceClient): Promise<void> {
let request;
const promise = new Promise<void>((resolve, reject) => {
request = new RequestFlowBuilder()
.disableInjections()
.withRawScript(
`
(xor
(seq
(call %init_peer_id% ("getDataSrv" "relay") [] relay)
(seq
(seq
(seq
(seq
(seq
(call %init_peer_id% ("fluence/get-config" "getApp") [] app)
(seq
(call relay ("op" "identity") [])
(call app.$.user_list.peer_id! (app.$.user_list.service_id! "leave") [%init_peer_id%] res)
)
)
(call relay ("op" "identity") [])
)
(seq
(call %init_peer_id% ("fluence/get-config" "getApp") [] app0)
(seq
(call relay ("op" "identity") [])
(call app0.$.user_list.peer_id! (app0.$.user_list.service_id! "get_users") [] allUsers)
)
)
)
(call relay ("op" "identity") [])
)
(fold allUsers.$.users! user
(par
(seq
(seq
(call relay ("op" "identity") [])
(call user.$.relay_id! ("op" "identity") [])
)
(call user.$.peer_id! ("fluence/fluent-pad" "notifyUserRemoved") [%init_peer_id%])
)
(next user)
)
)
)
)
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error%])
)
`,
)
.configHandler((h) => {
h.on('getDataSrv', 'relay', () => {
return client.relayPeerId!;
});
h.on('getRelayService', 'hasRelay', () => {
// Not Used
return client.relayPeerId !== undefined;
});
h.onEvent('errorHandlingSrv', 'error', (args) => {
// assuming error is the single argument
const [err] = args;
reject(err);
});
})
.handleScriptError(reject)
.handleTimeout(() => {
reject('Request timed out for leave');
})
.build();
});
await client.initiateFlow(request);
return Promise.race([promise, Promise.resolve()]);
}
export async function auth(
client: FluenceClient,
): Promise<{ err_msg: string; is_authenticated: boolean; ret_code: number }> {
let request;
const promise = new Promise<{ err_msg: string; is_authenticated: boolean; ret_code: number }>((resolve, reject) => {
request = new RequestFlowBuilder()
.disableInjections()
.withRawScript(
`
(xor
(seq
(seq
(seq
(call %init_peer_id% ("getDataSrv" "relay") [] relay)
(seq
(call %init_peer_id% ("fluence/get-config" "getApp") [] app)
(seq
(call relay ("op" "identity") [])
(call app.$.user_list.peer_id! (app.$.user_list.service_id! "is_authenticated") [] res)
)
)
)
(call relay ("op" "identity") [])
)
(call %init_peer_id% ("callbackSrv" "response") [res])
)
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error%])
)
`,
)
.configHandler((h) => {
h.on('getDataSrv', 'relay', () => {
return client.relayPeerId!;
});
h.on('getRelayService', 'hasRelay', () => {
// Not Used
return client.relayPeerId !== undefined;
});
h.onEvent('callbackSrv', 'response', (args) => {
const [res] = args;
resolve(res);
});
h.onEvent('errorHandlingSrv', 'error', (args) => {
// assuming error is the single argument
const [err] = args;
reject(err);
});
})
.handleScriptError(reject)
.handleTimeout(() => {
reject('Request timed out for auth');
})
.build();
});
await client.initiateFlow(request);
return promise;
}
export async function getHistory(
client: FluenceClient,
): Promise<{ entries: { body: string; id: number }[]; err_msg: string; ret_code: number }> {
let request;
const promise = new Promise<{ entries: { body: string; id: number }[]; err_msg: string; ret_code: number }>(
(resolve, reject) => {
request = new RequestFlowBuilder()
.disableInjections()
.withRawScript(
`
(xor
(seq
(seq
(seq
(call %init_peer_id% ("getDataSrv" "relay") [] relay)
(seq
(seq
(seq
(call %init_peer_id% ("fluence/get-config" "getApp") [] app)
(seq
(call %init_peer_id% ("fluence/get-config" "getApp") [] app0)
(seq
(call relay ("op" "identity") [])
(call app0.$.user_list.peer_id! (app0.$.user_list.service_id! "is_authenticated") [] res0)
)
)
)
(call relay ("op" "identity") [])
)
(seq
(call relay ("op" "identity") [])
(call app.$.history.peer_id! (app.$.history.service_id! "get_all") [res0.$.is_authenticated!] res)
)
)
)
(call relay ("op" "identity") [])
)
(call %init_peer_id% ("callbackSrv" "response") [res])
)
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error%])
)
`,
)
.configHandler((h) => {
h.on('getDataSrv', 'relay', () => {
return client.relayPeerId!;
});
h.on('getRelayService', 'hasRelay', () => {
// Not Used
return client.relayPeerId !== undefined;
});
h.onEvent('callbackSrv', 'response', (args) => {
const [res] = args;
resolve(res);
});
h.onEvent('errorHandlingSrv', 'error', (args) => {
// assuming error is the single argument
const [err] = args;
reject(err);
});
})
.handleScriptError(reject)
.handleTimeout(() => {
reject('Request timed out for getHistory');
})
.build();
},
);
await client.initiateFlow(request);
return promise;
}
export async function addEntry(
client: FluenceClient,
entry: string,
init_peer_id: string,
): Promise<{ entry_id: number; err_msg: string; ret_code: number }> {
let request;
const promise = new Promise<{ entry_id: number; err_msg: string; ret_code: number }>((resolve, reject) => {
request = new RequestFlowBuilder()
.disableInjections()
.withRawScript(
`
(xor
(seq
(seq
(seq
(seq
(call %init_peer_id% ("getDataSrv" "relay") [] relay)
(call %init_peer_id% ("getDataSrv" "entry") [] entry)
)
(call %init_peer_id% ("getDataSrv" "init_peer_id") [] init_peer_id)
)
(seq
(seq
(seq
(seq
(seq
(seq
(seq
(call %init_peer_id% ("fluence/get-config" "getApp") [] app)
(seq
(call %init_peer_id% ("fluence/get-config" "getApp") [] app0)
(seq
(call relay ("op" "identity") [])
(call app0.$.user_list.peer_id! (app0.$.user_list.service_id! "is_authenticated") [] res0)
)
)
)
(call relay ("op" "identity") [])
)
(seq
(call relay ("op" "identity") [])
(call app.$.history.peer_id! (app.$.history.service_id! "add") [entry res0.$.is_authenticated!] res)
)
)
(call relay ("op" "identity") [])
)
(seq
(call %init_peer_id% ("fluence/get-config" "getApp") [] app1)
(seq
(call relay ("op" "identity") [])
(call app1.$.user_list.peer_id! (app1.$.user_list.service_id! "get_users") [] allUsers)
)
)
)
(call relay ("op" "identity") [])
)
(fold allUsers.$.users! user
(par
(mismatch user.$.peer_id! init_peer_id
(seq
(seq
(call relay ("op" "identity") [])
(call user.$.relay_id! ("op" "identity") [])
)
(call user.$.peer_id! ("fluence/fluent-pad" "notifyTextUpdate") [entry res0.$.is_authenticated!])
)
)
(next user)
)
)
)
)
(call %init_peer_id% ("callbackSrv" "response") [res])
)
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error%])
)
`,
)
.configHandler((h) => {
h.on('getDataSrv', 'relay', () => {
return client.relayPeerId!;
});
h.on('getRelayService', 'hasRelay', () => {
// Not Used
return client.relayPeerId !== undefined;
});
h.on('getDataSrv', 'entry', () => {
return entry;
});
h.on('getDataSrv', 'init_peer_id', () => {
return init_peer_id;
});
h.onEvent('callbackSrv', 'response', (args) => {
const [res] = args;
resolve(res);
});
h.onEvent('errorHandlingSrv', 'error', (args) => {
// assuming error is the single argument
const [err] = args;
reject(err);
});
})
.handleScriptError(reject)
.handleTimeout(() => {
reject('Request timed out for addEntry');
})
.build();
});
await client.initiateFlow(request);
return promise;
}

View File

@ -5,11 +5,24 @@ import './App.scss';
import { FluenceClientContext } from '../app/FluenceClientContext'; import { FluenceClientContext } from '../app/FluenceClientContext';
import { UserList } from './UserList'; import { UserList } from './UserList';
import * as api from 'src/app/api';
import { CollaborativeEditor } from './CollaborativeEditor'; import { CollaborativeEditor } from './CollaborativeEditor';
import { relayNode } from 'src/app/constants'; import { fluentPadApp, relayNode } from 'src/app/constants';
import { withErrorHandlingAsync } from './util'; import { CheckResponse, withErrorHandlingAsync } from './util';
import { toast } from 'react-toastify'; import { join, leave } from 'src/aqua/app';
const createClientEx = async (relay) => {
const client = await createClient(relay);
client.aquaCallHandler.on('fluence/get-config', 'getApp', () => {
return fluentPadApp;
});
client.aquaCallHandler.on('fluence/get-config', 'get_init_peer_id', () => {
return client.selfPeerId;
});
client.aquaCallHandler.on('fluence/get-config', 'get_init_relay', () => {
return client.relayPeerId!;
});
return client;
};
const App = () => { const App = () => {
const [client, setClient] = useState<FluenceClient | null>(null); const [client, setClient] = useState<FluenceClient | null>(null);
@ -17,7 +30,7 @@ const App = () => {
const [nickName, setNickName] = useState(''); const [nickName, setNickName] = useState('');
useEffect(() => { useEffect(() => {
createClient(relayNode) createClientEx(relayNode)
.then((client) => setClient(client)) .then((client) => setClient(client))
.catch((err) => console.log('Client initialization failed', err)); .catch((err) => console.log('Client initialization failed', err));
}, []); }, []);
@ -28,8 +41,14 @@ const App = () => {
} }
await withErrorHandlingAsync(async () => { await withErrorHandlingAsync(async () => {
await api.join(client, nickName); const res = await join(client, {
peer_id: client.selfPeerId,
relay_id: client.relayPeerId!,
name: nickName,
});
if (CheckResponse(res)) {
setIsInRoom(true); setIsInRoom(true);
}
}); });
}; };
@ -39,7 +58,7 @@ const App = () => {
} }
await withErrorHandlingAsync(async () => { await withErrorHandlingAsync(async () => {
await api.leave(client); await leave(client);
setIsInRoom(false); setIsInRoom(false);
}); });
}; };

View File

@ -5,8 +5,8 @@ import { PeerIdB58, subscribeToEvent } from '@fluencelabs/fluence';
import { fluentPadServiceId, notifyTextUpdateFnName } from 'src/app/constants'; import { fluentPadServiceId, notifyTextUpdateFnName } from 'src/app/constants';
import { useFluenceClient } from '../app/FluenceClientContext'; import { useFluenceClient } from '../app/FluenceClientContext';
import { getUpdatedDocFromText, initDoc, SyncClient } from '../app/sync'; import { getUpdatedDocFromText, initDoc, SyncClient } from '../app/sync';
import * as api from 'src/app/api';
import { withErrorHandlingAsync } from './util'; import { withErrorHandlingAsync } from './util';
import { addEntry, getHistory } from 'src/aqua/app';
const broadcastUpdates = _.debounce((text: string, syncClient: SyncClient) => { const broadcastUpdates = _.debounce((text: string, syncClient: SyncClient) => {
let doc = syncClient.getDoc(); let doc = syncClient.getDoc();
@ -28,15 +28,17 @@ export const CollaborativeEditor = () => {
syncClient.handleSendChanges = (changes: string) => { syncClient.handleSendChanges = (changes: string) => {
withErrorHandlingAsync(async () => { withErrorHandlingAsync(async () => {
await api.addEntry(client, changes); const res = await addEntry(client, changes, client.selfPeerId);
if (res.ret_code !== 0) {
throw new Error(
`Failed to add message to history service, code=${res.ret_code}, message=${res.err_msg}`,
);
}
}); });
}; };
const unsub = subscribeToEvent(client, fluentPadServiceId, notifyTextUpdateFnName, (args, tetraplets) => { const unsub = subscribeToEvent(client, fluentPadServiceId, notifyTextUpdateFnName, (args, tetraplets) => {
const [authorPeerId, changes, isAuthorized] = args as [PeerIdB58, string, boolean]; const [changes, isAuthorized] = args as [string, boolean];
if (authorPeerId === client.selfPeerId) {
return;
}
if (changes) { if (changes) {
syncClient.receiveChanges(changes); syncClient.receiveChanges(changes);
@ -47,8 +49,8 @@ export const CollaborativeEditor = () => {
// don't block // don't block
withErrorHandlingAsync(async () => { withErrorHandlingAsync(async () => {
const res = await api.getHistory(client); const res = await getHistory(client);
for (let e of res) { for (let e of res.entries) {
syncClient.receiveChanges(e.body); syncClient.receiveChanges(e.body);
} }

View File

@ -6,64 +6,61 @@ import {
notifyUserRemovedFnName, notifyUserRemovedFnName,
} from 'src/app/constants'; } from 'src/app/constants';
import { useFluenceClient } from '../app/FluenceClientContext'; import { useFluenceClient } from '../app/FluenceClientContext';
import * as api from 'src/app/api';
import { PeerIdB58, subscribeToEvent } from '@fluencelabs/fluence'; import { PeerIdB58, subscribeToEvent } from '@fluencelabs/fluence';
import { withErrorHandlingAsync } from './util'; import { withErrorHandlingAsync } from './util';
import { initAfterJoin, updateOnlineStatuses } from 'src/aqua/app';
interface User { interface User {
id: PeerIdB58; id: PeerIdB58;
name: string; name: string;
isOnline: boolean; isOnline: boolean;
shouldBecomeOnline: boolean;
} }
const turnUserAsOfflineCandidate = (u: User): User => { interface ApiUser {
return { name: string;
...u, peer_id: string;
isOnline: u.shouldBecomeOnline, relay_id: string;
shouldBecomeOnline: false, }
};
};
const refreshTimeoutMs = 2000; const refreshOnlineStatusTimeoutMs = 10000;
export const UserList = (props: { selfName: string }) => { export const UserList = (props: { selfName: string }) => {
const client = useFluenceClient()!; const client = useFluenceClient()!;
const [users, setUsers] = useState<Map<PeerIdB58, User>>(new Map()); const [users, setUsers] = useState<Map<PeerIdB58, User>>(new Map());
useEffect(() => { const updateOnlineStatus = (user, onlineStatus) => {
const listRefreshTimer = setInterval(() => {
setUsers((prev) => {
const newUsers = Array.from(prev).map(
([key, user]) => [key, turnUserAsOfflineCandidate(user)] as const,
);
return new Map(newUsers);
});
// don't block
withErrorHandlingAsync(async () => {
await api.updateOnlineStatuses(client);
});
}, refreshTimeoutMs);
const unsub1 = subscribeToEvent(client, fluentPadServiceId, notifyUserAddedFnName, (args, _) => {
const [users, setOnline] = args as [api.User[], boolean];
setUsers((prev) => { setUsers((prev) => {
const result = new Map(prev); const result = new Map(prev);
for (let u of users) { const u = result.get(user);
if (result.has(u.peer_id)) { if (u) {
continue; result.set(user, { ...u, isOnline: onlineStatus });
} }
return result;
});
};
const isCurrentUser = u.peer_id === client.selfPeerId; useEffect(() => {
const listRefreshTimer = setInterval(() => {
withErrorHandlingAsync(async () => {
// await updateOnlineStatuses(client);
});
}, refreshOnlineStatusTimeoutMs);
const unsub1 = subscribeToEvent(client, fluentPadServiceId, notifyUserAddedFnName, (args, _) => {
const [user, isOnline] = args as [ApiUser, boolean];
setUsers((prev) => {
const u = user;
const result = new Map(prev);
if (result.has(u.peer_id)) {
return result;
}
result.set(u.peer_id, { result.set(u.peer_id, {
name: u.name, name: u.name,
id: u.peer_id, id: u.peer_id,
isOnline: isCurrentUser || setOnline, isOnline: isOnline,
shouldBecomeOnline: isCurrentUser || setOnline,
}); });
}
return result; return result;
}); });
}); });
@ -78,26 +75,17 @@ export const UserList = (props: { selfName: string }) => {
}); });
const unsub3 = subscribeToEvent(client, fluentPadServiceId, notifyOnlineFnName, (args, _) => { const unsub3 = subscribeToEvent(client, fluentPadServiceId, notifyOnlineFnName, (args, _) => {
const [userOnline] = args as [PeerIdB58[]]; const [user, onlineStatus] = args as [PeerIdB58, boolean];
setUsers((prev) => { updateOnlineStatus(user, onlineStatus);
const result = new Map(prev);
for (let u of userOnline) {
const toSetOnline = result.get(u);
if (toSetOnline) {
toSetOnline.shouldBecomeOnline = true;
toSetOnline.isOnline = true;
}
}
return result;
});
}); });
// don't block // don't block
withErrorHandlingAsync(async () => { withErrorHandlingAsync(async () => {
await api.getUserList(client); await initAfterJoin(client, {
await api.notifySelfAdded(client, props.selfName); name: props.selfName,
peer_id: client.selfPeerId,
relay_id: client.relayPeerId!,
});
}); });
return () => { return () => {

View File

@ -1,5 +1,15 @@
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
export const CheckResponse = (response: { err_msg: string; ret_code: number }): boolean => {
if (response.ret_code !== 0) {
console.error(response.err_msg);
toast.error('Something went wrong: ' + response.err_msg);
return false;
}
return true;
};
export const withErrorHandling = (fn: () => void): void => { export const withErrorHandling = (fn: () => void): void => {
try { try {
fn(); fn();

View File

@ -6,7 +6,7 @@ import { setLogLevel } from '@fluencelabs/fluence';
import { ToastContainer } from 'react-toastify'; import { ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css'; import 'react-toastify/dist/ReactToastify.css';
setLogLevel('INFO'); setLogLevel('trace');
ReactDOM.render( ReactDOM.render(
<React.StrictMode> <React.StrictMode>

5
deploy_docker.sh Normal file
View File

@ -0,0 +1,5 @@
#!/bin/sh
docker kill fluence_node
docker run -d --rm --name fluence_node -e RUST_LOG="info" -p 1210:1210 -p 4310:4310 fluencelabs/fluence -t 1210 -w 4310 -k gKdiCSUr1TFGFEgu2t8Ch1XEUsrN5A2UfBLjSZvfci9SPR3NvZpACfcpPGC3eY4zma1pk7UvYv5zb1VjvPHwCjj
fldist deploy_app --env local -s Fs6nQaGEsM5EgnprUbUtoLYWhUC8o6QK1gseP9pfhzUm -i app/app.config.json -o client/src/app.json

3
package-lock.json generated Normal file
View File

@ -0,0 +1,3 @@
{
"lockfileVersion": 1
}

View File

@ -1 +0,0 @@
{"name":"history"}

View File

@ -1 +0,0 @@
{"name":"user-list"}

View File

@ -1,21 +0,0 @@
#!/bin/sh
(
cd user-list-inmemory
cargo update
fce build --release
)
(
cd history-inmemory
cargo update
fce build --release
)
rm -f artifacts/user-list.wasm
rm -f artifacts/history.wasm
mkdir -p artifacts
cp user-list-inmemory/target/wasm32-wasi/release/user-list.wasm artifacts/
echo '{"name":"user-list"}' > artifacts/user-list.json
cp history-inmemory/target/wasm32-wasi/release/history.wasm artifacts/
echo '{"name":"history"}' > artifacts/history.json

View File

@ -4,9 +4,9 @@ version = 3
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.38" version = "1.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afddf7f520a80dbf76e6f50a35bca42a2331ef227a28b3b6dc5c2e2338d114b1" checksum = "81cddc5f91628367664cc7c69714ff08deee8a3efc54623011c772544d7b2767"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
@ -112,9 +112,9 @@ checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.87" version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "265d751d31d6780a3f956bb5b8022feba2d94eeee5a84ba64f4212eedca42213" checksum = "8916b1f6ca17130ec6568feccee27c156ad12037880833a3b842a823236502e7"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
@ -243,9 +243,9 @@ checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.61" version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed22b90a0e734a23a7610f4283ac9e5acfb96cbb30dfefa540d66f866f1c09c5" checksum = "3fd9d1e9976102a03c542daa2eff1b43f9d72306342f3f8b3ed5fb8908195d6f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@ -4,9 +4,9 @@ version = 3
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.38" version = "1.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afddf7f520a80dbf76e6f50a35bca42a2331ef227a28b3b6dc5c2e2338d114b1" checksum = "81cddc5f91628367664cc7c69714ff08deee8a3efc54623011c772544d7b2767"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
@ -98,9 +98,9 @@ checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.87" version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "265d751d31d6780a3f956bb5b8022feba2d94eeee5a84ba64f4212eedca42213" checksum = "8916b1f6ca17130ec6568feccee27c156ad12037880833a3b842a823236502e7"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
@ -229,9 +229,9 @@ checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.61" version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed22b90a0e734a23a7610f4283ac9e5acfb96cbb30dfefa540d66f866f1c09c5" checksum = "3fd9d1e9976102a03c542daa2eff1b43f9d72306342f3f8b3ed5fb8908195d6f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -245,7 +245,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]] [[package]]
name = "user-list" name = "user_list"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",

View File

@ -1,11 +1,11 @@
[package] [package]
name = "user-list" name = "user_list"
version = "0.1.0" version = "0.1.0"
authors = ["Fluence Labs"] authors = ["Fluence Labs"]
edition = "2018" edition = "2018"
[[bin]] [[bin]]
name = "user-list" name = "user_list"
path = "src/main.rs" path = "src/main.rs"
[dependencies] [dependencies]