Files: 5d4d175f2895574c5670910407ca9436c1154f06 / ftu / app.js
7186 bytesRaw
1 | const { h, Value, when, resolve, computed, Struct, watch, throttle } = 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(throttle(state, 500), 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 | if (previousData === false) { |
75 | // there is a secret but there is no previous import data. |
76 | // so, we proceed as normal because we can't do anything else, |
77 | // it looks like a normal standard installation... |
78 | setImportData({ importing: false }) |
79 | electron.ipcRenderer.send('import-completed') |
80 | } else { |
81 | state.latestSequence.set(previousData.latestSequence) |
82 | state.currentSequence.set(previousData.currentSequence) |
83 | isPresentingOptions.set(false) |
84 | observeSequence() |
85 | } |
86 | } |
87 | |
88 | var app = h('App', [ |
89 | h('Header', [ |
90 | windowControls() |
91 | ]), |
92 | when(isPresentingOptions, initialOptions, importProcess) |
93 | ]) |
94 | |
95 | return app |
96 | } |
97 | }) |
98 | } |
99 | |
100 | electron.ipcRenderer.on('import-started', function (ev, c) { |
101 | console.log('background process is running, begin observing') |
102 | |
103 | observeSequence() |
104 | }) |
105 | |
106 | |
107 | |
108 | function actionCreateNewOne() { |
109 | isBusy.set(true) |
110 | const manifest = JSON.parse(fs.readFileSync(path.join(__dirname, "../manifest.json"))) |
111 | const manifestFile = path.join(configFolder, 'manifest.json') |
112 | if (!fs.existsSync(configFolder)) { |
113 | fs.mkdirSync(configFolder) |
114 | } |
115 | fs.writeFileSync(manifestFile, JSON.stringify(manifest)) |
116 | |
117 | |
118 | electron.ipcRenderer.send('create-new-identity') |
119 | } |
120 | |
121 | function actionImportIdentity(strings) { |
122 | const peersFile = path.join(configFolder, "gossip.json") |
123 | const secretFile = path.join(configFolder, "secret") |
124 | const manifest = JSON.parse(fs.readFileSync(path.join(__dirname, "../manifest.json"))) |
125 | const manifestFile = path.join(configFolder, 'manifest.json') |
126 | |
127 | // place the other files first |
128 | dialog.showOpenDialog( |
129 | { |
130 | title: strings.backup.import.dialog.title, |
131 | butttonLabel: strings.backup.import.dialog.label, |
132 | defaultPath: 'ticktack-identity.backup', |
133 | properties: ['openFile'] |
134 | }, |
135 | (filenames) => { |
136 | if (typeof filenames !== "undefined") { |
137 | let filename = filenames[0] |
138 | let data = JSON.parse(fs.readFileSync(filename)) |
139 | if (data.hasOwnProperty("secret") && data.hasOwnProperty("peers") && data.hasOwnProperty("latestSequence")) { |
140 | if (!fs.existsSync(configFolder)) { |
141 | fs.mkdirSync(configFolder) |
142 | } |
143 | |
144 | fs.writeFileSync(manifestFile, JSON.stringify(manifest)) |
145 | fs.writeFileSync(peersFile, JSON.stringify(data.peers), "utf8") |
146 | fs.writeFileSync(secretFile, data.secret, "utf8") |
147 | state.latestSequence.set(data.latestSequence) |
148 | state.currentSequence.set(0) |
149 | isPresentingOptions.set(false) |
150 | |
151 | data.importing = true |
152 | data.currentSequence = 0 |
153 | |
154 | setImportData(data) |
155 | |
156 | electron.ipcRenderer.send('import-identity') |
157 | } else { |
158 | console.log("> bad export file") |
159 | console.log(data) |
160 | alert("Bad Export File") |
161 | } |
162 | } |
163 | } |
164 | ) |
165 | } |
166 | |
167 | function windowControls() { |
168 | if (process.platform === 'darwin') return |
169 | |
170 | const window = remote.getCurrentWindow() |
171 | const minimize = () => window.minimize() |
172 | const maximize = () => { |
173 | if (!window.isMaximized()) window.maximize() |
174 | else window.unmaximize() |
175 | } |
176 | const close = () => window.close() |
177 | |
178 | return h('div.window-controls', [ |
179 | h('img.min', { |
180 | src: assetPath('minimize.png'), |
181 | 'ev-click': minimize |
182 | }), |
183 | h('img.max', { |
184 | src: assetPath('maximize.png'), |
185 | 'ev-click': maximize |
186 | }), |
187 | h('img.close', { |
188 | src: assetPath('close.png'), |
189 | 'ev-click': close |
190 | }) |
191 | ]) |
192 | } |
193 | |
194 | |
195 | function assetPath(name) { |
196 | return path.join(__dirname, '../assets', name) |
197 | } |
198 | |
199 | |
200 | function getImportData() { |
201 | var importFile = path.join(configFolder, 'importing.json') |
202 | if (fs.existsSync(importFile)) { |
203 | let data = JSON.parse(fs.readFileSync(importFile)) |
204 | return data || false |
205 | } else { |
206 | return false |
207 | } |
208 | } |
209 | |
210 | function setImportData(data) { |
211 | var importFile = path.join(configFolder, 'importing.json') |
212 | fs.writeFileSync(importFile, JSON.stringify(data)) |
213 | } |
214 | |
215 | function observeSequence() { |
216 | const pull = require('pull-stream') |
217 | const Client = require('ssb-client') |
218 | const config = require('../config').create().config.sync.load() |
219 | const _ = require('lodash') |
220 | |
221 | Client(config.keys, config, (err, ssbServer) => { |
222 | if (err) { |
223 | console.error('problem starting client', err) |
224 | } else { |
225 | console.log('> sbot running!!!!') |
226 | |
227 | var feedSource = ssbServer.createUserStream({ |
228 | live: true, |
229 | id: ssbServer.id |
230 | }) |
231 | |
232 | var valueLogger = pull.drain((msg) => { |
233 | let seq = _.get(msg, "value.sequence", false) |
234 | if (seq) { |
235 | state.currentSequence.set(seq) |
236 | } |
237 | }) |
238 | |
239 | pull( |
240 | feedSource, |
241 | valueLogger, |
242 | ) |
243 | |
244 | } |
245 | }) |
246 | } |
Built with git-ssb-web