feat(KAN-70): integrate native workspace and update settings
CI/CD / preconditions (pull_request) Successful in 1s
Native JavaScript checks / Native launcher and release tests (pull_request) Successful in 1m2s
macOS checks / Native Rust tests and unsigned build (pull_request) Successful in 1m38s
CI/CD / test_and_check_app (pull_request) Successful in 6m47s
CI/CD / Publish immutable image (pull_request) Skipped
CI/CD / preconditions (pull_request) Successful in 1s
Native JavaScript checks / Native launcher and release tests (pull_request) Successful in 1m2s
macOS checks / Native Rust tests and unsigned build (pull_request) Successful in 1m38s
CI/CD / test_and_check_app (pull_request) Successful in 6m47s
CI/CD / Publish immutable image (pull_request) Skipped
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
import { computed, readonly, ref } from 'vue';
|
||||
import { isNativeAppState, updateIsBusy } from '@/lib/native-app';
|
||||
import type { NativeAction, NativeAppState } from '@/types/native-app';
|
||||
|
||||
const state = ref<NativeAppState | null>(null);
|
||||
const pending = ref(false);
|
||||
const error = ref<string | null>(null);
|
||||
let initializing = false;
|
||||
let refreshing: Promise<void> | null = null;
|
||||
|
||||
async function request(
|
||||
action: NativeAction,
|
||||
payload: Record<string, unknown> = {},
|
||||
) {
|
||||
if (typeof window === 'undefined' || window.kanbanNative?.version !== 1) {
|
||||
throw new Error('bridgeUnavailable');
|
||||
}
|
||||
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||
try {
|
||||
return await Promise.race([
|
||||
window.kanbanNative.request(action, payload),
|
||||
new Promise<never>((_, reject) => {
|
||||
timer = setTimeout(
|
||||
() => reject(new Error('bridgeTimeout')),
|
||||
11000,
|
||||
);
|
||||
}),
|
||||
]);
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
if (refreshing) {
|
||||
return refreshing;
|
||||
}
|
||||
refreshing = (async () => {
|
||||
const result = await request('getState');
|
||||
if (!isNativeAppState(result)) {
|
||||
throw new Error('incompatibleVersion');
|
||||
}
|
||||
state.value = result;
|
||||
})();
|
||||
try {
|
||||
await refreshing;
|
||||
} finally {
|
||||
refreshing = null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function initializeNativeApp() {
|
||||
if (
|
||||
initializing ||
|
||||
state.value ||
|
||||
typeof window === 'undefined' ||
|
||||
window.kanbanNative?.version !== 1
|
||||
) {
|
||||
return;
|
||||
}
|
||||
initializing = true;
|
||||
try {
|
||||
await refresh();
|
||||
await request('settingsReady');
|
||||
} catch {
|
||||
state.value = null;
|
||||
} finally {
|
||||
initializing = false;
|
||||
}
|
||||
}
|
||||
|
||||
export function useNativeApp() {
|
||||
const busy = computed(
|
||||
() => pending.value || updateIsBusy(state.value?.updates),
|
||||
);
|
||||
async function act(
|
||||
action: NativeAction,
|
||||
payload: Record<string, unknown> = {},
|
||||
) {
|
||||
if (
|
||||
pending.value ||
|
||||
(updateIsBusy(state.value?.updates) && action !== 'openSettings')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
pending.value = true;
|
||||
error.value = null;
|
||||
try {
|
||||
await request(action, payload);
|
||||
if (action !== 'setWorkspace') {
|
||||
await refresh();
|
||||
}
|
||||
return true;
|
||||
} catch (cause) {
|
||||
error.value =
|
||||
cause instanceof Error ? cause.message : 'bridgeUnavailable';
|
||||
return false;
|
||||
} finally {
|
||||
pending.value = false;
|
||||
}
|
||||
}
|
||||
async function poll() {
|
||||
try {
|
||||
await refresh();
|
||||
} catch (cause) {
|
||||
error.value =
|
||||
cause instanceof Error ? cause.message : 'bridgeUnavailable';
|
||||
}
|
||||
}
|
||||
return { state: readonly(state), busy, error: readonly(error), act, poll };
|
||||
}
|
||||
|
||||
export async function recoverNativeWorkspace() {
|
||||
if (!state.value) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await request('openSettings');
|
||||
} catch {
|
||||
/* Native recovery remains available from the app menu or toolbar. */
|
||||
}
|
||||
}
|
||||
|
||||
export function registerNativeRecovery() {
|
||||
window.addEventListener('offline', () => {
|
||||
void recoverNativeWorkspace();
|
||||
});
|
||||
document.addEventListener('inertia:exception', () => {
|
||||
void recoverNativeWorkspace();
|
||||
});
|
||||
document.addEventListener('inertia:invalid', (event) => {
|
||||
if (event.detail.response.status >= 400) {
|
||||
void recoverNativeWorkspace();
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user