forked from hplush/slowreader
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathenvironment.ts
More file actions
221 lines (181 loc) · 5.28 KB
/
environment.ts
File metadata and controls
221 lines (181 loc) · 5.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
// Dependency Injection to change behavior in different environment
// (web, mobile native, tests, etc).
import type { ClientOptions } from '@logux/client'
import type { TestServer } from '@logux/server'
import type { TranslationLoader } from '@nanostores/i18n'
import {
type PersistentEvents,
type PersistentStore,
setPersistentEngine
} from '@nanostores/persistent'
import { atom, type ReadableAtom, type StoreValue } from 'nanostores'
import type { BaseRouter, Route, Routes } from './router.ts'
interface LogStoreCreator {
(): ClientOptions['store']
}
export type NetworkType = 'free' | 'paid' | 'unknown' | undefined
export interface NetworkTypeDetector {
(): {
saveData: boolean | undefined
type: NetworkType
}
}
type NormalizeParams<Params> = {
[K in keyof Params]: Params[K] extends number ? Params[K] : string
}
type RouterRoutes<Router extends BaseRouter> = {
[R in Exclude<StoreValue<Router>, undefined> as R['route']]: R['params']
}
type ExactType<Good, A, B> = A extends B ? (B extends A ? Good : never) : never
type ValidateRouter<Router extends BaseRouter> = ExactType<
Router,
NormalizeParams<RouterRoutes<Router>>,
NormalizeParams<Routes>
>
interface EnvironmentListener {
(env: Environment): (() => void) | void
}
interface ErrorEvents {
addEventListener(
event: 'unhandledrejection',
listener: (event: { reason: unknown }) => void
): void
}
export interface SavedPassword {
secret: string
userId: string
}
export interface Environment {
/**
* Smart store with current URL (or similar abstraction in your environment).
*/
baseRouter: BaseRouter
/**
* Clean log and settings store from all data. It will be called on exit
* or profile deletion.
*/
cleanStorage(): void
/**
* Object like `window` in web or `process` in Node.js to track unhandled
* errors.
*
* For instance, we are using it to catch not-found errors.
*/
errorEvents: ErrorEvents
/**
* Restore server’s session token saves in `saveSession()`.
*/
getSession(): string | undefined
/**
* Smart store taking user’s language from system.
*/
locale: ReadableAtom<string>
/**
* Persistent storage for Logux log.
*/
logStoreCreator: LogStoreCreator
/**
* Detect network type to not download images over expensive tariff.
*/
networkType: NetworkTypeDetector
/**
* Change current URL.
*/
openRoute(page: Route, redirect?: boolean): void
/**
* Restart app after sign-out to be sure that all in-memory caches are clean.
*/
restartApp(): void
/**
* Ask user to save file to their file system.
*/
saveFile(filename: string, content: Blob): void
/**
* Save credentials to system's password manager.
*/
savePassword(fields: SavedPassword): Promise<void>
/**
* Save server's session token to some secure storage.
* For instance, in web we are putting it to httpOnly cookie,
* which can't be accessed from JS code.
*/
saveSession(session: string | undefined): void
/**
* Hostname (without protocol) of default Slow Reader server.
*/
server: string | TestServer
/**
* Load app's translation. Based on Nano Stores I18n API.
*/
translationLoader: TranslationLoader
/**
* Print warning to help in debugging. Should be not visible by regular user.
*/
warn(error: unknown): void
}
export type EnvironmentAndStore = {
/**
* Web `storage` event like API to subscribe for settings changes.
*/
persistentEvents: PersistentEvents
/**
* `localStorage`-like API to keep per-client persistent settings.
*/
persistentStore: PersistentStore
} & Environment
let currentEnvironment: Environment | undefined
let listeners: EnvironmentListener[] = []
let unbinds: ((() => void) | void)[] = []
function runEnvListener(listener: EnvironmentListener): void {
unbinds.push(listener(currentEnvironment!))
}
/**
* Wait for environment being set and re-run on every environment change.
*/
export function onEnvironment(cb: EnvironmentListener): void {
/* node:coverage ignore next 3 */
if (currentEnvironment) {
runEnvListener(cb)
}
listeners.push(cb)
}
export function setupEnvironment<Router extends BaseRouter>(
env: {
baseRouter: ValidateRouter<Router>
} & EnvironmentAndStore
): void {
for (let unbind of unbinds) unbind?.()
setPersistentEngine(env.persistentStore, env.persistentEvents)
currentEnvironment = {
baseRouter: env.baseRouter,
cleanStorage: env.cleanStorage,
errorEvents: env.errorEvents,
getSession: env.getSession,
locale: env.locale,
logStoreCreator: env.logStoreCreator,
networkType: env.networkType,
openRoute: env.openRoute,
restartApp: env.restartApp,
saveFile: env.saveFile,
savePassword: env.savePassword,
saveSession: env.saveSession,
server: env.server,
translationLoader: env.translationLoader,
warn: env.warn
}
for (let listener of listeners) {
runEnvListener(listener)
}
}
export function getEnvironment(): Environment {
/* node:coverage ignore next 3 */
if (!currentEnvironment) {
throw new Error('No Slow Reader environment')
}
return currentEnvironment
}
export type LayoutType = 'desktop' | 'mobile' | 'tablet'
export const layoutType = atom<LayoutType>('desktop')
export function setLayoutType(type: LayoutType): void {
layoutType.set(type)
}