Commit a712df77a26a0ced340c16b0f754794049f19fe2
importing and creating identities working #122
andre alves garzia committed on 5/20/2018, 8:09:22 PMParent: e828eb68d13e6361f6c31b66a89a0b880ec6f257
Files changed
backup/async/exportIdentity.js | changed |
backup/async/importIdentity.js | added |
backup/index.js | changed |
ftu/app.js | changed |
ftu/index.js | changed |
index.js | changed |
translations/en.js | changed |
backup/async/exportIdentity.js | ||
---|---|---|
@@ -2,12 +2,11 @@ | ||
2 | 2 | const { onceTrue } = require('mutant') |
3 | 3 | const path = require('path') |
4 | 4 | const fs = require('fs') |
5 | 5 | const os = require('os') |
6 | -const homedir = os.homedir() | |
7 | -const ssbPath = `${homedir}/.ssb/` | |
8 | -const peersFile = path.join(homedir, ".ssb", "gossip.json") | |
9 | -const secretFile = path.join(homedir, ".ssb", "secret") | |
6 | +const config = require('../../config').create().config.sync.load() | |
7 | +const peersFile = path.join(config.path, "gossip.json") | |
8 | +const secretFile = path.join(config.path, "secret") | |
10 | 9 | |
11 | 10 | |
12 | 11 | exports.gives = nest('backup.async.exportIdentity') |
13 | 12 |
backup/async/importIdentity.js | ||
---|---|---|
@@ -1,0 +1,23 @@ | ||
1 | +const nest = require('depnest') | |
2 | +const { onceTrue } = require('mutant') | |
3 | +const path = require('path') | |
4 | +const fs = require('fs') | |
5 | +const os = require('os') | |
6 | +const config = require('../../config').create().config.sync.load() | |
7 | +const peersFile = path.join(config.path, "gossip.json") | |
8 | +const secretFile = path.join(config.path, "secret") | |
9 | + | |
10 | +// TODO: files should take into account env vars | |
11 | + | |
12 | +exports.gives = nest('backup.async.importIdentity') | |
13 | + | |
14 | +exports.create = function (api) { | |
15 | + return nest('backup.async.importIdentity', (importData, cb) => { | |
16 | + | |
17 | + fs.writeFileSync(peersFile, JSON.stringify(importData.peers), "utf8") | |
18 | + fs.writeFileSync(secretFile, importData.secret, "utf8") | |
19 | + | |
20 | + cb() | |
21 | + | |
22 | + }) | |
23 | +} |
backup/index.js | ||
---|---|---|
@@ -2,7 +2,8 @@ | ||
2 | 2 | html: { |
3 | 3 | backup: require('./html/backup') |
4 | 4 | }, |
5 | 5 | async: { |
6 | - exportIdentity: require('./async/exportIdentity') | |
6 | + exportIdentity: require('./async/exportIdentity'), | |
7 | + importIdentity: require('./async/importIdentity') | |
7 | 8 | } |
8 | 9 | } |
ftu/app.js | ||
---|---|---|
@@ -1,19 +1,31 @@ | ||
1 | -const { h, Value, when } = require('mutant') | |
1 | +const { h, Value, when, resolve, computed, Struct, watch } = require('mutant') | |
2 | 2 | const nest = require('depnest') |
3 | 3 | const path = require('path') |
4 | 4 | const fs = require('fs') |
5 | 5 | const { remote } = require('electron') |
6 | 6 | const insertCss = require('insert-css') |
7 | 7 | const values = require('lodash/values') |
8 | 8 | const electron = require('electron') |
9 | +const { dialog } = require('electron').remote | |
10 | +const os = require('os') | |
11 | +const appName = process.env.SSB_APPNAME || 'ssb' | |
12 | +const configFolder = path.join(os.homedir(), `.${appName}`) | |
9 | 13 | |
14 | +var isBusy = Value(false) | |
15 | +var isPresentingOptions = Value(true) | |
10 | 16 | |
17 | +// these initial values are overwritten by the identity file. | |
18 | +var state = Struct({ | |
19 | + latestSequence: 0, | |
20 | + currentSequence: -1 | |
21 | +}) | |
22 | + | |
11 | 23 | exports.gives = nest('ftu.app') |
12 | 24 | |
13 | 25 | exports.needs = nest({ |
14 | 26 | 'styles.css': 'reduce', |
15 | - 'translations.sync.strings': 'first' | |
27 | + 'translations.sync.strings': 'first', | |
16 | 28 | }) |
17 | 29 | |
18 | 30 | exports.create = (api) => { |
19 | 31 | return nest({ |
@@ -23,47 +35,128 @@ | ||
23 | 35 | |
24 | 36 | const css = values(api.styles.css()).join('\n') |
25 | 37 | insertCss(css) |
26 | 38 | |
27 | - var isBusy = Value(false) | |
28 | - | |
29 | 39 | var actionButtons = h('section', [ |
30 | - h('div.left', h('Button', strings.backup.ftu.importAction)), | |
31 | - h('div.right', h('Button', { 'ev-click': () => actionCreateNewOne(isBusy) }, strings.backup.ftu.createAction)) | |
40 | + h('div.left', h('Button', { 'ev-click': () => actionImportIdentity(strings) }, strings.backup.ftu.importAction)), | |
41 | + h('div.right', h('Button', { 'ev-click': () => actionCreateNewOne() }, strings.backup.ftu.createAction)) | |
32 | 42 | ]) |
33 | 43 | |
34 | 44 | var busyMessage = h('p', strings.backup.ftu.busyMessage) |
35 | 45 | |
46 | + var initialOptions = h('Page -ftu', [ | |
47 | + h('div.content', [ | |
48 | + h('h1', strings.backup.ftu.welcomeHeader), | |
49 | + h('p', strings.backup.ftu.welcomeMessage), | |
50 | + when(isBusy, busyMessage, actionButtons) | |
51 | + ]) | |
52 | + ]) | |
53 | + | |
54 | + var importProcess = h('Page -ftu', [ | |
55 | + h('div.content', [ | |
56 | + h('h1', strings.backup.import.header), | |
57 | + h('p', [strings.backup.import.synchronizeMessage, state.currentSequence, '/', state.latestSequence]), | |
58 | + ]) | |
59 | + ]) | |
60 | + | |
61 | + // This watcher is responsible for switching from FTU to Ticktack main app | |
62 | + watch(state, s => { | |
63 | + if (s.currentSequence >= s.latestSequence) { | |
64 | + console.log('all imported') | |
65 | + electron.ipcRenderer.send('import-completed') | |
66 | + } | |
67 | + }) | |
68 | + | |
69 | + if (fs.existsSync(path.join(configFolder, "secret"))) { | |
70 | + // somehow the FTU started but the identity is already in place. | |
71 | + // treat it as a failed import and start importing... | |
72 | + console.log('resuming import') | |
73 | + let previousData = getImportData() | |
74 | + state.latestSequence.set(previousData.latestSequence) | |
75 | + state.currentSequence.set(previousData.currentSequence) | |
76 | + isPresentingOptions.set(false) | |
77 | + observeSequence() | |
78 | + } | |
79 | + | |
36 | 80 | var app = h('App', [ |
37 | 81 | h('Header', [ |
38 | 82 | windowControls() |
39 | 83 | ]), |
40 | - h('Page -ftu', [ | |
41 | - h('div.content', [ | |
42 | - h('h1', strings.backup.ftu.welcomeHeader), | |
43 | - h('p', strings.backup.ftu.welcomeMessage), | |
44 | - when(isBusy, busyMessage, actionButtons) | |
45 | - ]) | |
46 | - ]) | |
84 | + when(isPresentingOptions, initialOptions, importProcess) | |
47 | 85 | ]) |
48 | 86 | |
49 | 87 | return app |
50 | 88 | } |
51 | - | |
52 | 89 | }) |
53 | - | |
54 | 90 | } |
55 | 91 | |
56 | -function actionCreateNewOne(isBusy) { | |
92 | +electron.ipcRenderer.on('import-started', function (ev, c) { | |
93 | + console.log('background process is running, begin observing') | |
94 | + | |
95 | + observeSequence() | |
96 | +}) | |
97 | + | |
98 | + | |
99 | + | |
100 | +function actionCreateNewOne() { | |
57 | 101 | isBusy.set(true) |
58 | - const config = require('../config').create().config.sync.load() | |
59 | 102 | const manifest = JSON.parse(fs.readFileSync(path.join(__dirname, "../manifest.json"))) |
60 | - fs.writeFileSync(path.join(config.path, 'manifest.json'), JSON.stringify(manifest)) | |
103 | + const manifestFile = path.join(configFolder, 'manifest.json') | |
104 | + if (!fs.existsSync(configFolder)) { | |
105 | + fs.mkdirSync(configFolder) | |
106 | + } | |
107 | + fs.writeFileSync(manifestFile, JSON.stringify(manifest)) | |
61 | 108 | |
62 | 109 | |
63 | 110 | electron.ipcRenderer.send('create-new-identity') |
64 | 111 | } |
65 | 112 | |
113 | +function actionImportIdentity(strings) { | |
114 | + const peersFile = path.join(configFolder, "gossip.json") | |
115 | + const secretFile = path.join(configFolder, "secret") | |
116 | + const manifest = JSON.parse(fs.readFileSync(path.join(__dirname, "../manifest.json"))) | |
117 | + const manifestFile = path.join(configFolder, 'manifest.json') | |
118 | + | |
119 | + // place the other files first | |
120 | + dialog.showOpenDialog( | |
121 | + { | |
122 | + title: strings.backup.import.dialog.title, | |
123 | + butttonLabel: strings.backup.import.dialog.label, | |
124 | + defaultPath: 'ticktack-identity.backup', | |
125 | + properties: ['openFile'] | |
126 | + }, | |
127 | + (filenames) => { | |
128 | + if (typeof filenames !== "undefined") { | |
129 | + let filename = filenames[0] | |
130 | + let data = JSON.parse(fs.readFileSync(filename)) | |
131 | + if (data.hasOwnProperty("secret") && data.hasOwnProperty("peers") && data.hasOwnProperty("latestSequence")) { | |
132 | + if (!fs.existsSync(configFolder)) { | |
133 | + fs.mkdirSync(configFolder) | |
134 | + } | |
135 | + | |
136 | + fs.writeFileSync(manifestFile, JSON.stringify(manifest)) | |
137 | + fs.writeFileSync(peersFile, JSON.stringify(data.peers), "utf8") | |
138 | + fs.writeFileSync(secretFile, data.secret, "utf8") | |
139 | + state.latestSequence.set(data.latestSequence) | |
140 | + state.currentSequence.set(0) | |
141 | + isPresentingOptions.set(false) | |
142 | + | |
143 | + data.importing = true | |
144 | + data.currentSequence = 0 | |
145 | + | |
146 | + setImportData(data) | |
147 | + | |
148 | + electron.ipcRenderer.send('import-identity') | |
149 | + } else { | |
150 | + console.log("> bad export file") | |
151 | + console.log(data) | |
152 | + alert("Bad Export File") | |
153 | + } | |
154 | + } | |
155 | + } | |
156 | + ) | |
157 | +} | |
158 | + | |
66 | 159 | function windowControls() { |
67 | 160 | if (process.platform === 'darwin') return |
68 | 161 | |
69 | 162 | const window = remote.getCurrentWindow() |
@@ -93,4 +186,58 @@ | ||
93 | 186 | |
94 | 187 | function assetPath(name) { |
95 | 188 | return path.join(__dirname, '../assets', name) |
96 | 189 | } |
190 | + | |
191 | + | |
192 | +function getImportData() { | |
193 | + var importFile = path.join(configFolder, 'importing.json') | |
194 | + if (fs.existsSync(importFile)) { | |
195 | + let data = JSON.parse(fs.readFileSync(importFile)) | |
196 | + return data || false | |
197 | + } else { | |
198 | + return false | |
199 | + } | |
200 | +} | |
201 | + | |
202 | +function setImportData(data) { | |
203 | + var importFile = path.join(configFolder, 'importing.json') | |
204 | + fs.writeFileSync(importFile, JSON.stringify(data)) | |
205 | +} | |
206 | + | |
207 | +function observeSequence() { | |
208 | + const pull = require('pull-stream') | |
209 | + const Client = require('ssb-client') | |
210 | + const config = require('../config').create().config.sync.load() | |
211 | + const _ = require('lodash') | |
212 | + | |
213 | + Client(config.keys, config, (err, ssbServer) => { | |
214 | + if (err) { | |
215 | + console.error('problem starting client', err) | |
216 | + } else { | |
217 | + console.log('> sbot running!!!!') | |
218 | + ssbServer.whoami((err, data) => { | |
219 | + console.log("whoami", data.id) | |
220 | + | |
221 | + var feedSource = ssbServer.createUserStream({ | |
222 | + live: true, | |
223 | + id: data.id | |
224 | + }) | |
225 | + | |
226 | + var valueLogger = pull.drain((msg) => { | |
227 | + console.log("msg", msg) | |
228 | + | |
229 | + let seq = _.get(msg, "value.sequence", false) | |
230 | + if (seq) { | |
231 | + state.currentSequence.set(seq) | |
232 | + } | |
233 | + }) | |
234 | + | |
235 | + pull( | |
236 | + feedSource, | |
237 | + valueLogger, | |
238 | + ) | |
239 | + | |
240 | + }) | |
241 | + } | |
242 | + }) | |
243 | +} |
ftu/index.js | ||
---|---|---|
@@ -14,9 +14,9 @@ | ||
14 | 14 | // need some modules first |
15 | 15 | { |
16 | 16 | styles: require('../styles'), |
17 | 17 | settings: require('patch-settings'), |
18 | - translations: require('../translations/sync') | |
18 | + translations: require('../translations/sync'), | |
19 | 19 | }, |
20 | 20 | { |
21 | 21 | app: require('./app') |
22 | 22 | } |
index.js | ||
---|---|---|
@@ -3,14 +3,15 @@ | ||
3 | 3 | var electron = require('electron') |
4 | 4 | var Menu = electron.Menu |
5 | 5 | var Path = require('path') |
6 | 6 | |
7 | -// FTU needs | |
7 | +// First-Time User Experience (FTU) needs the items below | |
8 | 8 | const fs = require('fs') |
9 | -const Config = require('ssb-config/inject') | |
10 | -const appName = process.env.ssb_appname || 'ssb' | |
11 | -const config = Config(appName) | |
12 | -const isInstalled = fs.existsSync(Path.join(config.path, 'secret')) | |
9 | +const path = require('path') | |
10 | +const os = require('os') | |
11 | +const appName = process.env.SSB_APPNAME || 'ssb' | |
12 | +const configFolder = path.join(os.homedir(), `.${appName}`) | |
13 | +const isInstalled = fs.existsSync(Path.join(configFolder, 'secret')) | |
13 | 14 | |
14 | 15 | var windows = {} |
15 | 16 | var quitting = false |
16 | 17 | |
@@ -39,9 +40,8 @@ | ||
39 | 40 | } |
40 | 41 | |
41 | 42 | Menu.setApplicationMenu(Menu.buildFromTemplate(menu)) |
42 | 43 | |
43 | - // TODO: FTU must happen before this part. | |
44 | 44 | if (!isInstalled) { |
45 | 45 | console.log('Ticktack or SSB not installed, run FTU') |
46 | 46 | openFTUWindow() |
47 | 47 | } else { |
@@ -50,15 +50,38 @@ | ||
50 | 50 | |
51 | 51 | // FTU told app to create new identity, so proceed as normal |
52 | 52 | electron.ipcMain.once('create-new-identity', function (ev) { |
53 | 53 | console.log('create new identity') |
54 | + setImportRunningFlag(false) | |
54 | 55 | startBackgroundProcess() |
55 | 56 | }) |
56 | 57 | |
58 | + // FTU told app to import some identity, need to start sbot and keep FTU running | |
59 | + electron.ipcMain.once('import-identity', function (ev) { | |
60 | + console.log('import identity') | |
61 | + setImportRunningFlag(true) | |
62 | + startBackgroundProcess() | |
63 | + }) | |
64 | + | |
65 | + // FTU import finished, ready to start main window | |
66 | + electron.ipcMain.once('import-completed', function (ev) { | |
67 | + console.log('> import finished, opening main window') | |
68 | + setImportRunningFlag(false) | |
69 | + openMainWindow() | |
70 | + }) | |
71 | + | |
57 | 72 | // wait until server has started before opening main window |
58 | 73 | electron.ipcMain.once('server-started', function (ev, config) { |
59 | - console.log("> Opening main window") | |
60 | - openMainWindow() | |
74 | + let keepFTURunning = getImportRunningFlag(false) | |
75 | + if (!keepFTURunning) { | |
76 | + console.log("> Opening main window") | |
77 | + openMainWindow() | |
78 | + } else { | |
79 | + // sbot started but we're importing an older identity, need | |
80 | + // to tell FTU to wait for sync. | |
81 | + openFTUWindow() | |
82 | + windows.ftu.webContents.send('import-started') | |
83 | + } | |
61 | 84 | }) |
62 | 85 | |
63 | 86 | electron.app.on('before-quit', function () { |
64 | 87 | quitting = true |
@@ -193,4 +216,26 @@ | ||
193 | 216 | |
194 | 217 | window.loadURL('file://' + Path.join(__dirname, 'assets', 'base.html')) |
195 | 218 | return window |
196 | 219 | } |
220 | + | |
221 | +function getImportRunningFlag(defaultValue) { | |
222 | + var importFile = Path.join(configFolder, 'importing.json') | |
223 | + if (fs.existsSync(importFile)) { | |
224 | + let data = JSON.parse(fs.readFileSync(importFile)) | |
225 | + return data.importing || defaultValue | |
226 | + } else { | |
227 | + return defaultValue | |
228 | + } | |
229 | +} | |
230 | + | |
231 | +function setImportRunningFlag(v) { | |
232 | + let data = {} | |
233 | + var importFile = Path.join(configFolder, 'importing.json') | |
234 | + if (fs.existsSync(importFile)) { | |
235 | + data = JSON.parse(fs.readFileSync(importFile)) | |
236 | + } | |
237 | + | |
238 | + data.importing = v | |
239 | + | |
240 | + fs.writeFileSync(importFile, JSON.stringify(data)) | |
241 | +} |
translations/en.js | ||
---|---|---|
@@ -189,9 +189,15 @@ | ||
189 | 189 | title: 'Export Identity' |
190 | 190 | } |
191 | 191 | }, |
192 | 192 | import: { |
193 | - importAction: 'Import Identity' | |
193 | + header: 'Import identity', | |
194 | + importAction: 'Import Identity', | |
195 | + synchronizeMessage: 'Synchronizing feed: ', | |
196 | + dialog: { | |
197 | + label: 'Import Identity', | |
198 | + title: 'Import Identity' | |
199 | + } | |
194 | 200 | } |
195 | 201 | }, |
196 | 202 | languages: { |
197 | 203 | en: 'English', |
Built with git-ssb-web