diff options
Diffstat (limited to 'packages')
-rw-r--r-- | packages/main/package.json | 5 | ||||
-rw-r--r-- | packages/main/src/controllers/initConfig.ts | 12 | ||||
-rw-r--r-- | packages/main/src/stores/Config.ts | 32 | ||||
-rw-r--r-- | packages/main/src/stores/Profile.ts | 51 | ||||
-rw-r--r-- | packages/main/src/stores/Service.ts | 63 | ||||
-rw-r--r-- | packages/main/src/stores/__tests__/Config.spec.ts | 156 | ||||
-rw-r--r-- | packages/main/src/utils/generateId.ts | 27 | ||||
-rw-r--r-- | packages/preload/src/contextBridge/__tests__/createSophieRenderer.spec.ts | 27 | ||||
-rw-r--r-- | packages/preload/src/contextBridge/createSophieRenderer.ts | 15 | ||||
-rw-r--r-- | packages/shared/src/index.ts | 14 | ||||
-rw-r--r-- | packages/shared/src/stores/Config.ts | 5 | ||||
-rw-r--r-- | packages/shared/src/stores/Profile.ts | 32 | ||||
-rw-r--r-- | packages/shared/src/stores/Service.ts | 37 |
13 files changed, 431 insertions, 45 deletions
diff --git a/packages/main/package.json b/packages/main/package.json index 862b83a..80a93b9 100644 --- a/packages/main/package.json +++ b/packages/main/package.json | |||
@@ -20,7 +20,9 @@ | |||
20 | "mobx": "^6.3.13", | 20 | "mobx": "^6.3.13", |
21 | "mobx-state-tree": "^5.1.0", | 21 | "mobx-state-tree": "^5.1.0", |
22 | "ms": "^2.1.3", | 22 | "ms": "^2.1.3", |
23 | "os-name": "^5.0.1" | 23 | "nanoid": "^3.1.30", |
24 | "os-name": "^5.0.1", | ||
25 | "slug": "^5.2.0" | ||
24 | }, | 26 | }, |
25 | "devDependencies": { | 27 | "devDependencies": { |
26 | "@jest/globals": "^27.4.6", | 28 | "@jest/globals": "^27.4.6", |
@@ -28,6 +30,7 @@ | |||
28 | "@types/lodash-es": "^4.17.5", | 30 | "@types/lodash-es": "^4.17.5", |
29 | "@types/ms": "^0.7.31", | 31 | "@types/ms": "^0.7.31", |
30 | "@types/node": "^17.0.12", | 32 | "@types/node": "^17.0.12", |
33 | "@types/slug": "^5", | ||
31 | "electron-devtools-installer": "^3.2.0", | 34 | "electron-devtools-installer": "^3.2.0", |
32 | "esbuild": "^0.14.14", | 35 | "esbuild": "^0.14.14", |
33 | "git-repo-info": "^2.1.1", | 36 | "git-repo-info": "^2.1.1", |
diff --git a/packages/main/src/controllers/initConfig.ts b/packages/main/src/controllers/initConfig.ts index 915f451..93be978 100644 --- a/packages/main/src/controllers/initConfig.ts +++ b/packages/main/src/controllers/initConfig.ts | |||
@@ -19,11 +19,11 @@ | |||
19 | */ | 19 | */ |
20 | 20 | ||
21 | import { debounce } from 'lodash-es'; | 21 | import { debounce } from 'lodash-es'; |
22 | import { applySnapshot, getSnapshot, onSnapshot } from 'mobx-state-tree'; | 22 | import { getSnapshot, onSnapshot } from 'mobx-state-tree'; |
23 | import ms from 'ms'; | 23 | import ms from 'ms'; |
24 | 24 | ||
25 | import type ConfigPersistenceService from '../services/ConfigPersistenceService'; | 25 | import type ConfigPersistenceService from '../services/ConfigPersistenceService.js'; |
26 | import type { Config, ConfigSnapshotOut } from '../stores/Config'; | 26 | import { Config, ConfigFileIn, ConfigSnapshotOut } from '../stores/Config.js'; |
27 | import type Disposer from '../utils/Disposer'; | 27 | import type Disposer from '../utils/Disposer'; |
28 | import { getLogger } from '../utils/log'; | 28 | import { getLogger } from '../utils/log'; |
29 | 29 | ||
@@ -44,12 +44,14 @@ export default async function initConfig( | |||
44 | const result = await persistenceService.readConfig(); | 44 | const result = await persistenceService.readConfig(); |
45 | if (result.found) { | 45 | if (result.found) { |
46 | try { | 46 | try { |
47 | applySnapshot(config, result.data); | 47 | // This cast is unsound if the config file is invalid, |
48 | lastSnapshotOnDisk = getSnapshot(config); | 48 | // but we'll throw an error in the end anyways. |
49 | config.loadFromConfigFile(result.data as ConfigFileIn); | ||
49 | } catch (error) { | 50 | } catch (error) { |
50 | log.error('Failed to apply config snapshot', result.data, error); | 51 | log.error('Failed to apply config snapshot', result.data, error); |
51 | } | 52 | } |
52 | } | 53 | } |
54 | lastSnapshotOnDisk = getSnapshot(config); | ||
53 | return result.found; | 55 | return result.found; |
54 | } | 56 | } |
55 | 57 | ||
diff --git a/packages/main/src/stores/Config.ts b/packages/main/src/stores/Config.ts index ca90c0c..e7fc360 100644 --- a/packages/main/src/stores/Config.ts +++ b/packages/main/src/stores/Config.ts | |||
@@ -19,14 +19,40 @@ | |||
19 | */ | 19 | */ |
20 | 20 | ||
21 | import { config as originalConfig, ThemeSource } from '@sophie/shared'; | 21 | import { config as originalConfig, ThemeSource } from '@sophie/shared'; |
22 | import { Instance } from 'mobx-state-tree'; | 22 | import { applySnapshot, Instance, SnapshotIn } from 'mobx-state-tree'; |
23 | |||
24 | import { addMissingProfileIds, PartialProfileSnapshotIn } from './Profile'; | ||
25 | import { | ||
26 | addMissingServiceIdsAndProfiles, | ||
27 | PartialServiceSnapshotIn, | ||
28 | } from './Service'; | ||
23 | 29 | ||
24 | export const config = originalConfig.actions((self) => ({ | 30 | export const config = originalConfig.actions((self) => ({ |
25 | setThemeSource(mode: ThemeSource) { | 31 | loadFromConfigFile(snapshot: ConfigFileIn): void { |
32 | const profiles = addMissingProfileIds(snapshot.profiles); | ||
33 | const services = addMissingServiceIdsAndProfiles( | ||
34 | snapshot.services, | ||
35 | profiles, | ||
36 | ); | ||
37 | applySnapshot(self, { | ||
38 | ...snapshot, | ||
39 | profiles, | ||
40 | services, | ||
41 | }); | ||
42 | }, | ||
43 | setThemeSource(mode: ThemeSource): void { | ||
26 | self.themeSource = mode; | 44 | self.themeSource = mode; |
27 | }, | 45 | }, |
28 | })); | 46 | })); |
29 | 47 | ||
30 | export interface Config extends Instance<typeof config> {} | 48 | export interface Config extends Instance<typeof config> {} |
31 | 49 | ||
32 | export type { ConfigSnapshotIn, ConfigSnapshotOut } from '@sophie/shared'; | 50 | export interface ConfigSnapshotIn extends SnapshotIn<typeof config> {} |
51 | |||
52 | export interface ConfigFileIn | ||
53 | extends Omit<ConfigSnapshotIn, 'profiles' | 'services'> { | ||
54 | profiles?: PartialProfileSnapshotIn[] | undefined; | ||
55 | services?: PartialServiceSnapshotIn[] | undefined; | ||
56 | } | ||
57 | |||
58 | export type { ConfigSnapshotOut } from '@sophie/shared'; | ||
diff --git a/packages/main/src/stores/Profile.ts b/packages/main/src/stores/Profile.ts new file mode 100644 index 0000000..4705862 --- /dev/null +++ b/packages/main/src/stores/Profile.ts | |||
@@ -0,0 +1,51 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2022 Kristóf Marussy <kristof@marussy.com> | ||
3 | * | ||
4 | * This file is part of Sophie. | ||
5 | * | ||
6 | * Sophie is free software: you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU Affero General Public License as | ||
8 | * published by the Free Software Foundation, version 3. | ||
9 | * | ||
10 | * This program is distributed in the hope that it will be useful, | ||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | * GNU Affero General Public License for more details. | ||
14 | * | ||
15 | * You should have received a copy of the GNU Affero General Public License | ||
16 | * along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
17 | * | ||
18 | * SPDX-License-Identifier: AGPL-3.0-only | ||
19 | */ | ||
20 | |||
21 | import type { ProfileSnapshotIn } from '@sophie/shared'; | ||
22 | |||
23 | import generateId from '../utils/generateId'; | ||
24 | |||
25 | export interface PartialProfileSnapshotIn | ||
26 | extends Omit<ProfileSnapshotIn, 'id'> { | ||
27 | id?: string | undefined; | ||
28 | } | ||
29 | |||
30 | export function addMissingProfileIds( | ||
31 | partialProfiles: PartialProfileSnapshotIn[] | undefined, | ||
32 | ): ProfileSnapshotIn[] { | ||
33 | return (partialProfiles ?? []).map((profile) => { | ||
34 | const { name } = profile; | ||
35 | let { id } = profile; | ||
36 | if (typeof id === 'undefined') { | ||
37 | id = generateId(name); | ||
38 | } | ||
39 | return { | ||
40 | ...profile, | ||
41 | id, | ||
42 | }; | ||
43 | }); | ||
44 | } | ||
45 | |||
46 | export type { | ||
47 | Profile, | ||
48 | ProfileSnapshotOut, | ||
49 | ProfileSnapshotIn, | ||
50 | } from '@sophie/shared'; | ||
51 | export { profile } from '@sophie/shared'; | ||
diff --git a/packages/main/src/stores/Service.ts b/packages/main/src/stores/Service.ts new file mode 100644 index 0000000..9bc6a43 --- /dev/null +++ b/packages/main/src/stores/Service.ts | |||
@@ -0,0 +1,63 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2022 Kristóf Marussy <kristof@marussy.com> | ||
3 | * | ||
4 | * This file is part of Sophie. | ||
5 | * | ||
6 | * Sophie is free software: you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU Affero General Public License as | ||
8 | * published by the Free Software Foundation, version 3. | ||
9 | * | ||
10 | * This program is distributed in the hope that it will be useful, | ||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | * GNU Affero General Public License for more details. | ||
14 | * | ||
15 | * You should have received a copy of the GNU Affero General Public License | ||
16 | * along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
17 | * | ||
18 | * SPDX-License-Identifier: AGPL-3.0-only | ||
19 | */ | ||
20 | |||
21 | import type { ServiceSnapshotIn } from '@sophie/shared'; | ||
22 | |||
23 | import generateId from '../utils/generateId'; | ||
24 | |||
25 | import type { ProfileSnapshotIn } from './Profile'; | ||
26 | |||
27 | export interface PartialServiceSnapshotIn | ||
28 | extends Omit<ServiceSnapshotIn, 'id' | 'profile'> { | ||
29 | id?: string | undefined; | ||
30 | profile?: string | undefined; | ||
31 | } | ||
32 | |||
33 | export function addMissingServiceIdsAndProfiles( | ||
34 | partialServices: PartialServiceSnapshotIn[] | undefined, | ||
35 | profiles: ProfileSnapshotIn[], | ||
36 | ): ServiceSnapshotIn[] { | ||
37 | return (partialServices ?? []).map((service) => { | ||
38 | const { name } = service; | ||
39 | let { id, profile } = service; | ||
40 | if (typeof id === 'undefined') { | ||
41 | id = generateId(name); | ||
42 | } | ||
43 | if (typeof profile === 'undefined') { | ||
44 | profile = generateId(name); | ||
45 | profiles.push({ | ||
46 | id: profile, | ||
47 | name: service.name, | ||
48 | }); | ||
49 | } | ||
50 | return { | ||
51 | ...service, | ||
52 | id, | ||
53 | profile, | ||
54 | }; | ||
55 | }); | ||
56 | } | ||
57 | |||
58 | export type { | ||
59 | Service, | ||
60 | ServiceSnapshotOut, | ||
61 | ServiceSnapshotIn, | ||
62 | } from '@sophie/shared'; | ||
63 | export { service } from '@sophie/shared'; | ||
diff --git a/packages/main/src/stores/__tests__/Config.spec.ts b/packages/main/src/stores/__tests__/Config.spec.ts new file mode 100644 index 0000000..22ccbc7 --- /dev/null +++ b/packages/main/src/stores/__tests__/Config.spec.ts | |||
@@ -0,0 +1,156 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2021-2022 Kristóf Marussy <kristof@marussy.com> | ||
3 | * | ||
4 | * This file is part of Sophie. | ||
5 | * | ||
6 | * Sophie is free software: you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU Affero General Public License as | ||
8 | * published by the Free Software Foundation, version 3. | ||
9 | * | ||
10 | * This program is distributed in the hope that it will be useful, | ||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | * GNU Affero General Public License for more details. | ||
14 | * | ||
15 | * You should have received a copy of the GNU Affero General Public License | ||
16 | * along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
17 | * | ||
18 | * SPDX-License-Identifier: AGPL-3.0-only | ||
19 | */ | ||
20 | |||
21 | import { config, Config, ConfigFileIn } from '../Config'; | ||
22 | import type { PartialProfileSnapshotIn } from '../Profile'; | ||
23 | import type { PartialServiceSnapshotIn } from '../Service'; | ||
24 | |||
25 | const profileProps: PartialProfileSnapshotIn = { | ||
26 | name: 'Test profile', | ||
27 | }; | ||
28 | |||
29 | const serviceProps: PartialServiceSnapshotIn = { | ||
30 | name: 'Test service', | ||
31 | url: 'https://example.com', | ||
32 | }; | ||
33 | |||
34 | let sut: Config; | ||
35 | |||
36 | beforeEach(() => { | ||
37 | sut = config.create(); | ||
38 | }); | ||
39 | |||
40 | describe('preprocessConfigFile', () => { | ||
41 | it('should load profiles with an ID', () => { | ||
42 | sut.loadFromConfigFile({ | ||
43 | profiles: [ | ||
44 | { | ||
45 | id: 'someId', | ||
46 | ...profileProps, | ||
47 | }, | ||
48 | ], | ||
49 | }); | ||
50 | expect(sut.profiles[0].id).toBe('someId'); | ||
51 | }); | ||
52 | |||
53 | it('should generate an ID for profiles without and ID', () => { | ||
54 | sut.loadFromConfigFile({ | ||
55 | profiles: [profileProps], | ||
56 | }); | ||
57 | expect(sut.profiles[0].id).toBeDefined(); | ||
58 | }); | ||
59 | |||
60 | it('should load services with an ID and a profile', () => { | ||
61 | sut.loadFromConfigFile({ | ||
62 | profiles: [ | ||
63 | { | ||
64 | id: 'someProfileId', | ||
65 | ...profileProps, | ||
66 | }, | ||
67 | ], | ||
68 | services: [ | ||
69 | { | ||
70 | id: 'someServiceId', | ||
71 | profile: 'someProfileId', | ||
72 | ...serviceProps, | ||
73 | }, | ||
74 | ], | ||
75 | }); | ||
76 | expect(sut.services[0].id).toBe('someServiceId'); | ||
77 | expect(sut.services[0].profile).toBe(sut.profiles[0]); | ||
78 | }); | ||
79 | |||
80 | it('should refuse to load a profile without a name', () => { | ||
81 | expect(() => { | ||
82 | sut.loadFromConfigFile({ | ||
83 | profiles: [ | ||
84 | { | ||
85 | id: 'someProfileId', | ||
86 | ...profileProps, | ||
87 | name: undefined, | ||
88 | }, | ||
89 | ], | ||
90 | } as unknown as ConfigFileIn); | ||
91 | }).toThrow(); | ||
92 | expect(sut.profiles).toHaveLength(0); | ||
93 | }); | ||
94 | |||
95 | it('should load services without an ID but with a profile', () => { | ||
96 | sut.loadFromConfigFile({ | ||
97 | profiles: [ | ||
98 | { | ||
99 | id: 'someProfileId', | ||
100 | ...profileProps, | ||
101 | }, | ||
102 | ], | ||
103 | services: [ | ||
104 | { | ||
105 | profile: 'someProfileId', | ||
106 | ...serviceProps, | ||
107 | }, | ||
108 | ], | ||
109 | }); | ||
110 | expect(sut.services[0].id).toBeDefined(); | ||
111 | expect(sut.services[0].profile).toBe(sut.profiles[0]); | ||
112 | }); | ||
113 | |||
114 | it('should create a profile for a service with an ID but no profile', () => { | ||
115 | sut.loadFromConfigFile({ | ||
116 | services: [ | ||
117 | { | ||
118 | id: 'someServiceId', | ||
119 | ...serviceProps, | ||
120 | }, | ||
121 | ], | ||
122 | }); | ||
123 | expect(sut.services[0].id).toBe('someServiceId'); | ||
124 | expect(sut.services[0].profile).toBeDefined(); | ||
125 | expect(sut.services[0].profile.name).toBe(serviceProps.name); | ||
126 | }); | ||
127 | |||
128 | it('should create a profile for a service without an ID or profile', () => { | ||
129 | sut.loadFromConfigFile({ | ||
130 | services: [ | ||
131 | { | ||
132 | ...serviceProps, | ||
133 | }, | ||
134 | ], | ||
135 | }); | ||
136 | expect(sut.services[0].id).toBeDefined(); | ||
137 | expect(sut.services[0].profile).toBeDefined(); | ||
138 | expect(sut.services[0].profile.name).toBe(serviceProps.name); | ||
139 | }); | ||
140 | |||
141 | it('should refuse to load a service without a name', () => { | ||
142 | expect(() => { | ||
143 | sut.loadFromConfigFile({ | ||
144 | services: [ | ||
145 | { | ||
146 | id: 'someServiceId', | ||
147 | ...serviceProps, | ||
148 | name: undefined, | ||
149 | }, | ||
150 | ], | ||
151 | } as unknown as ConfigFileIn); | ||
152 | }).toThrow(); | ||
153 | expect(sut.profiles).toHaveLength(0); | ||
154 | expect(sut.services).toHaveLength(0); | ||
155 | }); | ||
156 | }); | ||
diff --git a/packages/main/src/utils/generateId.ts b/packages/main/src/utils/generateId.ts new file mode 100644 index 0000000..8a87e5a --- /dev/null +++ b/packages/main/src/utils/generateId.ts | |||
@@ -0,0 +1,27 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2021-2022 Kristóf Marussy <kristof@marussy.com> | ||
3 | * | ||
4 | * This file is part of Sophie. | ||
5 | * | ||
6 | * Sophie is free software: you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU Affero General Public License as | ||
8 | * published by the Free Software Foundation, version 3. | ||
9 | * | ||
10 | * This program is distributed in the hope that it will be useful, | ||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | * GNU Affero General Public License for more details. | ||
14 | * | ||
15 | * You should have received a copy of the GNU Affero General Public License | ||
16 | * along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
17 | * | ||
18 | * SPDX-License-Identifier: AGPL-3.0-only | ||
19 | */ | ||
20 | |||
21 | import { nanoid } from 'nanoid'; | ||
22 | import slug from 'slug'; | ||
23 | |||
24 | export default function generateId(name?: string | undefined) { | ||
25 | const nameSlug = typeof name === 'undefined' ? '' : slug(name); | ||
26 | return `${nameSlug}_${nanoid()}`; | ||
27 | } | ||
diff --git a/packages/preload/src/contextBridge/__tests__/createSophieRenderer.spec.ts b/packages/preload/src/contextBridge/__tests__/createSophieRenderer.spec.ts index 88b0077..2652c4e 100644 --- a/packages/preload/src/contextBridge/__tests__/createSophieRenderer.spec.ts +++ b/packages/preload/src/contextBridge/__tests__/createSophieRenderer.spec.ts | |||
@@ -51,10 +51,6 @@ const snapshot: SharedStoreSnapshotIn = { | |||
51 | shouldUseDarkColors: true, | 51 | shouldUseDarkColors: true, |
52 | }; | 52 | }; |
53 | 53 | ||
54 | const invalidSnapshot = { | ||
55 | shouldUseDarkColors: -1, | ||
56 | } as unknown as SharedStoreSnapshotIn; | ||
57 | |||
58 | const patch: IJsonPatch = { | 54 | const patch: IJsonPatch = { |
59 | op: 'replace', | 55 | op: 'replace', |
60 | path: 'foo', | 56 | path: 'foo', |
@@ -121,14 +117,6 @@ describe('SharedStoreConnector', () => { | |||
121 | ).rejects.not.toHaveProperty('message', expect.stringMatching(/s3cr3t/)); | 117 | ).rejects.not.toHaveProperty('message', expect.stringMatching(/s3cr3t/)); |
122 | expect(listener.onSnapshot).not.toHaveBeenCalled(); | 118 | expect(listener.onSnapshot).not.toHaveBeenCalled(); |
123 | }); | 119 | }); |
124 | |||
125 | it('should not pass on invalid snapshots', async () => { | ||
126 | mocked(ipcRenderer.invoke).mockResolvedValueOnce(invalidSnapshot); | ||
127 | await expect(sut.onSharedStoreChange(listener)).rejects.toBeInstanceOf( | ||
128 | Error, | ||
129 | ); | ||
130 | expect(listener.onSnapshot).not.toHaveBeenCalled(); | ||
131 | }); | ||
132 | }); | 120 | }); |
133 | 121 | ||
134 | describe('dispatchAction', () => { | 122 | describe('dispatchAction', () => { |
@@ -220,21 +208,6 @@ describe('SharedStoreConnector', () => { | |||
220 | itDoesNotPassPatchesToTheListener(); | 208 | itDoesNotPassPatchesToTheListener(); |
221 | }); | 209 | }); |
222 | 210 | ||
223 | describe('when a listener failed to register due to an invalid snapshot', () => { | ||
224 | beforeEach(async () => { | ||
225 | mocked(ipcRenderer.invoke).mockResolvedValueOnce(invalidSnapshot); | ||
226 | try { | ||
227 | await sut.onSharedStoreChange(listener); | ||
228 | } catch { | ||
229 | // Ignore error. | ||
230 | } | ||
231 | }); | ||
232 | |||
233 | itRefusesToRegisterAnotherListener(); | ||
234 | |||
235 | itDoesNotPassPatchesToTheListener(); | ||
236 | }); | ||
237 | |||
238 | describe('when a listener failed to register due to listener error', () => { | 211 | describe('when a listener failed to register due to listener error', () => { |
239 | beforeEach(async () => { | 212 | beforeEach(async () => { |
240 | mocked(ipcRenderer.invoke).mockResolvedValueOnce(snapshot); | 213 | mocked(ipcRenderer.invoke).mockResolvedValueOnce(snapshot); |
diff --git a/packages/preload/src/contextBridge/createSophieRenderer.ts b/packages/preload/src/contextBridge/createSophieRenderer.ts index 3174fed..41accfd 100644 --- a/packages/preload/src/contextBridge/createSophieRenderer.ts +++ b/packages/preload/src/contextBridge/createSophieRenderer.ts | |||
@@ -23,8 +23,8 @@ import { | |||
23 | action, | 23 | action, |
24 | MainToRendererIpcMessage, | 24 | MainToRendererIpcMessage, |
25 | RendererToMainIpcMessage, | 25 | RendererToMainIpcMessage, |
26 | sharedStore, | ||
27 | SharedStoreListener, | 26 | SharedStoreListener, |
27 | SharedStoreSnapshotIn, | ||
28 | SophieRenderer, | 28 | SophieRenderer, |
29 | } from '@sophie/shared'; | 29 | } from '@sophie/shared'; |
30 | import { ipcRenderer } from 'electron'; | 30 | import { ipcRenderer } from 'electron'; |
@@ -66,15 +66,12 @@ class SharedStoreConnector { | |||
66 | } catch (error) { | 66 | } catch (error) { |
67 | log.error('Failed to get initial shared store snapshot', error); | 67 | log.error('Failed to get initial shared store snapshot', error); |
68 | } | 68 | } |
69 | if (success) { | 69 | if (!success) { |
70 | if (sharedStore.is(snapshot)) { | 70 | throw new Error('Failed to connect to shared store'); |
71 | listener.onSnapshot(snapshot); | ||
72 | this.listener = listener; | ||
73 | return; | ||
74 | } | ||
75 | log.error('Got invalid initial shared store snapshot', snapshot); | ||
76 | } | 71 | } |
77 | throw new Error('Failed to connect to shared store'); | 72 | // `mobx-state-tree` will validate the snapshot, so we can safely cast here. |
73 | listener.onSnapshot(snapshot as SharedStoreSnapshotIn); | ||
74 | this.listener = listener; | ||
78 | } | 75 | } |
79 | } | 76 | } |
80 | 77 | ||
diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 6383f63..9f4e9b3 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts | |||
@@ -33,6 +33,20 @@ export type { | |||
33 | export { config } from './stores/Config'; | 33 | export { config } from './stores/Config'; |
34 | 34 | ||
35 | export type { | 35 | export type { |
36 | Profile, | ||
37 | ProfileSnapshotIn, | ||
38 | ProfileSnapshotOut, | ||
39 | } from './stores/Profile'; | ||
40 | export { profile } from './stores/Profile'; | ||
41 | |||
42 | export type { | ||
43 | Service, | ||
44 | ServiceSnapshotIn, | ||
45 | ServiceSnapshotOut, | ||
46 | } from './stores/Service'; | ||
47 | export { service } from './stores/Service'; | ||
48 | |||
49 | export type { | ||
36 | SharedStore, | 50 | SharedStore, |
37 | SharedStoreListener, | 51 | SharedStoreListener, |
38 | SharedStoreSnapshotIn, | 52 | SharedStoreSnapshotIn, |
diff --git a/packages/shared/src/stores/Config.ts b/packages/shared/src/stores/Config.ts index 1d98a33..1d97e7d 100644 --- a/packages/shared/src/stores/Config.ts +++ b/packages/shared/src/stores/Config.ts | |||
@@ -22,7 +22,12 @@ import { Instance, types, SnapshotIn, SnapshotOut } from 'mobx-state-tree'; | |||
22 | 22 | ||
23 | import { themeSource } from '../schemas'; | 23 | import { themeSource } from '../schemas'; |
24 | 24 | ||
25 | import { profile } from './Profile'; | ||
26 | import { service } from './Service'; | ||
27 | |||
25 | export const config = types.model('Config', { | 28 | export const config = types.model('Config', { |
29 | profiles: types.array(profile), | ||
30 | services: types.array(service), | ||
26 | themeSource: types.optional(types.enumeration(themeSource.options), 'system'), | 31 | themeSource: types.optional(types.enumeration(themeSource.options), 'system'), |
27 | }); | 32 | }); |
28 | 33 | ||
diff --git a/packages/shared/src/stores/Profile.ts b/packages/shared/src/stores/Profile.ts new file mode 100644 index 0000000..88a0f4d --- /dev/null +++ b/packages/shared/src/stores/Profile.ts | |||
@@ -0,0 +1,32 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2021-2022 Kristóf Marussy <kristof@marussy.com> | ||
3 | * | ||
4 | * This file is part of Sophie. | ||
5 | * | ||
6 | * Sophie is free software: you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU Affero General Public License as | ||
8 | * published by the Free Software Foundation, version 3. | ||
9 | * | ||
10 | * This program is distributed in the hope that it will be useful, | ||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | * GNU Affero General Public License for more details. | ||
14 | * | ||
15 | * You should have received a copy of the GNU Affero General Public License | ||
16 | * along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
17 | * | ||
18 | * SPDX-License-Identifier: AGPL-3.0-only | ||
19 | */ | ||
20 | |||
21 | import { Instance, types, SnapshotIn, SnapshotOut } from 'mobx-state-tree'; | ||
22 | |||
23 | export const profile = types.model('Profile', { | ||
24 | id: types.identifier, | ||
25 | name: types.string, | ||
26 | }); | ||
27 | |||
28 | export interface Profile extends Instance<typeof profile> {} | ||
29 | |||
30 | export interface ProfileSnapshotIn extends SnapshotIn<typeof profile> {} | ||
31 | |||
32 | export interface ProfileSnapshotOut extends SnapshotOut<typeof profile> {} | ||
diff --git a/packages/shared/src/stores/Service.ts b/packages/shared/src/stores/Service.ts new file mode 100644 index 0000000..ed2cd9a --- /dev/null +++ b/packages/shared/src/stores/Service.ts | |||
@@ -0,0 +1,37 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2021-2022 Kristóf Marussy <kristof@marussy.com> | ||
3 | * | ||
4 | * This file is part of Sophie. | ||
5 | * | ||
6 | * Sophie is free software: you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU Affero General Public License as | ||
8 | * published by the Free Software Foundation, version 3. | ||
9 | * | ||
10 | * This program is distributed in the hope that it will be useful, | ||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | * GNU Affero General Public License for more details. | ||
14 | * | ||
15 | * You should have received a copy of the GNU Affero General Public License | ||
16 | * along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
17 | * | ||
18 | * SPDX-License-Identifier: AGPL-3.0-only | ||
19 | */ | ||
20 | |||
21 | import { Instance, types, SnapshotIn, SnapshotOut } from 'mobx-state-tree'; | ||
22 | |||
23 | import { profile } from './Profile'; | ||
24 | |||
25 | export const service = types.model('Service', { | ||
26 | id: types.identifier, | ||
27 | name: types.string, | ||
28 | profile: types.reference(profile), | ||
29 | // TODO: Remove this once recipes are added. | ||
30 | url: types.string, | ||
31 | }); | ||
32 | |||
33 | export interface Service extends Instance<typeof service> {} | ||
34 | |||
35 | export interface ServiceSnapshotIn extends SnapshotIn<typeof service> {} | ||
36 | |||
37 | export interface ServiceSnapshotOut extends SnapshotOut<typeof service> {} | ||