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