Skip to content

Commit 8431ae9

Browse files
committed
Refactor backend, unify interface, remove deprecated code
Refactor backend to separate tauri and web implementations, add unified backend interface, and remove deprecated code. - Added a new backend interface (IBackend) with implementations for Tauri and Web. - Refactored tauri.ts to use new backend abstraction and exported new tauri functions. - Added web backend (web.ts) implementation for web builds. - Cleaned up ipc.ts and removed deprecated code. - Removed deprecated backendService.svelte.ts stub.
1 parent b2d9f4b commit 8431ae9

File tree

5 files changed

+267
-126
lines changed

5 files changed

+267
-126
lines changed

apps/desktop/src/lib/backend/backendService.svelte.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.

apps/desktop/src/lib/backend/index.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import {
2+
tauriInvoke,
3+
tauriListen,
4+
tauriCheck,
5+
tauriGetVersion,
6+
tauriReadFile
7+
} from '$lib/backend/tauri';
8+
import {
9+
webCheckUpdate,
10+
webCurrentVersion,
11+
webInvoke,
12+
webListen,
13+
webReadFile
14+
} from '$lib/backend/web';
15+
16+
type Event<T> = {
17+
/** Event name */
18+
event: string;
19+
/** Event identifier used to unlisten */
20+
id: number;
21+
/** Event payload */
22+
payload: T;
23+
};
24+
25+
export type DownloadEvent =
26+
| {
27+
event: 'Started';
28+
data: {
29+
contentLength?: number;
30+
};
31+
}
32+
| {
33+
event: 'Progress';
34+
data: {
35+
chunkLength: number;
36+
};
37+
}
38+
| {
39+
event: 'Finished';
40+
};
41+
42+
export type DownloadEventName = DownloadEvent['event'];
43+
44+
export type DownloadUpdate = (onEvent?: (progress: DownloadEvent) => void) => Promise<void>;
45+
export type InstallUpdate = () => Promise<void>;
46+
47+
export type Update = {
48+
version: string;
49+
currentVersion: string;
50+
body?: string;
51+
download: DownloadUpdate;
52+
install: InstallUpdate;
53+
};
54+
55+
export interface IBackend {
56+
invoke: <T>(command: string, ...args: any[]) => Promise<T>;
57+
listen: <T>(event: string, callback: (event: Event<T>) => void) => () => Promise<void>;
58+
checkUpdate: () => Promise<Update | null>;
59+
currentVersion: () => Promise<string>;
60+
readFile: (path: string) => Promise<Uint8Array>;
61+
}
62+
63+
class Tauri implements IBackend {
64+
invoke = tauriInvoke;
65+
listen = tauriListen;
66+
checkUpdate = tauriCheck;
67+
currentVersion = tauriGetVersion;
68+
readFile = tauriReadFile;
69+
}
70+
71+
class Web implements IBackend {
72+
invoke = webInvoke;
73+
listen = webListen;
74+
checkUpdate = webCheckUpdate;
75+
currentVersion = webCurrentVersion;
76+
readFile = webReadFile;
77+
}
78+
79+
export default function createBackend(): IBackend {
80+
if (import.meta.env.VITE_BUILD_TARGET === 'web') {
81+
return new Web();
82+
}
83+
return new Tauri();
84+
}

apps/desktop/src/lib/backend/ipc.ts

Lines changed: 0 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,4 @@
11
import { Code } from '$lib/error/knownErrors';
2-
import { isReduxError } from '$lib/state/reduxError';
3-
import { getCookie } from '$lib/utils/cookies';
4-
import { invoke as invokeTauri } from '@tauri-apps/api/core';
5-
import { listen as listenTauri } from '@tauri-apps/api/event';
6-
import type { EventCallback, EventName } from '@tauri-apps/api/event';
7-
8-
type ServerResonse<T> =
9-
| {
10-
type: 'success';
11-
subject: T;
12-
}
13-
| {
14-
type: 'error';
15-
subject: unknown;
16-
};
172

