@axium/client 0.36.7 → 0.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/cli/cache.d.ts +1 -0
  2. package/dist/cli/cache.js +3 -14
  3. package/dist/cli/config.d.ts +1 -0
  4. package/dist/cli/config.js +2 -0
  5. package/dist/cli/sync.d.ts +2 -5
  6. package/dist/cli/sync.js +2 -4
  7. package/dist/locales.d.ts +3 -270
  8. package/dist/locales.js +5 -17
  9. package/dist/socket.js +4 -5
  10. package/dist/sync.d.ts +22 -0
  11. package/dist/sync.js +67 -0
  12. package/dist/themes.d.ts +2 -0
  13. package/dist/themes.js +34 -0
  14. package/dist/user.d.ts +1 -0
  15. package/dist/user.js +5 -0
  16. package/dist/web/features.d.ts +17 -0
  17. package/dist/web/features.js +47 -0
  18. package/dist/web/hooks.d.ts +21 -0
  19. package/dist/web/hooks.js +33 -0
  20. package/dist/{gui → web}/index.d.ts +4 -0
  21. package/dist/{gui → web}/index.js +4 -0
  22. package/dist/web/locales.d.ts +6 -0
  23. package/dist/web/locales.js +21 -0
  24. package/dist/web/pwa.d.ts +44 -0
  25. package/dist/web/pwa.js +138 -0
  26. package/dist/web/service-worker.d.ts +1 -0
  27. package/dist/web/service-worker.js +66 -0
  28. package/dist/web/utils.d.ts +1 -0
  29. package/dist/web/utils.js +4 -0
  30. package/lib/ClipboardCopy.svelte +1 -1
  31. package/lib/PWAIndicator.svelte +22 -0
  32. package/lib/ZodForm.svelte +10 -1
  33. package/lib/ZodInput.svelte +4 -1
  34. package/lib/attachments/context-menu.ts +1 -1
  35. package/lib/attachments/selection.ts +1 -1
  36. package/lib/index.ts +1 -0
  37. package/lib/reactive/index.svelte.ts +1 -0
  38. package/lib/reactive/sync.svelte.ts +45 -0
  39. package/lib/toast.ts +1 -1
  40. package/lib/tsconfig.json +1 -1
  41. package/locales/en.json +11 -1
  42. package/package.json +3 -3
  43. /package/dist/{gui → web}/animate.d.ts +0 -0
  44. /package/dist/{gui → web}/animate.js +0 -0
  45. /package/dist/{gui → web}/clipboard.d.ts +0 -0
  46. /package/dist/{gui → web}/clipboard.js +0 -0
  47. /package/dist/{gui → web}/controls.d.ts +0 -0
  48. /package/dist/{gui → web}/controls.js +0 -0
  49. /package/dist/{gui → web}/mobile.d.ts +0 -0
  50. /package/dist/{gui → web}/mobile.js +0 -0
