akanjs 2.1.0-rc.0 → 2.1.0-rc.10
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.
- package/capacitor.base.config.ts +88 -0
- package/client/capacitor.ts +182 -24
- package/client/cookie.ts +14 -5
- package/client/device.ts +86 -10
- package/client/index.ts +0 -1
- package/client/storage.ts +38 -14
- package/document/filterMeta.ts +5 -0
- package/fetch/client/fetchClient.ts +17 -3
- package/package.json +15 -13
- package/server/di/predefinedAdaptor.ts +2 -2
- package/server/resolver/database.resolver.ts +8 -5
- package/server/resolver/service.resolver.ts +6 -3
- package/service/predefinedAdaptor/compress.adaptor.ts +58 -17
- package/service/predefinedAdaptor/database.adaptor.ts +2 -1
- package/service/predefinedAdaptor/queue.adaptor.ts +35 -16
- package/store/action.ts +38 -15
- package/store/state.ts +4 -13
- package/types/capacitor.base.config.d.ts +3 -0
- package/types/client/capacitor.d.ts +224 -24
- package/types/client/device.d.ts +9 -5
- package/types/client/index.d.ts +0 -1
- package/types/dictionary/base.dictionary.d.ts +1 -1
- package/types/dictionary/dictionary.d.ts +8 -8
- package/types/document/filterMeta.d.ts +1 -0
- package/types/server/di/predefinedAdaptor.d.ts +2 -2
- package/types/service/predefinedAdaptor/compress.adaptor.d.ts +8 -0
- package/types/service/predefinedAdaptor/queue.adaptor.d.ts +8 -5
- package/types/ui/Copy.d.ts +1 -1
- package/types/webkit/useCamera.d.ts +14 -3
- package/types/webkit/useContact.d.ts +6 -2
- package/types/webkit/useGeoLocation.d.ts +1 -1
- package/ui/Copy.tsx +36 -7
- package/ui/Data/ListContainer.tsx +14 -3
- package/ui/DatePicker.tsx +14 -7
- package/ui/Link/CsrLink.tsx +5 -2
- package/ui/More.tsx +2 -3
- package/ui/Refresh.tsx +5 -1
- package/webkit/useCamera.tsx +8 -4
- package/webkit/useCodepush.tsx +1 -1
- package/webkit/useContact.tsx +6 -2
- package/webkit/useCsrValues.ts +2 -1
- package/webkit/useGeoLocation.tsx +1 -1
- package/webkit/usePurchase.tsx +1 -1
- package/webkit/usePushNoti.tsx +1 -1
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import type { CapacitorConfig } from "@capacitor/cli";
|
|
3
|
+
import type { AkanMobileTargetConfig, AppScanResult } from "akanjs";
|
|
4
|
+
|
|
5
|
+
const getLocalIP = () => {
|
|
6
|
+
const interfaces = os.networkInterfaces();
|
|
7
|
+
for (const interfaceName in interfaces) {
|
|
8
|
+
const iface = interfaces[interfaceName];
|
|
9
|
+
if (!iface) continue;
|
|
10
|
+
for (const alias of iface) {
|
|
11
|
+
if (alias.family === "IPv4" && !alias.internal) return alias.address;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return "127.0.0.1";
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const normalizeBasePath = (basePath: string | undefined) => basePath?.replace(/^\/+|\/+$/g, "");
|
|
18
|
+
|
|
19
|
+
const routeBasePaths = (appInfo: AppScanResult) =>
|
|
20
|
+
new Set(
|
|
21
|
+
appInfo.routes
|
|
22
|
+
.map((route) => route.replace(/^\.\//, "").split("/")[0])
|
|
23
|
+
.filter((segment): segment is string => !!segment && !segment.startsWith("_") && !segment.startsWith("(")),
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const resolveTarget = (appInfo: AppScanResult, targetName = process.env.AKAN_MOBILE_TARGET) => {
|
|
27
|
+
const targets = appInfo.akanConfig.mobile.targets;
|
|
28
|
+
if (!targets || Object.keys(targets).length === 0) throw new Error("Akan mobile target metadata is missing.");
|
|
29
|
+
if (targetName) {
|
|
30
|
+
const target = targets[targetName];
|
|
31
|
+
if (!target) {
|
|
32
|
+
const basePath = normalizeBasePath(targetName);
|
|
33
|
+
const [template] = Object.values(targets);
|
|
34
|
+
if (basePath && template && routeBasePaths(appInfo).has(basePath))
|
|
35
|
+
return { ...template, name: basePath, basePath };
|
|
36
|
+
throw new Error(`Akan mobile target '${targetName}' was not found.`);
|
|
37
|
+
}
|
|
38
|
+
return target;
|
|
39
|
+
}
|
|
40
|
+
const entries = Object.entries(targets);
|
|
41
|
+
if (entries.length !== 1) throw new Error("AKAN_MOBILE_TARGET is required when multiple mobile targets exist.");
|
|
42
|
+
return entries[0]?.[1] as AkanMobileTargetConfig;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const localCsrUrl = (ip: string, target: AkanMobileTargetConfig) => {
|
|
46
|
+
const basePath = normalizeBasePath(target.basePath);
|
|
47
|
+
const port = process.env.AKAN_PUBLIC_CLIENT_PORT ?? process.env.PORT ?? "8282";
|
|
48
|
+
return `http://${ip}:${port}/${basePath ? `${basePath}` : ""}?csr=true`;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const withBase = (
|
|
52
|
+
configImp: (config: CapacitorConfig, target: AkanMobileTargetConfig) => CapacitorConfig = (config) => config,
|
|
53
|
+
appData?: AppScanResult,
|
|
54
|
+
targetName?: string,
|
|
55
|
+
) => {
|
|
56
|
+
const ip = getLocalIP();
|
|
57
|
+
const appInfo = appData;
|
|
58
|
+
if (!appInfo) throw new Error("withBase requires apps/<app>/akan.app.json metadata.");
|
|
59
|
+
const target = resolveTarget(appInfo, targetName);
|
|
60
|
+
const baseConfig: CapacitorConfig = {
|
|
61
|
+
...target,
|
|
62
|
+
appId: target.appId,
|
|
63
|
+
appName: target.appName,
|
|
64
|
+
webDir: "dist",
|
|
65
|
+
server:
|
|
66
|
+
process.env.APP_OPERATION_MODE !== "release"
|
|
67
|
+
? {
|
|
68
|
+
androidScheme: "http",
|
|
69
|
+
url: localCsrUrl(ip, target),
|
|
70
|
+
cleartext: true,
|
|
71
|
+
allowNavigation: [ip, "localhost"],
|
|
72
|
+
}
|
|
73
|
+
: {
|
|
74
|
+
allowNavigation: ["*"],
|
|
75
|
+
},
|
|
76
|
+
plugins: {
|
|
77
|
+
CapacitorCookies: { enabled: true },
|
|
78
|
+
...target.plugins,
|
|
79
|
+
},
|
|
80
|
+
android: {
|
|
81
|
+
...target.android,
|
|
82
|
+
},
|
|
83
|
+
ios: {
|
|
84
|
+
...target.ios,
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
return configImp(baseConfig, target);
|
|
88
|
+
};
|
package/client/capacitor.ts
CHANGED
|
@@ -1,16 +1,145 @@
|
|
|
1
|
+
export type CapacitorDeviceInfo = {
|
|
2
|
+
platform: string;
|
|
3
|
+
isVirtual: boolean;
|
|
4
|
+
osVersion: string;
|
|
5
|
+
[key: string]: unknown;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type CapacitorKeyboardInfo = {
|
|
9
|
+
keyboardHeight: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type CapacitorPermissionState = "prompt" | "prompt-with-rationale" | "granted" | "denied" | string;
|
|
13
|
+
|
|
14
|
+
export type CapacitorAppModule = {
|
|
15
|
+
App: {
|
|
16
|
+
addListener: (eventName: string, listenerFunc: (...args: unknown[]) => void) => Promise<unknown> | unknown;
|
|
17
|
+
removeAllListeners: () => Promise<void> | void;
|
|
18
|
+
getInfo: () => Promise<{ id: string; version: string; build: string; [key: string]: unknown }>;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type CapacitorBrowserModule = {
|
|
23
|
+
Browser: {
|
|
24
|
+
open: (options: { url: string; presentationStyle?: string }) => Promise<void> | void;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type CapacitorCameraModule = {
|
|
29
|
+
Camera: {
|
|
30
|
+
checkPermissions: () => Promise<{ camera: CapacitorPermissionState; photos: CapacitorPermissionState }>;
|
|
31
|
+
requestPermissions: () => Promise<{ camera: CapacitorPermissionState; photos: CapacitorPermissionState }>;
|
|
32
|
+
getPhoto: (options: Record<string, unknown>) => Promise<{ dataUrl?: string; [key: string]: unknown }>;
|
|
33
|
+
pickImages: (options: Record<string, unknown>) => Promise<{ photos: unknown[]; [key: string]: unknown }>;
|
|
34
|
+
};
|
|
35
|
+
CameraResultType: { DataUrl: string };
|
|
36
|
+
CameraSource: { Prompt: string; Camera: string; Photos: string };
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type CapacitorContactsModule = {
|
|
40
|
+
Contacts: {
|
|
41
|
+
checkPermissions: () => Promise<{ contacts: CapacitorPermissionState }>;
|
|
42
|
+
requestPermissions: () => Promise<{ contacts: CapacitorPermissionState }>;
|
|
43
|
+
getContacts: (options: Record<string, unknown>) => Promise<{ contacts: unknown[] }>;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type CapacitorCoreModule = {
|
|
48
|
+
CapacitorCookies: {
|
|
49
|
+
setCookie: (options: { key: string; value: string; path?: string }) => Promise<void> | void;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export type CapacitorDeviceModule = {
|
|
54
|
+
Device: {
|
|
55
|
+
getInfo: () => Promise<CapacitorDeviceInfo>;
|
|
56
|
+
getLanguageCode: () => Promise<{ value: string }>;
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export type CapacitorFcmModule = {
|
|
61
|
+
FCM: {
|
|
62
|
+
setAutoInit: (options: { enabled: boolean }) => Promise<void> | void;
|
|
63
|
+
getToken: () => Promise<{ token: string }>;
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export type CapacitorGeolocationModule = {
|
|
68
|
+
Geolocation: {
|
|
69
|
+
requestPermissions: () => Promise<{ location: string; coarseLocation: string; [key: string]: string }>;
|
|
70
|
+
getCurrentPosition: () => Promise<unknown>;
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export type CapacitorHapticsModule = {
|
|
75
|
+
ImpactStyle: { Light: string; Medium: string; Heavy: string };
|
|
76
|
+
Haptics: {
|
|
77
|
+
vibrate: (options: { duration: number }) => Promise<void> | void;
|
|
78
|
+
impact: (options: { style: string }) => Promise<void> | void;
|
|
79
|
+
selectionStart: () => Promise<void> | void;
|
|
80
|
+
selectionChanged: () => Promise<void> | void;
|
|
81
|
+
selectionEnd: () => Promise<void> | void;
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export type CapacitorKeyboardModule = {
|
|
86
|
+
Keyboard: {
|
|
87
|
+
show: () => Promise<void> | void;
|
|
88
|
+
hide: () => Promise<void> | void;
|
|
89
|
+
addListener: (eventName: string, listenerFunc: (info: CapacitorKeyboardInfo) => void) => Promise<unknown> | unknown;
|
|
90
|
+
removeAllListeners: () => Promise<void> | void;
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export type CapacitorPreferencesModule = {
|
|
95
|
+
Preferences: {
|
|
96
|
+
get: (options: { key: string }) => Promise<{ value: string | null }>;
|
|
97
|
+
set: (options: { key: string; value: string }) => Promise<void> | void;
|
|
98
|
+
remove: (options: { key: string }) => Promise<void> | void;
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export type CapacitorPushNotificationsModule = {
|
|
103
|
+
PushNotifications: {
|
|
104
|
+
requestPermissions: () => Promise<{ receive: "granted" | "denied" | string }>;
|
|
105
|
+
checkPermissions: () => Promise<{ receive: "granted" | "denied" | string }>;
|
|
106
|
+
register: () => Promise<void> | void;
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export type CapacitorSafeAreaModule = {
|
|
111
|
+
SafeArea: {
|
|
112
|
+
getSafeAreaInsets: () => Promise<{ insets: { top: number; bottom: number } }>;
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export type CapacitorUpdaterModule = {
|
|
117
|
+
CapacitorUpdater: {
|
|
118
|
+
notifyAppReady: () => Promise<void> | void;
|
|
119
|
+
getPluginVersion: () => Promise<{ version: string }>;
|
|
120
|
+
getDeviceId: () => Promise<{ deviceId: string }>;
|
|
121
|
+
current: () => Promise<{ bundle: { version: string }; native: string }>;
|
|
122
|
+
getBuiltinVersion: () => Promise<{ version: string }>;
|
|
123
|
+
download: (options: { url: string; version: string }) => Promise<unknown>;
|
|
124
|
+
set: (bundle: unknown) => Promise<void> | void;
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
|
|
1
128
|
type CapacitorModuleMap = {
|
|
2
|
-
app:
|
|
3
|
-
browser:
|
|
4
|
-
camera:
|
|
5
|
-
contacts:
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
129
|
+
app: CapacitorAppModule;
|
|
130
|
+
browser: CapacitorBrowserModule;
|
|
131
|
+
camera: CapacitorCameraModule;
|
|
132
|
+
contacts: CapacitorContactsModule;
|
|
133
|
+
core: CapacitorCoreModule;
|
|
134
|
+
device: CapacitorDeviceModule;
|
|
135
|
+
fcm: CapacitorFcmModule;
|
|
136
|
+
geolocation: CapacitorGeolocationModule;
|
|
137
|
+
haptics: CapacitorHapticsModule;
|
|
138
|
+
keyboard: CapacitorKeyboardModule;
|
|
139
|
+
preferences: CapacitorPreferencesModule;
|
|
140
|
+
pushNotifications: CapacitorPushNotificationsModule;
|
|
141
|
+
safeArea: CapacitorSafeAreaModule;
|
|
142
|
+
updater: CapacitorUpdaterModule;
|
|
14
143
|
};
|
|
15
144
|
|
|
16
145
|
type CapacitorImportCache = Partial<{
|
|
@@ -40,29 +169,58 @@ const loadCapacitorModule = <K extends keyof CapacitorModuleMap>(
|
|
|
40
169
|
return loaded;
|
|
41
170
|
};
|
|
42
171
|
|
|
43
|
-
|
|
172
|
+
const importNativeModule = <T>(specifier: string) => import(specifier) as Promise<T>;
|
|
173
|
+
|
|
174
|
+
const capacitorPackage = (name: string) => `@capacitor/${name}`;
|
|
44
175
|
|
|
45
|
-
|
|
176
|
+
const capacitorCommunityPackage = (name: string) => `@capacitor-community/${name}`;
|
|
46
177
|
|
|
47
|
-
export const
|
|
178
|
+
export const loadCapacitorApp = () =>
|
|
179
|
+
loadCapacitorModule("app", () => importNativeModule<CapacitorAppModule>(capacitorPackage("app")));
|
|
180
|
+
|
|
181
|
+
export const loadCapacitorBrowser = () =>
|
|
182
|
+
loadCapacitorModule("browser", () => importNativeModule<CapacitorBrowserModule>(capacitorPackage("browser")));
|
|
183
|
+
|
|
184
|
+
export const loadCapacitorCamera = () =>
|
|
185
|
+
loadCapacitorModule("camera", () => importNativeModule<CapacitorCameraModule>(capacitorPackage("camera")));
|
|
48
186
|
|
|
49
187
|
export const loadCapacitorContacts = () =>
|
|
50
|
-
loadCapacitorModule("contacts", () =>
|
|
188
|
+
loadCapacitorModule("contacts", () =>
|
|
189
|
+
importNativeModule<CapacitorContactsModule>(capacitorCommunityPackage("contacts")),
|
|
190
|
+
);
|
|
51
191
|
|
|
52
|
-
export const
|
|
192
|
+
export const loadCapacitorCore = () =>
|
|
193
|
+
loadCapacitorModule("core", () => importNativeModule<CapacitorCoreModule>(capacitorPackage("core")));
|
|
53
194
|
|
|
54
|
-
export const
|
|
195
|
+
export const loadCapacitorDevice = () =>
|
|
196
|
+
loadCapacitorModule("device", () => importNativeModule<CapacitorDeviceModule>(capacitorPackage("device")));
|
|
197
|
+
|
|
198
|
+
export const loadCapacitorFcm = () =>
|
|
199
|
+
loadCapacitorModule("fcm", () => importNativeModule<CapacitorFcmModule>(capacitorCommunityPackage("fcm")));
|
|
55
200
|
|
|
56
201
|
export const loadCapacitorGeolocation = () =>
|
|
57
|
-
loadCapacitorModule("geolocation", () =>
|
|
202
|
+
loadCapacitorModule("geolocation", () =>
|
|
203
|
+
importNativeModule<CapacitorGeolocationModule>(capacitorPackage("geolocation")),
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
export const loadCapacitorHaptics = () =>
|
|
207
|
+
loadCapacitorModule("haptics", () => importNativeModule<CapacitorHapticsModule>(capacitorPackage("haptics")));
|
|
58
208
|
|
|
59
|
-
export const
|
|
209
|
+
export const loadCapacitorKeyboard = () =>
|
|
210
|
+
loadCapacitorModule("keyboard", () => importNativeModule<CapacitorKeyboardModule>(capacitorPackage("keyboard")));
|
|
60
211
|
|
|
61
|
-
export const
|
|
212
|
+
export const loadCapacitorPreferences = () =>
|
|
213
|
+
loadCapacitorModule("preferences", () =>
|
|
214
|
+
importNativeModule<CapacitorPreferencesModule>(capacitorPackage("preferences")),
|
|
215
|
+
);
|
|
62
216
|
|
|
63
217
|
export const loadCapacitorPushNotifications = () =>
|
|
64
|
-
loadCapacitorModule("pushNotifications", () =>
|
|
218
|
+
loadCapacitorModule("pushNotifications", () =>
|
|
219
|
+
importNativeModule<CapacitorPushNotificationsModule>(capacitorPackage("push-notifications")),
|
|
220
|
+
);
|
|
65
221
|
|
|
66
|
-
export const loadCapacitorSafeArea = () =>
|
|
222
|
+
export const loadCapacitorSafeArea = () =>
|
|
223
|
+
loadCapacitorModule("safeArea", () => importNativeModule<CapacitorSafeAreaModule>("capacitor-plugin-safe-area"));
|
|
67
224
|
|
|
68
|
-
export const loadCapacitorUpdater = () =>
|
|
225
|
+
export const loadCapacitorUpdater = () =>
|
|
226
|
+
loadCapacitorModule("updater", () => importNativeModule<CapacitorUpdaterModule>("@capgo/capacitor-updater"));
|
package/client/cookie.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { getEnv } from "akanjs/base";
|
|
|
2
2
|
import { decodeJwtPayload, Logger } from "akanjs/common";
|
|
3
3
|
import type { Account } from "akanjs/fetch";
|
|
4
4
|
import { requestStorage } from "akanjs/fetch";
|
|
5
|
+
import { loadCapacitorCore } from "./capacitor";
|
|
5
6
|
import { storage } from "./storage";
|
|
6
7
|
import { fetch } from "./useClient";
|
|
7
8
|
|
|
@@ -42,11 +43,18 @@ export const setCookie = (
|
|
|
42
43
|
value: string,
|
|
43
44
|
options: CookieOptions = { path: "/", sameSite: "none", secure: true },
|
|
44
45
|
) => {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
const env = getEnv();
|
|
47
|
+
if (env.side === "server") return;
|
|
48
|
+
const encoded = `${key}=${value}`;
|
|
49
|
+
const path = options.path ? `; path=${options.path}` : "";
|
|
50
|
+
const sameSite = options.sameSite ? `; SameSite=${options.sameSite}` : "";
|
|
51
|
+
const secure = options.secure ? "; Secure" : "";
|
|
52
|
+
|
|
53
|
+
document.cookie = `${encoded}${path}${sameSite}${secure}`;
|
|
54
|
+
if (env.renderMode !== "csr") return;
|
|
55
|
+
void loadCapacitorCore()
|
|
56
|
+
.then(({ CapacitorCookies }) => CapacitorCookies.setCookie({ key, value, path: options.path }))
|
|
57
|
+
.catch(() => undefined);
|
|
50
58
|
};
|
|
51
59
|
|
|
52
60
|
export const getCookie = (key: string): string | undefined => {
|
|
@@ -61,6 +69,7 @@ export const getCookie = (key: string): string | undefined => {
|
|
|
61
69
|
export const removeCookie = (key: string, options: { path: string } = { path: "/" }) => {
|
|
62
70
|
if (getEnv().side === "server") return cookies().delete(key);
|
|
63
71
|
else {
|
|
72
|
+
|
|
64
73
|
document.cookie = `${key}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
|
|
65
74
|
}
|
|
66
75
|
};
|
package/client/device.ts
CHANGED
|
@@ -1,19 +1,87 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import type { DeviceInfo } from "@capacitor/device";
|
|
3
|
-
import type { KeyboardInfo } from "@capacitor/keyboard";
|
|
4
2
|
import type { RefObject } from "react";
|
|
5
|
-
import {
|
|
6
|
-
|
|
3
|
+
import type {
|
|
4
|
+
CapacitorDeviceInfo,
|
|
5
|
+
CapacitorHapticsModule,
|
|
6
|
+
CapacitorKeyboardInfo,
|
|
7
|
+
CapacitorKeyboardModule,
|
|
8
|
+
} from "./capacitor";
|
|
9
|
+
|
|
10
|
+
type DeviceInfo = CapacitorDeviceInfo;
|
|
11
|
+
type Keyboard = CapacitorKeyboardModule["Keyboard"];
|
|
12
|
+
type Haptics = CapacitorHapticsModule["Haptics"];
|
|
13
|
+
type ImpactStyle = CapacitorHapticsModule["ImpactStyle"];
|
|
14
|
+
type ProcessEnvLike = { env?: Record<string, string | undefined> };
|
|
15
|
+
|
|
16
|
+
const globalWithProcess = globalThis as typeof globalThis & { process?: ProcessEnvLike };
|
|
7
17
|
|
|
8
18
|
interface DeviceInitOption {
|
|
9
19
|
lang: string;
|
|
10
20
|
info: DeviceInfo;
|
|
11
21
|
topSafeArea: number;
|
|
12
22
|
bottomSafeArea: number;
|
|
13
|
-
keyboard:
|
|
14
|
-
haptics:
|
|
15
|
-
impactStyle:
|
|
23
|
+
keyboard: Keyboard;
|
|
24
|
+
haptics: Haptics;
|
|
25
|
+
impactStyle: ImpactStyle;
|
|
16
26
|
}
|
|
27
|
+
|
|
28
|
+
const noopKeyboard: Keyboard = {
|
|
29
|
+
show: async () => undefined,
|
|
30
|
+
hide: async () => undefined,
|
|
31
|
+
addListener: async () => undefined,
|
|
32
|
+
removeAllListeners: async () => undefined,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const noopHaptics: Haptics = {
|
|
36
|
+
vibrate: async () => undefined,
|
|
37
|
+
impact: async () => undefined,
|
|
38
|
+
selectionStart: async () => undefined,
|
|
39
|
+
selectionChanged: async () => undefined,
|
|
40
|
+
selectionEnd: async () => undefined,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const noopImpactStyle: ImpactStyle = {
|
|
44
|
+
Light: "light",
|
|
45
|
+
Medium: "medium",
|
|
46
|
+
Heavy: "heavy",
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const getRenderMode = () => globalWithProcess.process?.env?.AKAN_PUBLIC_RENDER_ENV ?? "csr";
|
|
50
|
+
|
|
51
|
+
const getBrowserLanguage = () => globalThis.navigator?.language?.split("-")[0] ?? "en";
|
|
52
|
+
|
|
53
|
+
const isNativeTarget = () => {
|
|
54
|
+
if (typeof window === "undefined") return false;
|
|
55
|
+
return Boolean((window as typeof window & { __AKAN_MOBILE_TARGET__?: unknown }).__AKAN_MOBILE_TARGET__);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const isMobileDevice = () => {
|
|
59
|
+
if (typeof navigator === "undefined") return false;
|
|
60
|
+
if (typeof window !== "undefined" && window.matchMedia?.("(hover: none) and (pointer: coarse)")?.matches) return true;
|
|
61
|
+
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const createWebDevice = ({
|
|
65
|
+
lang,
|
|
66
|
+
supportLanguages,
|
|
67
|
+
}: {
|
|
68
|
+
lang?: string;
|
|
69
|
+
supportLanguages: string[] | readonly string[];
|
|
70
|
+
}) => {
|
|
71
|
+
const pathname = typeof window === "undefined" ? "" : window.location.pathname;
|
|
72
|
+
const predefinedLangPath = pathname.split("/")[1]?.split("?")[0];
|
|
73
|
+
const predefinedLang = supportLanguages.find((language) => language === predefinedLangPath);
|
|
74
|
+
return new Device({
|
|
75
|
+
lang: lang ?? predefinedLang ?? getBrowserLanguage(),
|
|
76
|
+
info: { platform: "web", isVirtual: false, osVersion: "" },
|
|
77
|
+
topSafeArea: 0,
|
|
78
|
+
bottomSafeArea: 0,
|
|
79
|
+
keyboard: noopKeyboard,
|
|
80
|
+
haptics: noopHaptics,
|
|
81
|
+
impactStyle: noopImpactStyle,
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
|
|
17
85
|
/** Capacitor-aware device helper for platform info, safe areas, keyboard, haptics, and scroll state. */
|
|
18
86
|
export class Device {
|
|
19
87
|
static instance: Device | null = null;
|
|
@@ -25,6 +93,14 @@ export class Device {
|
|
|
25
93
|
supportLanguages?: string[] | readonly string[];
|
|
26
94
|
}) {
|
|
27
95
|
if (Device.instance) return Device.instance;
|
|
96
|
+
if (getRenderMode() !== "csr" || !isNativeTarget()) {
|
|
97
|
+
const device = createWebDevice({ lang, supportLanguages });
|
|
98
|
+
Device.instance = device;
|
|
99
|
+
return device;
|
|
100
|
+
}
|
|
101
|
+
const { loadCapacitorDevice, loadCapacitorHaptics, loadCapacitorKeyboard, loadCapacitorSafeArea } = await import(
|
|
102
|
+
"./capacitor"
|
|
103
|
+
);
|
|
28
104
|
const [{ Device: CapacitorDevice }, { Keyboard }, { Haptics, ImpactStyle }, { SafeArea }] = await Promise.all([
|
|
29
105
|
loadCapacitorDevice(),
|
|
30
106
|
loadCapacitorKeyboard(),
|
|
@@ -61,7 +137,7 @@ export class Device {
|
|
|
61
137
|
lang: string;
|
|
62
138
|
topSafeArea: number;
|
|
63
139
|
bottomSafeArea: number;
|
|
64
|
-
isMobile =
|
|
140
|
+
isMobile = isMobileDevice();
|
|
65
141
|
#keyboard: DeviceInitOption["keyboard"];
|
|
66
142
|
#haptics: DeviceInitOption["haptics"];
|
|
67
143
|
#impactStyle: DeviceInitOption["impactStyle"];
|
|
@@ -89,10 +165,10 @@ export class Device {
|
|
|
89
165
|
}
|
|
90
166
|
listenKeyboardChanged(onKeyboardChanged: (height: number) => void) {
|
|
91
167
|
if (this.info.platform === "web") return;
|
|
92
|
-
void this.#keyboard.addListener("keyboardWillShow", (keyboard:
|
|
168
|
+
void this.#keyboard.addListener("keyboardWillShow", (keyboard: CapacitorKeyboardInfo) => {
|
|
93
169
|
onKeyboardChanged(keyboard.keyboardHeight);
|
|
94
170
|
});
|
|
95
|
-
void this.#keyboard.addListener("keyboardDidShow", (keyboard:
|
|
171
|
+
void this.#keyboard.addListener("keyboardDidShow", (keyboard: CapacitorKeyboardInfo) => {
|
|
96
172
|
onKeyboardChanged(keyboard.keyboardHeight);
|
|
97
173
|
});
|
|
98
174
|
void this.#keyboard.addListener("keyboardWillHide", () => {
|
package/client/index.ts
CHANGED
package/client/storage.ts
CHANGED
|
@@ -1,33 +1,57 @@
|
|
|
1
1
|
import { getEnv } from "akanjs/base";
|
|
2
|
+
import { loadCapacitorPreferences } from "./capacitor";
|
|
3
|
+
|
|
4
|
+
const getLocalStorageItem = (key: string) => localStorage.getItem(key);
|
|
5
|
+
|
|
6
|
+
const setLocalStorageItem = (key: string, value: string) => {
|
|
7
|
+
localStorage.setItem(key, value);
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const removeLocalStorageItem = (key: string) => {
|
|
11
|
+
localStorage.removeItem(key);
|
|
12
|
+
};
|
|
2
13
|
|
|
3
14
|
export const storage = {
|
|
4
15
|
getItem: async (key: string) => {
|
|
5
|
-
|
|
6
|
-
if (
|
|
7
|
-
|
|
8
|
-
|
|
16
|
+
const env = getEnv();
|
|
17
|
+
if (env.side === "server") return;
|
|
18
|
+
if (env.renderMode === "ssr") return getLocalStorageItem(key);
|
|
19
|
+
try {
|
|
20
|
+
const { Preferences } = await loadCapacitorPreferences();
|
|
9
21
|
return (await Preferences.get({ key })).value;
|
|
22
|
+
} catch {
|
|
23
|
+
return getLocalStorageItem(key);
|
|
10
24
|
}
|
|
11
25
|
},
|
|
12
26
|
setItem: async (key: string, value: string) => {
|
|
13
|
-
|
|
14
|
-
if (
|
|
15
|
-
|
|
27
|
+
const env = getEnv();
|
|
28
|
+
if (env.side === "server") return;
|
|
29
|
+
if (env.renderMode === "ssr") {
|
|
30
|
+
setLocalStorageItem(key, value);
|
|
16
31
|
return;
|
|
17
|
-
}
|
|
18
|
-
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const { Preferences } = await loadCapacitorPreferences();
|
|
19
35
|
await Preferences.set({ key, value });
|
|
20
36
|
return;
|
|
37
|
+
} catch {
|
|
38
|
+
setLocalStorageItem(key, value);
|
|
39
|
+
return;
|
|
21
40
|
}
|
|
22
41
|
},
|
|
23
42
|
removeItem: async (key: string) => {
|
|
24
|
-
|
|
25
|
-
if (
|
|
26
|
-
|
|
43
|
+
const env = getEnv();
|
|
44
|
+
if (env.side === "server") return;
|
|
45
|
+
if (env.renderMode === "ssr") {
|
|
46
|
+
removeLocalStorageItem(key);
|
|
27
47
|
return;
|
|
28
|
-
}
|
|
29
|
-
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const { Preferences } = await loadCapacitorPreferences();
|
|
30
51
|
return Preferences.remove({ key });
|
|
52
|
+
} catch {
|
|
53
|
+
removeLocalStorageItem(key);
|
|
54
|
+
return;
|
|
31
55
|
}
|
|
32
56
|
},
|
|
33
57
|
};
|
package/document/filterMeta.ts
CHANGED
|
@@ -93,6 +93,11 @@ export const getFilterSortByKey = (modelRef: FilterCls, key: string) => {
|
|
|
93
93
|
return filterMeta.sort[key];
|
|
94
94
|
};
|
|
95
95
|
|
|
96
|
+
export const fillMissingFilterArgs = (filterInfo: FilterInfo, args: unknown[]) => {
|
|
97
|
+
if (args.length >= filterInfo.args.length) return args;
|
|
98
|
+
return [...args, ...Array(filterInfo.args.length - args.length).fill(undefined)];
|
|
99
|
+
};
|
|
100
|
+
|
|
96
101
|
export type BaseFilterSortKey = "latest" | "oldest";
|
|
97
102
|
export type BaseFilterQueryKey = "any";
|
|
98
103
|
export type BaseFilterKey = BaseFilterSortKey | BaseFilterQueryKey;
|
|
@@ -19,6 +19,16 @@ import { WsClient } from "./wsClient";
|
|
|
19
19
|
type FetchHandler = (...args: unknown[]) => PromiseOrObject<unknown>;
|
|
20
20
|
type UnknownRecord = Record<string, unknown>;
|
|
21
21
|
|
|
22
|
+
const isNullableArg = (arg: SerializedArg) => arg.nullable ?? arg.type === "search";
|
|
23
|
+
|
|
24
|
+
const normalizeQueryArgs = (queryArgs: unknown[], args: SerializedArg[]) => {
|
|
25
|
+
let length = Math.min(queryArgs.length, args.length);
|
|
26
|
+
while (length > 0 && isNullableArg(args[length - 1]) && queryArgs[length - 1] == null) length--;
|
|
27
|
+
return queryArgs.slice(0, length);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const expandQueryArgs = (queryArgs: unknown[], args: SerializedArg[]) => args.map((_, idx) => queryArgs[idx]);
|
|
31
|
+
|
|
22
32
|
export type FetchProxy<
|
|
23
33
|
FetchType = unknown,
|
|
24
34
|
SliceMetaObj extends Record<string, SliceMeta> = Record<never, never>,
|
|
@@ -411,14 +421,18 @@ export class FetchClient {
|
|
|
411
421
|
const listFn = this.handler[names.list] as (...args: unknown[]) => Promise<unknown[]>;
|
|
412
422
|
const insightFn = this.handler[names.insight] as (...args: unknown[]) => Promise<unknown>;
|
|
413
423
|
const initFn = async (...argData: unknown[]) => {
|
|
414
|
-
const queryArgs =
|
|
424
|
+
const queryArgs = normalizeQueryArgs(
|
|
425
|
+
Array.from({ length: Math.min(argData.length, argLength) }, (_, idx) => argData[idx]),
|
|
426
|
+
slice.args,
|
|
427
|
+
);
|
|
428
|
+
const fetchQueryArgs = expandQueryArgs(queryArgs, slice.args);
|
|
415
429
|
const option = (argData[argLength] ?? {}) as { page?: number; limit?: number; sort?: string; insight?: boolean };
|
|
416
430
|
const { page = 1, limit = 20, sort = "latest", insight: fetchInsight = true } = option;
|
|
417
431
|
const skip = (page - 1) * limit;
|
|
418
432
|
|
|
419
433
|
const [modelObjList, modelObjInsight] = (await Promise.all([
|
|
420
|
-
listFn(...
|
|
421
|
-
fetchInsight ? insightFn(...
|
|
434
|
+
listFn(...fetchQueryArgs, skip, limit, sort, { ...option, crystalize: false }),
|
|
435
|
+
fetchInsight ? insightFn(...fetchQueryArgs, { ...option, crystalize: false }) : null,
|
|
422
436
|
])) as unknown as [BaseObject[], BaseInsight];
|
|
423
437
|
const modelList = new DataList(modelObjList.map((modelObj) => new cnst.light(modelObj)));
|
|
424
438
|
const modelInsight = new cnst.insight(modelObjInsight);
|