Files: 3d4eca03f309655578710ab4453f309c514268d1 / ftu / app.js
6760 bytesRaw
1 | const { h, Value, when, computed, Struct, watch, throttle } = require('mutant') |
2 | const nest = require('depnest') |
3 | const path = require('path') |
4 | const fs = require('fs') |
5 | const electron = require('electron') |
6 | const os = require('os') |
7 | const progress = require('progress-string') |
8 | const values = require('lodash/values') |
9 | |
10 | const observeSequence = require('./observeSequence') |
11 | const windowControls = require('../windowControls') |
12 | const ftuCss = require('./styles') |
13 | |
14 | const appName = process.env.SSB_APPNAME || 'ssb' |
15 | const CONFIG_FOLDER = path.join(os.homedir(), `.${appName}`) |
16 | const IMPORT_FILE = path.join(CONFIG_FOLDER, 'importing.json') |
17 | |
18 | var isBusy = Value(false) |
19 | var isPresentingOptions = Value(true) |
20 | var checkerTimeout |
21 | |
22 | // these initial values are overwritten by the identity file. |
23 | var state = Struct({ |
24 | latestSequence: 0, |
25 | confirmedRemotely: false, |
26 | currentSequence: -1 |
27 | }) |
28 | |
29 | exports.gives = nest('ftu.app') |
30 | |
31 | exports.needs = nest({ |
32 | 'styles.css': 'reduce', |
33 | 'translations.sync.strings': 'first' |
34 | }) |
35 | |
36 | exports.create = (api) => { |
37 | return nest({ |
38 | 'ftu.app': function app () { |
39 | const strings = api.translations.sync.strings() |
40 | |
41 | const css = [...values(api.styles.css()), ftuCss].join('\n') |
42 | document.head.appendChild(h('style', { innerHTML: css })) |
43 | |
44 | // This watcher is responsible for switching from FTU to Ticktack main app |
45 | watch(throttle(state, 500), s => { |
46 | if (s.currentSequence >= s.latestSequence && s.confirmedRemotely) { |
47 | console.log('all imported') |
48 | clearTimeout(checkerTimeout) |
49 | electron.ipcRenderer.send('import-completed') |
50 | } |
51 | }) |
52 | |
53 | if (fs.existsSync(path.join(CONFIG_FOLDER, 'secret'))) { |
54 | // somehow the FTU started but the identity is already in place. |
55 | // treat it as a failed import and start importing... |
56 | console.log('resuming import') |
57 | let previousData = getImportData() |
58 | if (previousData === false) { |
59 | // there is a secret but there is no previous import data. |
60 | // so, we proceed as normal because we can't do anything else, |
61 | // it looks like a normal standard installation... |
62 | setImportData({ importing: false }) |
63 | electron.ipcRenderer.send('import-completed') |
64 | } else { |
65 | state.latestSequence.set(previousData.latestSequence) |
66 | state.currentSequence.set(previousData.currentSequence) |
67 | isPresentingOptions.set(false) |
68 | observeSequence({ state, timeout: checkerTimeout }) |
69 | } |
70 | } |
71 | |
72 | var app = h('App', [ |
73 | h('Header', [ |
74 | h('img.logoName', { src: assetPath('logo_and_name.png') }), |
75 | windowControls() |
76 | ]), |
77 | when(isPresentingOptions, InitialOptions(), ImportProgress()) |
78 | ]) |
79 | |
80 | return app |
81 | |
82 | function InitialOptions () { |
83 | const { welcomeHeader, welcomeMessage, busyMessage, importAction, createAction } = strings.backup.ftu |
84 | |
85 | return h('Page', [ |
86 | h('div.content', [ |
87 | h('section.welcome', [ |
88 | h('h1', welcomeHeader), |
89 | h('div', welcomeMessage) |
90 | ]), |
91 | when(isBusy, |
92 | h('p', busyMessage), |
93 | h('section.actionButtons', [ |
94 | h('div.left', h('Button', { 'ev-click': () => actionImportIdentity(strings) }, importAction)), |
95 | h('div.right', h('Button -strong', { 'ev-click': () => actionCreateNewOne() }, createAction)) |
96 | ]) |
97 | ) |
98 | ]) |
99 | ]) |
100 | } |
101 | |
102 | function ImportProgress () { |
103 | const { header, synchronizeMessage, details } = strings.backup.import |
104 | |
105 | return h('Page', [ |
106 | h('div.content', [ |
107 | h('h1', header), |
108 | h('p', synchronizeMessage), |
109 | h('pre', computed(state, s => { |
110 | return progress({ |
111 | width: 42, |
112 | total: s.latestSequence, |
113 | style: function (complete, incomplete) { |
114 | // add an arrow at the head of the completed part |
115 | return `${complete}>${incomplete} (${s.currentSequence}/ ${s.latestSequence})` |
116 | } |
117 | })(s.currentSequence) |
118 | })), |
119 | h('p', details) |
120 | ]) |
121 | ]) |
122 | } |
123 | } |
124 | }) |
125 | } |
126 | |
127 | electron.ipcRenderer.on('import-resumed', function (ev, c) { |
128 | console.log('background process is running, begin observing') |
129 | |
130 | observeSequence({ state, timeout: checkerTimeout }) |
131 | }) |
132 | |
133 | function actionCreateNewOne () { |
134 | isBusy.set(true) |
135 | const manifest = JSON.parse(fs.readFileSync(path.join(__dirname, '../manifest.json'))) |
136 | const manifestFile = path.join(CONFIG_FOLDER, 'manifest.json') |
137 | if (!fs.existsSync(CONFIG_FOLDER)) { |
138 | fs.mkdirSync(CONFIG_FOLDER) |
139 | } |
140 | fs.writeFileSync(manifestFile, JSON.stringify(manifest)) |
141 | |
142 | electron.ipcRenderer.send('create-new-identity') |
143 | } |
144 | |
145 | function actionImportIdentity (strings) { |
146 | const peersFile = path.join(CONFIG_FOLDER, 'gossip.json') |
147 | const secretFile = path.join(CONFIG_FOLDER, 'secret') |
148 | const manifest = JSON.parse(fs.readFileSync(path.join(__dirname, '../manifest.json'))) |
149 | const manifestFile = path.join(CONFIG_FOLDER, 'manifest.json') |
150 | |
151 | // place the other files first |
152 | electron.remote.dialog.showOpenDialog( |
153 | { |
154 | title: strings.backup.import.dialog.title, |
155 | butttonLabel: strings.backup.import.dialog.label, |
156 | defaultPath: 'ticktack-identity.backup', |
157 | properties: ['openFile'] |
158 | }, |
159 | (filenames) => { |
160 | if (typeof filenames !== 'undefined') { |
161 | let filename = filenames[0] |
162 | let data = JSON.parse(fs.readFileSync(filename)) |
163 | if (data.hasOwnProperty('secret') && data.hasOwnProperty('peers') && data.hasOwnProperty('latestSequence')) { |
164 | if (!fs.existsSync(CONFIG_FOLDER)) { |
165 | fs.mkdirSync(CONFIG_FOLDER) |
166 | } |
167 | |
168 | fs.writeFileSync(manifestFile, JSON.stringify(manifest)) |
169 | fs.writeFileSync(peersFile, JSON.stringify(data.peers), 'utf8') |
170 | fs.writeFileSync(secretFile, data.secret, 'utf8') |
171 | state.latestSequence.set(data.latestSequence) |
172 | state.currentSequence.set(0) |
173 | isPresentingOptions.set(false) |
174 | |
175 | data.importing = true |
176 | data.currentSequence = 0 |
177 | |
178 | setImportData(data) |
179 | |
180 | electron.ipcRenderer.send('import-identity') |
181 | } else { |
182 | console.log('> bad export file') |
183 | console.log(data) |
184 | alert('Bad Export File') |
185 | } |
186 | } |
187 | } |
188 | ) |
189 | } |
190 | |
191 | function assetPath (name) { |
192 | return path.join(__dirname, '../assets', name) |
193 | } |
194 | |
195 | function getImportData () { |
196 | if (fs.existsSync(IMPORT_FILE)) { |
197 | let data = JSON.parse(fs.readFileSync(IMPORT_FILE)) |
198 | return data || false |
199 | } else { |
200 | return false |
201 | } |
202 | } |
203 | |
204 | function setImportData (data) { |
205 | fs.writeFileSync(IMPORT_FILE, JSON.stringify(data)) |
206 | } |
207 |
Built with git-ssb-web