@@ -39,6 +39,7 @@ export declare const meta: Handle<z.ZodObject<{
39
39
  emailVerified: z.ZodOptional<z.ZodNullable<z.ZodCoercedDate<unknown>>>;
40
40
  preferences: z.ZodLazy<z.ZodObject<{
41
41
  debug: z.ZodDefault<z.ZodBoolean>;
42
+ theme: z.ZodDefault<z.ZodLiteral<"default" | "light" | "forest" | "midnight" | "beach" | "cherry" | "volcano">>;
42
43
  }, z.core.$strip>>;
43
44
  roles: z.ZodArray<z.ZodString>;
44
45
  tags: z.ZodArray<z.ZodString>;
package/dist/cli/cache.js CHANGED
@@ -6,6 +6,7 @@ import { dirname, join, resolve } from 'node:path/posix';
6
6
  import * as z from 'zod';
7
7
  import { CacheData } from '../cache.js';
8
8
  import { fetchAPI } from '../requests.js';
9
+ import { applyDiff } from '../sync.js';
9
10
  import { apiUserCache, getCurrentSession } from '../user.js';
10
11
  export const dir = join(process.env.XDG_CACHE_HOME || join(homedir(), '.cache'), 'axium');
11
12
  mkdirSync(dir, { recursive: true });
@@ -90,20 +91,8 @@ export const sync = useAt({
90
91
  if (!sync)
91
92
  return await fetchAPI('GET', 'sync/init');
92
93
  const diff = await fetchAPI('GET', 'sync', { since: sync.index });
93
- const deleted = new Set(diff.deleted);
94
- const objects = sync.objects.filter(o => !deleted.has(o.id));
95
- const existing = Object.fromEntries(objects.map(o => [o.id, o]));
96
- for (const obj of diff.created)
97
- objects.push(obj);
98
- for (const updated of diff.updated) {
99
- const base = existing[updated.id];
100
- if (!base)
101
- throw new ReferenceError("Can not update object because it isn't cached");
102
- if (base.$type !== updated.$type)
103
- throw new ReferenceError(`Type mismatch whilst updating cache object: currently ${base.$type}, incoming ${updated.$type}`);
104
- Object.assign(base, updated);
105
- }
106
- return { objects, index: diff.index };
94
+ applyDiff(sync.objects, diff);
95
+ return { objects: sync.objects, index: diff.index };
107
96
  },
108
97
  async isValid({ index }) {
109
98
  const md = await fetchAPI('GET', 'sync/metadata');
@@ -13,6 +13,7 @@ export declare function session(): {
13
13
  username: string;
14
14
  preferences: {
15
15
  debug: boolean;
16
+ theme: "default" | "light" | "forest" | "midnight" | "beach" | "cherry" | "volcano";
16
17
  };
17
18
  roles: string[];
18
19
  tags: string[];
@@ -1,3 +1,4 @@
1
+ import { persistFeaturesTo } from '@axium/core/node/features';
1
2
  import { loadPlugin } from '@axium/core/node/plugins';
2
3
  import * as io from 'ioium/node';
3
4
  import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
@@ -11,6 +12,7 @@ mkdirSync(configDir, { recursive: true });
11
12
  export const axcConfigPath = join(configDir, 'config.json');
12
13
  if (!existsSync(axcConfigPath))
13
14
  writeFileSync(axcConfigPath, '{}');
15
+ persistFeaturesTo(join(configDir, 'features.json'));
14
16
  export function session() {
15
17
  if (!config.token)
16
18
  io.exit('Not logged in.', 4);
@@ -1,4 +1,5 @@
1
- import type { ZodObject, ZodUUID } from 'zod';
1
+ import { schemas } from '../sync.js';
2
+ export { schemas };
2
3
  export interface $Objects {
3
4
  }
4
5
  export type ObjectType = keyof $Objects extends never ? string : keyof $Objects;
@@ -9,9 +10,5 @@ type ObjectValues = keyof $Objects extends never ? Record<string, {
9
10
  id: string;
10
11
  })[];
11
12
  };
12
- export declare function useSchema<Type extends ObjectType, S extends ZodObject<{
13
- id: ZodUUID;
14
- }>>(type: Type, schema: S): void;
15
13
  export declare function get<Type extends ObjectType>(type: Type): ObjectValues[Type];
16
14
  export declare function save<Type extends ObjectType>(type: Type, objects: ObjectValues[Type]): void;
17
- export {};
package/dist/cli/sync.js CHANGED
@@ -1,13 +1,11 @@
1
+ import { schemas } from '../sync.js';
1
2
  import { sync as syncCache } from './cache.js';
3
+ export { schemas };
2
4
  let _byType;
3
5
  function byType() {
4
6
  _byType ||= Object.groupBy(syncCache.data.objects, o => o.$type);
5
7
  return _byType;
6
8
  }
7
- const schemas = new Map();
8
- export function useSchema(type, schema) {
9
- schemas.set(type, schema);
10
- }
11
9
  export function get(type) {
12
10
  const value = byType()[type] || [];
13
11
  const schema = schemas.get(type);
package/dist/locales.d.ts CHANGED
@@ -1,263 +1,4 @@
1
- import type { FlattenKeys, GetByString, Split, UnionToIntersection } from 'utilium';
2
- /**
3
- * Add translations to a locale.
4
- * Note that translations are rendered as HTML, only replacements are escaped.
5
- */
6
- export declare function extendLocale(locale: string, data: object): void;
7
- declare let currentLoaded: {
8
- readonly AccessControlDialog: {
9
- readonly named_title: "Permissions for <strong>{name}</strong>";
10
- readonly owner: "Owner";
11
- readonly title: "Permissions";
12
- readonly remove: "Remove";
13
- readonly toast_removed: "Removed access";
14
- readonly public_target: "Everyone";
15
- readonly add_public: "Add Public Access";
16
- readonly placeholder: "Add users and roles";
17
- };
18
- readonly AppMenu: {
19
- readonly failed: "Couldn't load apps.";
20
- readonly none: "No apps available.";
21
- };
22
- readonly AppPreferences: {
23
- readonly title: "Preferences for {name}";
24
- readonly save: "Save";
25
- readonly toast_saved: "Preferences saved";
26
- readonly dialog_toggle: "Settings";
27
- };
28
- readonly Discovery: {
29
- readonly no_results: "No results";
30
- };
31
- readonly Login: {
32
- readonly register: "Register instead";
33
- };
34
- readonly Logout: {
35
- readonly back: "Take me back";
36
- readonly question: "Are you sure you want to log out?";
37
- };
38
- readonly Register: {
39
- readonly login: "Login instead";
40
- };
41
- readonly SessionList: {
42
- readonly created: "Created {date}";
43
- readonly current: "Current";
44
- readonly elevated: "Elevated";
45
- readonly expires: "Expires {date}";
46
- readonly logout_all_question: "Are you sure you want to log out all sessions?";
47
- readonly logout_all_submit: "Logout All Sessions";
48
- readonly logout_all_trigger: "Logout All";
49
- readonly logout_single: "Are you sure you want to log out this session?";
50
- };
51
- readonly Upload: {
52
- readonly upload: "Upload";
53
- };
54
- readonly UserCard: {
55
- readonly you: "(You)";
56
- };
57
- readonly UserMenu: {
58
- readonly account: "Your Account";
59
- readonly admin: "Administration";
60
- };
61
- readonly Version: {
62
- readonly error: "Latest unknown";
63
- readonly latest: "Latest";
64
- readonly upgrade: "<span class=\"version\">{latest}</span> available";
65
- };
66
- readonly ZodInput: {
67
- readonly invalid_type: "Invalid input type: {type}";
68
- };
69
- readonly generic: {
70
- readonly action_irreversible: "This action can't be undone.";
71
- readonly cancel: "Cancel";
72
- readonly change: "Change";
73
- readonly create: "Create";
74
- readonly delete: "Delete";
75
- readonly done: "Done";
76
- readonly email: "Email";
77
- readonly loading: "Loading...";
78
- readonly login: "Login";
79
- readonly logout: "Logout";
80
- readonly no: "No";
81
- readonly none: "None";
82
- readonly ok: "Okay";
83
- readonly preferences: "Preferences";
84
- readonly recovery: "Recovery";
85
- readonly recovery_method_disabled: "This recovery method is disabled and can't be used.";
86
- readonly register: "Register";
87
- readonly rename: "Rename";
88
- readonly sessions: "Sessions";
89
- readonly share: "Share";
90
- readonly success: "Success";
91
- readonly unknown: "Unknown";
92
- readonly unnamed: "Unnamed";
93
- readonly user_display_name: "Display name";
94
- readonly username: "Username";
95
- readonly yes: "Yes";
96
- };
97
- readonly page: {
98
- readonly account: {
99
- readonly delete_account: "Delete Account";
100
- readonly delete_account_confirm: "Are you sure you want to delete your account?";
101
- readonly edit_email: "Email Address";
102
- readonly edit_name: "What do you want to be called?";
103
- readonly edit_username: "Pick a unique username";
104
- readonly email_verified_on: "Email verified on {date}";
105
- readonly greeting: "Welcome, {name}";
106
- readonly passkeys: {
107
- readonly backed_up: "This passkey is backed up";
108
- readonly create: "Create";
109
- readonly created: "Created {date}";
110
- readonly delete: "Delete";
111
- readonly delete_confirm: "Are you sure you want to delete this passkey?";
112
- readonly edit_name: "Passkey Name";
113
- readonly min_one: "You must have at least one passkey";
114
- readonly multi_device: "Multiple devices";
115
- readonly name_type_error: "Passkey name must be a string";
116
- readonly not_backed_up: "This passkey is not backed up";
117
- readonly rename: "Rename";
118
- readonly single_device: "Single device";
119
- readonly title: "Passkeys";
120
- };
121
- readonly personal_info: "Personal Information";
122
- readonly pfp: {
123
- readonly update: "Upload";
124
- readonly remove: "Remove";
125
- readonly toast_removed: "Profile picture removed";
126
- readonly toast_updated: "Profile picture updated";
127
- };
128
- readonly preferences: "Preferences";
129
- readonly remove_email: "Remove email";
130
- readonly remove_email_confirm: "Are you sure you want to remove your recovery email?";
131
- readonly sessions: "Sessions";
132
- readonly title: "Your Account";
133
- readonly toast_username_updated: "Username updated";
134
- readonly user_id: "User ID";
135
- readonly user_id_hint: "This is your UUID. It can't be changed.";
136
- readonly verification_sent: "Verification email sent";
137
- readonly verify: "Verify";
138
- };
139
- readonly admin: {
140
- readonly heading: "Administration";
141
- readonly tab: {
142
- readonly dashboard: "Dashboard";
143
- readonly users: "Users";
144
- readonly config: "Configuration";
145
- readonly plugins: "Plugins";
146
- readonly audit: "Audit Log";
147
- };
148
- readonly toast: {
149
- readonly suspended: "User suspended";
150
- readonly unsuspended: "User un-suspended";
151
- readonly user_updated: "User updated";
152
- };
153
- readonly audit: {
154
- readonly any: "Any";
155
- readonly apply: "Apply";
156
- readonly error_stack: "Error Stack";
157
- readonly event_heading: "Audit Event";
158
- readonly event_title: "Admin — Audit Log Event #{id}";
159
- readonly extra_data: "Extra Data";
160
- readonly filter: {
161
- readonly event: "Event Name:";
162
- readonly severity: "Minimum Severity:";
163
- readonly since: "Since:";
164
- readonly source: "Source:";
165
- readonly tags: "Tags:";
166
- readonly until: "Until:";
167
- readonly user: "User UUID:";
168
- };
169
- readonly filters: "Filters";
170
- readonly heading: "Audit Log";
171
- readonly invalid_filter: "Invalid Filter:";
172
- readonly name: "Name";
173
- readonly no_events: "No audit log events found";
174
- readonly reset: "Reset";
175
- readonly severity: "Severity";
176
- readonly source: "Source";
177
- readonly tags: "Tags";
178
- readonly timestamp: "Timestamp";
179
- readonly title: "Admin — Audit Log";
180
- readonly user: "User";
181
- readonly uuid: "UUID";
182
- };
183
- readonly config: {
184
- readonly active: "Active Configuration";
185
- readonly loaded_files: "Loaded Files";
186
- readonly title: "Admin — Configuration";
187
- };
188
- readonly dashboard: {
189
- readonly audit_link: "Audit Log";
190
- readonly config_files: "{count} files loaded.";
191
- readonly config_link: "Configuration";
192
- readonly plugins_link: "Plugins";
193
- readonly plugins_loaded: "{count} plugins loaded.";
194
- readonly stats: "{users} users, {sessions} sessions, {passkeys} passkeys.";
195
- readonly title: "Admin — Dashboard";
196
- readonly users_link: "Users";
197
- };
198
- readonly plugins: {
199
- readonly author: "Author:";
200
- readonly configuration: "Configuration";
201
- readonly heading: "Plugins";
202
- readonly loaded_from: "Loaded from";
203
- readonly none: "No plugins loaded.";
204
- readonly provided_apps: "Provided apps:";
205
- readonly title: "Admin — Plugins";
206
- };
207
- readonly users: {
208
- readonly admin_tag: "Admin";
209
- readonly administrator: "Administrator";
210
- readonly attributes: "Attributes";
211
- readonly audit: "Audit";
212
- readonly back: "Back to all users";
213
- readonly create: "Create User";
214
- readonly created_title: "New User Created";
215
- readonly created_url: "They can log in using this URL:";
216
- readonly default_image: "Default";
217
- readonly delete_confirm: "Are you sure you want to delete this user?";
218
- readonly delete_user: "Delete User";
219
- readonly display_name: "Display Name";
220
- readonly email_not_verified: "not verified";
221
- readonly email_verified: "verified {date}";
222
- readonly heading: "Users";
223
- readonly manage: "Manage";
224
- readonly manage_heading: "User Management";
225
- readonly manage_title: "Admin — User Management";
226
- readonly none: "No users!";
227
- readonly profile_image: "Profile Image";
228
- readonly registered: "Registered";
229
- readonly roles: "Roles";
230
- readonly suspend: "Suspend";
231
- readonly suspended: "Suspended";
232
- readonly tags: "Tags";
233
- readonly title: "Admin — Users";
234
- readonly unsuspend: "Unsuspend";
235
- readonly uuid: "UUID";
236
- };
237
- };
238
- readonly login: {
239
- readonly client: {
240
- readonly authorize: "Authorize";
241
- readonly confirm: "Are you sure you want to log in to this local client?";
242
- readonly success: "Login successful! You can close this tab.";
243
- readonly title: "Local Client Login";
244
- };
245
- readonly failed: "Login Failed";
246
- };
247
- };
248
- readonly location: {
249
- readonly country: "Country";
250
- readonly subdivision: "State / province";
251
- readonly locality: "City / town";
252
- readonly postal_code: "Postal code";
253
- readonly street1: "Street address";
254
- readonly street2: "Street address line 2";
255
- };
256
- readonly audit_severity: readonly ["Emergency", "Alert", "Critical", "Error", "Warning", "Notice", "Info", "Debug"];
257
- readonly preference: {
258
- readonly debug: "Debug mode";
259
- };
260
- };
1
+ import { type LocaleKey, type LocaleKeys, type LocaleValue } from '@axium/core/locales';
261
2
  /**
262
3
  * Current locale
263
4
  */
@@ -267,20 +8,12 @@ export declare function dateField(name: string): string | undefined;
267
8
  export declare function conjoin(list: Iterable<string>): string;
268
9
  export declare function disjoin(list: Iterable<string>): string;
269
10
  export declare let currentMonthNames: string[];
270
- type _locale = typeof currentLoaded;
271
- export interface Locale extends _locale {
272
- }
273
11
  export interface ReplacementOptions {
274
12
  $default?: string;
275
13
  /** Whether to treat the replacement as HTML */
276
14
  $html?: boolean;
277
15
  }
278
- type _ArgsValue<V extends string[]> = UnionToIntersection<{
279
- [I in keyof V]: Split<V[I], '}'> extends [infer Name extends string, string] ? {
280
- [N in Name]: string | number | bigint | boolean;
281
- } : {};
282
- }[keyof V & number]>;
283
- type Replacements<K extends string> = ReplacementOptions & (GetByString<Locale, K> extends string ? _ArgsValue<Split<GetByString<Locale, K> & string, '{'>> : Record<string, any>);
16
+ type Replacements<K extends string> = ReplacementOptions & (K extends LocaleKey ? LocaleKeys[K & LocaleKey] : Record<string, LocaleValue>);
284
17
  type ReplacementsArgs<K extends string> = {} extends Replacements<K> ? [replacements?: Replacements<K>] : [replacements: Replacements<K>];
285
18
  export declare function useLocale(newLocale: string): void;
286
19
  export declare function escape(text: string): string;
@@ -291,5 +24,5 @@ export declare function escape(text: string): string;
291
24
  * text(`example.translation.key.${dynamicPart}`, { a: 1, b: 2 });
292
25
  * ```
293
26
  */
294
- export declare function text<const K extends string = FlattenKeys<Locale>>(key: K, ...args: ReplacementsArgs<K>): string;
27
+ export declare function text<const K extends string = LocaleKey>(key: K, ...args: ReplacementsArgs<K>): string;
295
28
  export {};
package/dist/locales.js CHANGED
@@ -1,21 +1,9 @@
1
- import { debug, error, info, warn } from 'ioium';
2
- import { deepAssign, getByString } from 'utilium';
1
+ import { extendLocale, loadedLocales } from '@axium/core/locales';
2
+ import { error, warn } from 'ioium';
3
3
  import en from '../locales/en.json' with { type: 'json' };
4
- const loadedLocales = Object.assign(Object.create(null), { en });
5
- /**
6
- * Add translations to a locale.
7
- * Note that translations are rendered as HTML, only replacements are escaped.
8
- */
9
- export function extendLocale(locale, data) {
10
- if (!loadedLocales[locale]) {
11
- info('Adding new locale (no built-in): ' + locale);
12
- loadedLocales[locale] = {};
13
- }
14
- else
15
- debug('Extending locale: ' + locale);
16
- deepAssign(loadedLocales[locale], data);
17
- }
18
- let currentLoaded = en;
4
+ import { getByString } from 'utilium';
5
+ extendLocale('en', en);
6
+ let currentLoaded;
19
7
  /**
20
8
  * Current locale
21
9
  */
package/dist/socket.js CHANGED
@@ -5,12 +5,11 @@ import { origin, token, userAgent } from './requests.js';
5
5
  const listeners = new Set();
6
6
  export function addListener(event, listener) {
7
7
  const schema = ServerToClient[event];
8
- if (schema)
9
- listeners.add([event, schema.implement(listener)]);
10
- else {
8
+ if (!schema)
11
9
  io.warn(`Attaching a listener to the '${event}' socket event without schema`);
12
- listeners.add([event, listener]);
13
- }
10
+ const entry = [event, schema ? schema.implement(listener) : listener];
11
+ listeners.add(entry);
12
+ socket?.on(...entry);
14
13
  }
15
14
  export let socket = null;
16
15
  export function connect(opts) {
package/dist/sync.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ import type { SyncDiff, SyncDiffObject } from '@axium/core';
2
+ import type { ZodObject, ZodUUID } from 'zod';
3
+ /** Schemas used to parse synced objects, by object type */
4
+ export declare const schemas: Map<string, ZodObject<{
5
+ id: ZodUUID;
6
+ }, import("@axium/client").$strip>>;
7
+ /** Parse a synced object using the schema for its type. Objects without a schema are passed through. */
8
+ export declare function parseObject<T extends SyncDiffObject>(object: SyncDiffObject): T;
9
+ /**
10
+ * Apply a diff to some synced objects, updating them in place.
11
+ * Existing objects are updated in place, so bound references stay valid.
12
+ */
13
+ export declare function applyDiff<T extends {
14
+ id: string;
15
+ $type?: string;
16
+ }>(objects: T[], diff: SyncDiff, type?: string): void;
17
+ /**
18
+ * Handle changes to synced objects pushed by the server.
19
+ * The socket must be connected for these to be received.
20
+ * @returns a function that removes the listener
21
+ */
22
+ export declare function onSync(listener: (diff: SyncDiff) => void): () => void;
package/dist/sync.js ADDED
@@ -0,0 +1,67 @@
1
+ import * as io from 'ioium';
2
+ import { addListener } from './socket.js';
3
+ /** Schemas used to parse synced objects, by object type */
4
+ export const schemas = new Map();
5
+ /** Parse a synced object using the schema for its type. Objects without a schema are passed through. */
6
+ export function parseObject(object) {
7
+ const schema = schemas.get(object.$type);
8
+ if (!schema)
9
+ return object;
10
+ return Object.assign(schema.parse(object), { $type: object.$type });
11
+ }
12
+ /**
13
+ * Apply a diff to some synced objects, updating them in place.
14
+ * Existing objects are updated in place, so bound references stay valid.
15
+ */
16
+ export function applyDiff(objects, diff, type) {
17
+ const deleted = new Set(diff.deleted);
18
+ for (let i = objects.length - 1; i >= 0; i--) {
19
+ if (deleted.has(objects[i].id))
20
+ objects.splice(i, 1);
21
+ }
22
+ const existing = new Map(objects.map(object => [object.id, object]));
23
+ for (const incoming of [...diff.created, ...diff.updated]) {
24
+ if (type && incoming.$type != type)
25
+ continue;
26
+ let object;
27
+ try {
28
+ object = parseObject(incoming);
29
+ }
30
+ catch (e) {
31
+ io.warn(`Ignoring invalid ${incoming.$type} object from sync: ${io.errorText(e)}`);
32
+ continue;
33
+ }
34
+ const current = existing.get(incoming.id);
35
+ if (!current) {
36
+ objects.push(object);
37
+ existing.set(object.id, object);
38
+ continue;
39
+ }
40
+ if (current.$type && current.$type != incoming.$type) {
41
+ io.warn(`Type mismatch for synced object ${incoming.id}: currently ${current.$type}, incoming ${incoming.$type}`);
42
+ continue;
43
+ }
44
+ Object.assign(current, object);
45
+ }
46
+ }
47
+ const listeners = new Set();
48
+ /**
49
+ * Handle changes to synced objects pushed by the server.
50
+ * The socket must be connected for these to be received.
51
+ * @returns a function that removes the listener
52
+ */
53
+ export function onSync(listener) {
54
+ listeners.add(listener);
55
+ return () => listeners.delete(listener);
56
+ }
57
+ addListener('sync', diff => {
58
+ io.debug(`sync: ${diff.created.length} created, ${diff.updated.length} updated, ${diff.deleted.length} deleted`);
59
+ for (const listener of listeners) {
60
+ try {
61
+ listener(diff);
62
+ }
63
+ catch (e) {
64
+ io.error('Sync listener failed: ' + io.errorText(e));
65
+ }
66
+ }
67
+ });
@@ -0,0 +1,2 @@
1
+ import type { Theme } from '@axium/core/preferences';
2
+ export declare const themeStyles: Record<Theme, Record<string, string>>;
package/dist/themes.js ADDED
@@ -0,0 +1,34 @@
1
+ export const themeStyles = Object.assign(Object.create(null), {
2
+ light: {
3
+ 'fg-light': '10%',
4
+ 'bg-light': '70%',
5
+ 'light-step': '-6%',
6
+ },
7
+ forest: {
8
+ hue: '150',
9
+ 'fg-light': '75%',
10
+ 'bg-light': '20%',
11
+ },
12
+ midnight: {
13
+ 'fg-light': '60%',
14
+ 'bg-light': '2.5%',
15
+ 'light-step': '2.5%',
16
+ },
17
+ beach: {
18
+ hue: '50',
19
+ 'fg-light': '30%',
20
+ 'bg-light': '60%',
21
+ 'light-step': '-4%',
22
+ },
23
+ cherry: {
24
+ hue: '330',
25
+ 'fg-light': '80%',
26
+ 'bg-light': '20%',
27
+ },
28
+ volcano: {
29
+ hue: '10',
30
+ 'fg-light': '80%',
31
+ 'bg-light': '10%',
32
+ 'light-step': '5%',
33
+ },
34
+ });
package/dist/user.d.ts CHANGED
@@ -10,6 +10,7 @@ export declare function loginByUsername(username: string): Promise<NewSessionRes
10
10
  export declare function getCurrentSession(): Promise<Session & {
11
11
  user: User;
12
12
  }>;
13
+ export declare function extendCurrentSession(userId: string): Promise<void>;
13
14
  export declare function getSessions(userId: string): Promise<Session[]>;
14
15
  export declare function logout(userId: string, ...sessionId: string[]): Promise<Session[]>;
15
16
  export declare function logoutAll(userId: string): Promise<Session[]>;
package/dist/user.js CHANGED
@@ -25,6 +25,11 @@ export async function getCurrentSession() {
25
25
  _currentSession ||= await fetchAPI('GET', 'session');
26
26
  return _currentSession;
27
27
  }
28
+ export async function extendCurrentSession(userId) {
29
+ const optionsJSON = await fetchAPI('PUT', 'users/:id/auth', { type: 'extend_session' }, userId);
30
+ const response = await startAuthentication({ optionsJSON });
31
+ await fetchAPI('POST', 'users/:id/auth', response, userId);
32
+ }
28
33
  export async function getSessions(userId) {
29
34
  _checkId(userId);
30
35
  return await fetchAPI('GET', 'users/:id/sessions', {}, userId);
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Browser-side feature state.
3
+ *
4
+ * Enabled feature IDs are listed in a single `feature` attribute on `<html>`, so CSS selects them with the `~=` operator
5
+ *
6
+ * @example
7
+ * ```css
8
+ * [feature~='checkbox-switch'] {
9
+ * input[type='checkbox'] { ... }
10
+ * }
11
+ * ```
12
+ *
13
+ * @module
14
+ */
15
+ export declare function loadFeatures(userId?: string): Promise<void>;
16
+ export declare function setUserFeatures(userId: string, update: Record<string, boolean>): Promise<Record<string, boolean>>;
17
+ export declare function setGlobalFeatures(update: Record<string, boolean>): Promise<Record<string, boolean>>;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Browser-side feature state.
3
+ *
4
+ * Enabled feature IDs are listed in a single `feature` attribute on `<html>`, so CSS selects them with the `~=` operator
5
+ *
6
+ * @example
7
+ * ```css
8
+ * [feature~='checkbox-switch'] {
9
+ * input[type='checkbox'] { ... }
10
+ * }
11
+ * ```
12
+ *
13
+ * @module
14
+ */
15
+ import { getAll, set, use } from '@axium/core/features';
16
+ import { fetchAPI } from '../requests.js';
17
+ function _setFeatureAttributes() {
18
+ const enabled = getAll()
19
+ .filter(f => f.value)
20
+ .map(f => f.id)
21
+ .toArray();
22
+ if (enabled.length)
23
+ document.documentElement.setAttribute('feature', enabled.join(' '));
24
+ else
25
+ document.documentElement.removeAttribute('feature');
26
+ }
27
+ export async function loadFeatures(userId) {
28
+ const features = userId
29
+ ? await fetchAPI('GET', 'users/:id/features', {}, userId).catch(() => fetchAPI('GET', 'features'))
30
+ : await fetchAPI('GET', 'features');
31
+ use(features);
32
+ _setFeatureAttributes();
33
+ }
34
+ export async function setUserFeatures(userId, update) {
35
+ const values = await fetchAPI('POST', 'users/:id/features', update, userId);
36
+ for (const [id, value] of Object.entries(values))
37
+ set(id, value);
38
+ _setFeatureAttributes();
39
+ return values;
40
+ }
41
+ export async function setGlobalFeatures(update) {
42
+ const values = await fetchAPI('POST', 'features', update);
43
+ for (const [id, value] of Object.entries(values))
44
+ set(id, value);
45
+ _setFeatureAttributes();
46
+ return values;
47
+ }