183
export class UserError extends Error {
194
code!: Code;
@@ -40,104 +25,3 @@ export function getUserErrorCode(error: unknown): Code | undefined {
4025
const userError = UserError.fromError(error);
4126
return userError.code;
4227
}
43-
44-
export async function invoke<T>(command: string, params: Record<string, unknown> = {}): Promise<T> {
45-
// This commented out code can be used to delay/reject an api call
46-
// return new Promise<T>((resolve, reject) => {
47-
// if (command.startsWith('apply')) {
48-
// setTimeout(() => {
49-
// reject('testing the error page');
50-
// }, 500);
51-
// } else {
52-
// resolve(invokeTauri<T>(command, params));
53-
// }
54-
// }).catch((reason) => {
55-
// const userError = UserError.fromError(reason);
56-
// console.error(`ipc->${command}: ${JSON.stringify(params)}`, userError);
57-
// throw userError;
58-
// });
59-
60-
try {
61-
if (import.meta.env.VITE_BUILD_TARGET === 'web') {
62-
const response = await fetch(`http://${getWebUrl()}`, {
63-
method: 'POST',
64-
headers: {
65-
'Content-Type': 'application/json'
66-
},
67-
body: JSON.stringify({ command, params })
68-
});
69-
const out: ServerResonse<T> = await response.json();
70-
if (out.type === 'success') {
71-
return out.subject;
72-
} else {
73-
if (isReduxError(out.subject)) {
74-
console.error(`ipc->${command}: ${JSON.stringify(params)}`, out.subject);
75-
}
76-
throw out.subject;
77-
}
78-
} else {
79-
return await invokeTauri<T>(command, params);
80-
}
81-
} catch (error: unknown) {
82-
if (isReduxError(error)) {
83-
console.error(`ipc->${command}: ${JSON.stringify(params)}`, error);
84-
}
85-
throw error;
86-
}
87-
}
88-
89-
let webListener: WebListener | undefined;
90-
91-
export function listen<T>(event: EventName, handle: EventCallback<T>) {
92-
if (import.meta.env.VITE_BUILD_TARGET === 'web') {
93-
if (!webListener) {
94-
webListener = new WebListener();
95-
}
96-
97-
// TODO: Listening in electron
98-
return webListener.listen({ name: event, handle });
99-
} else {
100-
const unlisten = listenTauri(event, handle);
101-
return async () => await unlisten.then((unlistenFn) => unlistenFn());
102-
}
103-
}
104-
105-
class WebListener {
106-
private socket: WebSocket | undefined;
107-
private count = 0;
108-
private handlers: { name: EventName; handle: EventCallback<any> }[] = [];
109-
110-
listen(handler: { name: EventName; handle: EventCallback<any> }): () => Promise<void> {
111-
this.handlers.push(handler);
112-
this.count++;
113-
if (!this.socket) {
114-
this.socket = new WebSocket(`ws://${getWebUrl()}/ws`);
115-
this.socket.addEventListener('message', (event) => {
116-
const data: { name: string; payload: any } = JSON.parse(event.data);
117-
for (const handler of this.handlers) {
118-
if (handler.name === data.name) {
119-
// The id is an artifact from tauri, we don't use it so
120-
// I've used a random value
121-
handler.handle({ event: data.name, payload: data.payload, id: 69 });
122-
}
123-
}
124-
});
125-
}
126-
127-
// This needs to be async just so it's the same API as the tauri version
128-
return async () => {
129-
this.handlers = this.handlers.filter((h) => h !== handler);
130-
this.count--;
131-
if (this.count === 0) {
132-
this.socket?.close();
133-
this.socket = undefined;
134-
}
135-
};
136-
}
137-
}
138-
139-
function getWebUrl(): string {
140-
const host = getCookie('butlerHost') || import.meta.env.VITE_BUTLER_HOST || 'localhost';
141-
const port = getCookie('butlerPort') || import.meta.env.VITE_BUTLER_PORT || '6978';
142-
return `${host}:${port}`;
143-
}

apps/desktop/src/lib/backend/tauri.ts

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,42 @@
1-
import { invoke as invokeIpc, listen as listenIpc } from '$lib/backend/ipc';
2-
import { getVersion } from '@tauri-apps/api/app';
3-
import { check } from '@tauri-apps/plugin-updater';
4-
5-
export class Tauri {
6-
invoke = invokeIpc;
7-
listen = listenIpc;
8-
checkUpdate = check;
9-
currentVersion = getVersion;
1+
import { isReduxError } from '$lib/state/reduxError';
2+
import { invoke as invokeTauri } from '@tauri-apps/api/core';
3+
import { listen as listenTauri } from '@tauri-apps/api/event';
4+
import type { EventCallback, EventName } from '@tauri-apps/api/event';
5+
6+
export async function tauriInvoke<T>(
7+
command: string,
8+
params: Record<string, unknown> = {}
9+
): Promise<T> {
10+
// This commented out code can be used to delay/reject an api call
11+
// return new Promise<T>((resolve, reject) => {
12+
// if (command.startsWith('apply')) {
13+
// setTimeout(() => {
14+
// reject('testing the error page');
15+
// }, 500);
16+
// } else {
17+
// resolve(invokeTauri<T>(command, params));
18+
// }
19+
// }).catch((reason) => {
20+
// const userError = UserError.fromError(reason);
21+
// console.error(`ipc->${command}: ${JSON.stringify(params)}`, userError);
22+
// throw userError;
23+
// });
24+
25+
try {
26+
return await invokeTauri<T>(command, params);
27+
} catch (error: unknown) {
28+
if (isReduxError(error)) {
29+
console.error(`ipc->${command}: ${JSON.stringify(params)}`, error);
30+
}
31+
throw error;
32+
}
1033
}
34+
35+
export function tauriListen<T>(event: EventName, handle: EventCallback<T>) {
36+
const unlisten = listenTauri(event, handle);
37+
return async () => await unlisten.then((unlistenFn) => unlistenFn());
38+
}
39+
40+
export { getVersion as tauriGetVersion } from '@tauri-apps/api/app';
41+
export { check as tauriCheck } from '@tauri-apps/plugin-updater';
42+
export { readFile as tauriReadFile } from '@tauri-apps/plugin-fs';

0 commit comments

Comments
 (0)