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-error.log*
.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

1878
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,57 +1,60 @@
{
"name": "fluent-pad",
"version": "0.1.0",
"private": true,
"dependencies": {
"@fluencelabs/fluence": "0.9.31",
"@fluencelabs/fluence-network-environment": "1.0.8",
"@testing-library/jest-dom": "^5.11.9",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^12.6.3",
"@types/jest": "^26.0.19",
"@types/node": "^12.19.16",
"@types/react": "^16.14.3",
"@types/react-dom": "^16.9.10",
"automerge": "^0.14.2",
"diff-match-patch": "^1.0.5",
"lodash": "^4.17.20",
"node-sass": "^4.14.1",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-scripts": "4.0.1",
"react-toastify": "^7.0.3",
"typescript": "^4.1.3",
"web-vitals": "^0.2.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 12
"name": "fluent-pad",
"version": "0.1.0",
"private": true,
"dependencies": {
"@fluencelabs/fluence": "^0.9.43",
"@fluencelabs/fluence-network-environment": "1.0.8",
"@testing-library/jest-dom": "^5.11.9",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^12.6.3",
"@types/jest": "^26.0.19",
"@types/node": "^12.19.16",
"@types/react": "^16.14.3",
"@types/react-dom": "^16.9.10",
"automerge": "^0.14.2",
"diff-match-patch": "^1.0.5",
"lodash": "^4.17.20",
"node-sass": "^4.14.1",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-scripts": "4.0.1",
"react-toastify": "^7.0.3",
"typescript": "^4.1.3",
"web-vitals": "^0.2.4"
},
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@types/lodash": "^4.14.168"
}
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"compile-aqua": "aqua-cli -i ./aqua/ -o ./src/aqua/"
},
"eslintConfig": {
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 12
},
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@fluencelabs/aqua-cli": "^0.1.1-94",
"@fluencelabs/aqua-lib": "0.1.1",
"@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';
export const fluentPadServiceId = 'fluence/fluent-pad';
@ -9,10 +8,23 @@ export const notifyUserAddedFnName = 'notifyUserAdded';
export const notifyUserRemovedFnName = 'notifyUserRemoved';
export const notifyTextUpdateFnName = 'notifyTextUpdate';
export const historyServiceId = '64ea579e-b863-4a42-b80c-e7b5ec1ab7fa';
export const userListServiceId = '91041afe-0c3c-451a-9003-6bb92a570aae';
export const userList = {
peer_id: config.services.user_list.node,
service_id: config.services.user_list.id,
};
export const userListNodePeerId = testNet[3].peerId;
export const historyNodePeerId = testNet[3].peerId;
export const history = {
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);
this.connection.receiveMsg(msg);
} 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 { UserList } from './UserList';
import * as api from 'src/app/api';
import { CollaborativeEditor } from './CollaborativeEditor';
import { relayNode } from 'src/app/constants';
import { withErrorHandlingAsync } from './util';
import { toast } from 'react-toastify';
import { fluentPadApp, relayNode } from 'src/app/constants';
import { CheckResponse, withErrorHandlingAsync } from './util';
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 [client, setClient] = useState<FluenceClient | null>(null);
@ -17,7 +30,7 @@ const App = () => {
const [nickName, setNickName] = useState('');
useEffect(() => {
createClient(relayNode)
createClientEx(relayNode)
.then((client) => setClient(client))
.catch((err) => console.log('Client initialization failed', err));
}, []);
@ -28,8 +41,14 @@ const App = () => {
}
await withErrorHandlingAsync(async () => {
await api.join(client, nickName);
setIsInRoom(true);
const res = await join(client, {
peer_id: client.selfPeerId,
relay_id: client.relayPeerId!,
name: nickName,
});
if (CheckResponse(res)) {
setIsInRoom(true);
}
});
};
@ -39,7 +58,7 @@ const App = () => {
}
await withErrorHandlingAsync(async () => {
await api.leave(client);
await leave(client);
setIsInRoom(false);
});
};

View File

@ -5,8 +5,8 @@ import { PeerIdB58, subscribeToEvent } from '@fluencelabs/fluence';
import { fluentPadServiceId, notifyTextUpdateFnName } from 'src/app/constants';
import { useFluenceClient } from '../app/FluenceClientContext';
import { getUpdatedDocFromText, initDoc, SyncClient } from '../app/sync';
import * as api from 'src/app/api';
import { withErrorHandlingAsync } from './util';
import { addEntry, getHistory } from 'src/aqua/app';
const broadcastUpdates = _.debounce((text: string, syncClient: SyncClient) => {
let doc = syncClient.getDoc();
@ -28,15 +28,17 @@ export const CollaborativeEditor = () => {
syncClient.handleSendChanges = (changes: string) => {
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 [authorPeerId, changes, isAuthorized] = args as [PeerIdB58, string, boolean];
if (authorPeerId === client.selfPeerId) {
return;
}
const [changes, isAuthorized] = args as [string, boolean];
if (changes) {
syncClient.receiveChanges(changes);
@ -47,8 +49,8 @@ export const CollaborativeEditor = () => {
// don't block
withErrorHandlingAsync(async () => {
const res = await api.getHistory(client);
for (let e of res) {
const res = await getHistory(client);
for (let e of res.entries) {
syncClient.receiveChanges(e.body);
}

View File

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

View File

@ -1,5 +1,15 @@
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 => {
try {
fn();

View File

@ -6,7 +6,7 @@ import { setLogLevel } from '@fluencelabs/fluence';
import { ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
setLogLevel('INFO');
setLogLevel('trace');
ReactDOM.render(
<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]]
name = "anyhow"
version = "1.0.38"
version = "1.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afddf7f520a80dbf76e6f50a35bca42a2331ef227a28b3b6dc5c2e2338d114b1"
checksum = "81cddc5f91628367664cc7c69714ff08deee8a3efc54623011c772544d7b2767"
[[package]]
name = "bitflags"
@ -112,9 +112,9 @@ checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
[[package]]
name = "libc"
version = "0.2.87"
version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "265d751d31d6780a3f956bb5b8022feba2d94eeee5a84ba64f4212eedca42213"
checksum = "8916b1f6ca17130ec6568feccee27c156ad12037880833a3b842a823236502e7"
[[package]]
name = "lock_api"
@ -243,9 +243,9 @@ checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
[[package]]
name = "syn"
version = "1.0.61"
version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed22b90a0e734a23a7610f4283ac9e5acfb96cbb30dfefa540d66f866f1c09c5"
checksum = "3fd9d1e9976102a03c542daa2eff1b43f9d72306342f3f8b3ed5fb8908195d6f"
dependencies = [
"proc-macro2",
"quote",

View File

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

View File

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