Files: e6df29b999257c928b0fe5316875680a479dba2e / ftu / app.js
7970 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 manageProgress = require('./manageProgress') |
11 | const windowControls = require('../windowControls') |
12 | const ftuCss = require('./styles') |
13 | |
14 | // const config = require('../config').create().config.sync.load() |
15 | const config = { |
16 | path: path.join(os.homedir(), `.${process.env.SSB_APPNAME || process.env.ssb_appname || 'ssb'}`) |
17 | } |
18 | const SECRET_PATH = path.join(config.path, 'secret') |
19 | const MANIFEST_PATH = path.join(config.path, 'manifest.json') |
20 | const GOSSIP_PATH = path.join(config.path, 'gossip.json') |
21 | const IMPORT_PATH = path.join(config.path, 'importing.json') |
22 | |
23 | // these initial values are overwritten by the identity file. |
24 | var state = Struct({ |
25 | isPresentingOptions: true, |
26 | creatingNewIdentity: false, |
27 | mySequence: Struct({ |
28 | current: 0, |
29 | latest: 0, |
30 | latestConfirmed: false |
31 | }), |
32 | peerSequences: Struct({ |
33 | current: 0, |
34 | latest: 0 |
35 | }), |
36 | importComplete: false |
37 | }) |
38 | |
39 | state.peerSequences(console.log) |
40 | |
41 | watch(throttle(state.peersLatestSequence, 1000), console.log) |
42 | |
43 | // Note you can't want state and get updates to mySequence! |
44 | watch(throttle(state, 500), s => { |
45 | const myFeedSynced = s.mySequence.current >= s.mySequence.latest && s.mySequence.latestConfirmed |
46 | const enoughFriends = s.peerSequences.current > 0.95 * s.peerSequences.latest |
47 | |
48 | if (myFeedSynced && enoughFriends) state.importComplete.set(true) |
49 | }) |
50 | |
51 | exports.gives = nest('ftu.app') |
52 | |
53 | exports.needs = nest({ |
54 | 'styles.css': 'reduce', |
55 | 'translations.sync.strings': 'first' |
56 | }) |
57 | |
58 | exports.create = (api) => { |
59 | return nest({ |
60 | 'ftu.app': function app () { |
61 | const strings = api.translations.sync.strings() |
62 | |
63 | const css = [...values(api.styles.css()), ftuCss].join('\n') |
64 | document.head.appendChild(h('style', { innerHTML: css })) |
65 | |
66 | // This watcher is responsible for switching from FTU to Ticktack main app |
67 | watch(state.importComplete, importComplete => { |
68 | if (importComplete) electron.ipcRenderer.send('import-completed') |
69 | }) |
70 | |
71 | if (fs.existsSync(SECRET_PATH)) { |
72 | // somehow the FTU started but the identity is already in place. |
73 | // treat it as a failed import and start importing... |
74 | console.log('resuming import') |
75 | let previousData = getImportData() |
76 | if (previousData === false) { |
77 | // there is a secret but there is no previous import data. |
78 | // so, we proceed as normal because we can't do anything else, |
79 | // it looks like a normal standard installation... |
80 | setImportData({ importing: false }) |
81 | electron.ipcRenderer.send('import-completed') |
82 | } else { |
83 | state.mySequence.latest.set(previousData.mySequence.latest) |
84 | // state.peerSequences.latest.set(previousData.peerSequences.latest) // nor made in exportIdentity yet |
85 | state.isPresentingOptions.set(false) |
86 | manageProgress({ state, config }) |
87 | } |
88 | } |
89 | |
90 | var app = h('App', [ |
91 | h('Header', [ |
92 | h('img.logoName', { src: assetPath('logo_and_name.png') }), |
93 | windowControls() |
94 | ]), |
95 | when(state.isPresentingOptions, InitialOptions(), ImportProgress()) |
96 | ]) |
97 | |
98 | return app |
99 | |
100 | function InitialOptions () { |
101 | const { welcomeHeader, welcomeMessage, busyMessage, importAction, createAction } = strings.backup.ftu |
102 | |
103 | return h('Page', [ |
104 | h('div.content', [ |
105 | h('section.welcome', [ |
106 | h('h1', welcomeHeader), |
107 | h('div', welcomeMessage) |
108 | ]), |
109 | when(state.creatingNewIdentity, |
110 | h('p', busyMessage), |
111 | h('section.actionButtons', [ |
112 | h('div.left', h('Button', { 'ev-click': () => actionImportIdentity(strings) }, importAction)), |
113 | h('div.right', h('Button -strong', { 'ev-click': () => actionCreateNewOne() }, createAction)) |
114 | ]) |
115 | ) |
116 | ]) |
117 | ]) |
118 | } |
119 | |
120 | function ImportProgress () { |
121 | const { header, myFeedProgress, myFriendsProgress, details } = strings.backup.import |
122 | |
123 | return h('Page', [ |
124 | h('div.content', [ |
125 | h('h1', header), |
126 | h('h2', myFeedProgress), |
127 | h('pre', computed(state.mySequence, s => { |
128 | return progress({ |
129 | width: 42, |
130 | total: s.latest, |
131 | complete: '/', |
132 | incomplete: '-', |
133 | style: function (complete, incomplete) { |
134 | // add an arrow at the head of the completed part |
135 | return `[${complete}${incomplete}] (${s.current}/ ${s.latest})` |
136 | } |
137 | })(s.current) |
138 | })), |
139 | h('p', details), |
140 | h('h2', myFriendsProgress), |
141 | h('pre', computed(state.peerSequences, s => { |
142 | return progress({ |
143 | width: 42, |
144 | total: s.latest, |
145 | complete: '\\', |
146 | incomplete: '-', |
147 | style: function (complete, incomplete) { |
148 | // add an arrow at the head of the completed part |
149 | return `[${complete}${incomplete}] (${s.current}/ ${Math.max(s.latest, s.current)})` |
150 | } |
151 | })(s.current) |
152 | })) |
153 | ]) |
154 | ]) |
155 | } |
156 | } |
157 | }) |
158 | } |
159 | |
160 | electron.ipcRenderer.on('import-resumed', function (ev, c) { |
161 | console.log('background process is running, begin observing') |
162 | |
163 | manageProgress({ state, config }) |
164 | }) |
165 | |
166 | function actionCreateNewOne () { |
167 | state.creatingNewIdentity.set(true) |
168 | /// //////////!!!!!! |
169 | // WARNING TODO: this needs replacing with manifest exported from actual sbot running! |
170 | const manifest = JSON.parse(fs.readFileSync(path.join(__dirname, '../manifest.json'))) |
171 | /// //////////!!!!!! |
172 | if (!fs.existsSync(config.path)) { |
173 | fs.mkdirSync(config.path) |
174 | } |
175 | fs.writeFileSync(MANIFEST_PATH, JSON.stringify(manifest)) |
176 | |
177 | electron.ipcRenderer.send('create-new-identity') |
178 | } |
179 | |
180 | function actionImportIdentity (strings) { |
181 | /// /////////!!!!!! |
182 | // WARNING TODO (same as above warning) |
183 | const manifest = JSON.parse(fs.readFileSync(path.join(__dirname, '../manifest.json'))) |
184 | |
185 | // place the other files first |
186 | electron.remote.dialog.showOpenDialog( |
187 | { |
188 | title: strings.backup.import.dialog.title, |
189 | butttonLabel: strings.backup.import.dialog.label, |
190 | defaultPath: 'ticktack-identity.backup', |
191 | properties: ['openFile'] |
192 | }, |
193 | (filenames) => { |
194 | if (typeof filenames !== 'undefined') { |
195 | let filename = filenames[0] |
196 | let data = JSON.parse(fs.readFileSync(filename)) |
197 | const requiredProps = ['secret', 'gossip', 'mySequence', 'peersLatestSequence'] |
198 | |
199 | if (requiredProps.every(prop => data.hasOwnProperty(prop))) { |
200 | if (!fs.existsSync(config.path)) { |
201 | fs.mkdirSync(config.path) |
202 | } |
203 | |
204 | fs.writeFileSync(MANIFEST_PATH, JSON.stringify(manifest)) |
205 | fs.writeFileSync(GOSSIP_PATH, JSON.stringify(data.gossip), 'utf8') |
206 | fs.writeFileSync(SECRET_PATH, data.secret, 'utf8') |
207 | |
208 | state.mySequence.latest.set(data.mySequence.latest) |
209 | state.isPresentingOptions.set(false) |
210 | |
211 | data.importing = true |
212 | |
213 | setImportData(data) |
214 | |
215 | electron.ipcRenderer.send('import-identity') |
216 | } else { |
217 | console.log('> bad export file') |
218 | console.log(data) |
219 | alert('Bad Export File') |
220 | } |
221 | } |
222 | } |
223 | ) |
224 | } |
225 | |
226 | function assetPath (name) { |
227 | return path.join(__dirname, '../assets', name) |
228 | } |
229 | |
230 | function getImportData () { |
231 | if (fs.existsSync(IMPORT_PATH)) { |
232 | let data = JSON.parse(fs.readFileSync(IMPORT_PATH)) |
233 | return data || false |
234 | } else { |
235 | return false |
236 | } |
237 | } |
238 | |
239 | function setImportData (data) { |
240 | fs.writeFileSync(IMPORT_PATH, JSON.stringify(data)) |
241 | } |
242 |
Built with git-ssb-web