From 963f7faaa078f416a1b8d3b8f8a646be2490fb63 Mon Sep 17 00:00:00 2001 From: Fedor Indutny Date: Tue, 17 Sep 2019 11:25:44 -0700 Subject: [PATCH] invite: fallback mode for failing connectivity --- package-lock.json | 6 +++--- package.json | 5 +++-- src/electron/main.js | 4 +++- src/electron/network.js | 28 +++++++++++++++++++++++++ src/redux/actions.js | 45 ++++++++++++++++++++++++++++++++++++++++- src/redux/commands.js | 6 +++++- src/redux/network.js | 9 +++++++++ 7 files changed, 95 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index ccf8445..f330c1e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1377,9 +1377,9 @@ "dev": true }, "@peerlinks/protocol": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@peerlinks/protocol/-/protocol-6.0.2.tgz", - "integrity": "sha512-uu2ACjrl+9I5GaJ1eLt0knDOACHVIh536A6AyetMJIv7EfmZJMHB2/fnsJ5UmCdXPzasPqeS3zr7Z6UDTOD1AQ==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@peerlinks/protocol/-/protocol-6.0.3.tgz", + "integrity": "sha512-kuHP4f8veQdozDrGgETqb7kpoxCdUy0Qy9zyAQBlZhHP7+aGPPKLYnl+XJl65sQsC28AwxYWTJ83M7cmXL/HJA==", "requires": { "debug": "^4.1.1", "promise-waitlist": "^1.5.0", diff --git a/package.json b/package.json index 63fcfce..8cea3d2 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ }, "homepage": "./", "dependencies": { - "@peerlinks/protocol": "^6.0.2", + "@peerlinks/protocol": "^6.0.3", "@peerlinks/sqlite-storage": "^3.0.0", "@peerlinks/swarm": "^3.0.0", "binary-search": "^1.3.6", @@ -44,7 +44,8 @@ "react:build": "react-scripts build", "react:test": "react-scripts test", "react:eject": "react-scripts eject", - "electron:dev": "concurrently \"BROWSER=none npm run react:start\" \"wait-on http://localhost:3000/ && electron .\"", + "electron:dev": "concurrently \"BROWSER=none npm run react:start\" \"wait-on http://localhost:3000/ && npm run electron:dev-standalone\"", + "electron:dev-standalone": "electron .", "electron:build": "npm run react:build && electron-builder -mlw", "electron:publish": "npm run electron:build -- -p always" }, diff --git a/src/electron/main.js b/src/electron/main.js index 28cf45d..0b2a495 100644 --- a/src/electron/main.js +++ b/src/electron/main.js @@ -16,7 +16,9 @@ const UPDATE_FREQUENCY = 4 * 3600 * 1000; const USER_DATA_DIR = app.getPath('userData'); const DB_FILE = path.join( - USER_DATA_DIR, isDev ? 'db-dev.sqlite' : 'db-v2.sqlite'); + USER_DATA_DIR, + isDev ? (process.env.PEERLINKS_DB || 'db-dev.sqlite') : + 'db-v2.sqlite'); // Create `userData` folder if it doesn't exist if (!fs.existsSync(USER_DATA_DIR)) { diff --git a/src/electron/network.js b/src/electron/network.js index 13b932e..2e19adc 100644 --- a/src/electron/network.js +++ b/src/electron/network.js @@ -390,12 +390,40 @@ export default class Network { const { encryptedInvite, peerId } = identity.issueInvite(channel, request, inviteeName); + return { + encryptedInvite: { + box: bs58.encode(encryptedInvite.box), + requestId: bs58.encode(encryptedInvite.requestId), + }, + peerId: peerId.toString('hex'), + }; + }); + + handle('sendInvite', async ({ peerId, encryptedInvite }) => { + peerId = Buffer.from(peerId, 'hex'); + encryptedInvite = { + box: bs58.decode(encryptedInvite.box), + requestId: bs58.decode(encryptedInvite.requestId), + }; + return await this.swarm.sendInvite({ peerId, encryptedInvite, }, INVITE_TIMEOUT); }); + handle('acceptInvite', async ({ requestId, box }) => { + const encryptedInvite = { + requestId: bs58.decode(requestId), + box: bs58.decode(box), + }; + + return await this.swarm.sendInvite({ + peerId: this.peerLinks.id, + encryptedInvite, + }, INVITE_TIMEOUT); + }); + handle('renameIdentityPair', async (params) => { const { channelId, identityKey, newName } = params; diff --git a/src/redux/actions.js b/src/redux/actions.js index 708899a..9414804 100644 --- a/src/redux/actions.js +++ b/src/redux/actions.js @@ -603,7 +603,29 @@ export function loadMessages(options) { // NOTE: Command export function invite(params) { const run = async (dispatch) => { - return await network.invite(params); + const { encryptedInvite, peerId } = await network.invite(params); + + const post = (text) => { + dispatch(appendInternalMessage({ + channelId: params.channelId, + text, + })); + }; + + post('(Sending invite...)'); + + const delay = setTimeout(() => { + post('It took unusually long to send an invite...'); + post( + 'As a fallback - consider asking your peer to run following command ' + + 'in any channel:'); + post(`\`/accept-invite ${encryptedInvite.requestId} ` + + `${encryptedInvite.box}\``); + }, 5000); + + const isSuccess = await network.sendInvite({ encryptedInvite, peerId }); + clearTimeout(delay); + return isSuccess; }; return (dispatch) => { @@ -627,6 +649,27 @@ export function invite(params) { }; } + +// NOTE: Command +export function acceptInvite(params) { + const run = async (dispatch) => { + return await network.acceptInvite(params); + }; + + return (dispatch) => { + run(dispatch).then((success) => { + if (!success) { + throw new Error('Was not waiting for an invite...'); + } + }).catch((e) => { + dispatch(addNotification({ + kind: 'error', + content: `Failed to accept invite: ` + e.message, + })); + }); + }; +} + // NOTE: Command export function displayHelp({ channelId }) { return appendInternalMessage({ diff --git a/src/redux/commands.js b/src/redux/commands.js index 45d9025..44ac74a 100644 --- a/src/redux/commands.js +++ b/src/redux/commands.js @@ -1,5 +1,5 @@ import { - invite, displayHelp, getFeedURL, renameIdentityPair, + invite, acceptInvite, displayHelp, getFeedURL, renameIdentityPair, } from './actions'; export default new Map([ @@ -11,6 +11,10 @@ export default new Map([ 'invite', { args: [ 'inviteeName', 'request' ], action: invite }, ], + [ + 'accept-invite', + { args: [ 'requestId', 'box' ], action: acceptInvite }, + ], [ 'get-feed-url', { args: [ ], action: getFeedURL }, diff --git a/src/redux/network.js b/src/redux/network.js index 9564430..60b9f5a 100644 --- a/src/redux/network.js +++ b/src/redux/network.js @@ -88,6 +88,15 @@ export default class Network { }); } + async acceptInvite({ requestId, box }) { + return await this.request('network:acceptInvite', { requestId, box }); + } + + async sendInvite({ encryptedInvite, peerId }) { + return await this.request('network:sendInvite', + { encryptedInvite, peerId }); + } + async renameIdentityPair({ channelId, identityKey, newName }) { return await this.request('network:renameIdentityPair', { channelId, identityKey, newName });