Files: af7a4df07ba10c8db16edf9a61244f671fe0fbf1 / ftu / app.js
6929 bytesRaw
1 | const { h, Value, when, resolve, computed, Struct, watch } = require('mutant') |
2 | const nest = require('depnest') |
3 | const path = require('path') |
4 | const fs = require('fs') |
5 | const { remote } = require('electron') |
6 | const insertCss = require('insert-css') |
7 | const values = require('lodash/values') |
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}`) |
13 | |
14 | var isBusy = Value(false) |
15 | var isPresentingOptions = Value(true) |
16 | |
17 | // these initial values are overwritten by the identity file. |
18 | var state = Struct({ |
19 | latestSequence: 0, |
20 | currentSequence: -1 |
21 | }) |
22 | |
23 | exports.gives = nest('ftu.app') |
24 | |
25 | exports.needs = nest({ |
26 | 'styles.css': 'reduce', |
27 | 'translations.sync.strings': 'first', |
28 | }) |
29 | |
30 | exports.create = (api) => { |
31 | return nest({ |
32 | 'ftu.app': function app() { |
33 | |
34 | const strings = api.translations.sync.strings() |
35 | |
36 | const css = values(api.styles.css()).join('\n') |
37 | insertCss(css) |
38 | |
39 | var actionButtons = h('section', [ |
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)) |
42 | ]) |
43 | |
44 | var busyMessage = h('p', strings.backup.ftu.busyMessage) |
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 | |
80 | var app = h('App', [ |
81 | h('Header', [ |
82 | windowControls() |
83 | ]), |
84 | when(isPresentingOptions, initialOptions, importProcess) |
85 | ]) |
86 | |
87 | return app |
88 | } |
89 | }) |
90 | } |
91 | |
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() { |
101 | isBusy.set(true) |
102 | const manifest = JSON.parse(fs.readFileSync(path.join(__dirname, "../manifest.json"))) |
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)) |
108 | |
109 | |
110 | electron.ipcRenderer.send('create-new-identity') |
111 | } |
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 | |
159 | function windowControls() { |
160 | if (process.platform === 'darwin') return |
161 | |
162 | const window = remote.getCurrentWindow() |
163 | const minimize = () => window.minimize() |
164 | const maximize = () => { |
165 | if (!window.isMaximized()) window.maximize() |
166 | else window.unmaximize() |
167 | } |
168 | const close = () => window.close() |
169 | |
170 | return h('div.window-controls', [ |
171 | h('img.min', { |
172 | src: assetPath('minimize.png'), |
173 | 'ev-click': minimize |
174 | }), |
175 | h('img.max', { |
176 | src: assetPath('maximize.png'), |
177 | 'ev-click': maximize |
178 | }), |
179 | h('img.close', { |
180 | src: assetPath('close.png'), |
181 | 'ev-click': close |
182 | }) |
183 | ]) |
184 | } |
185 | |
186 | |
187 | function assetPath(name) { |
188 | return path.join(__dirname, '../assets', name) |
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 | } |
Built with git-ssb-web