@jskit-ai/mobile-capacitor 0.1.1
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/README.md +320 -0
- package/package.descriptor.mjs +114 -0
- package/package.json +17 -0
- package/src/client/index.js +13 -0
- package/src/client/providers/MobileCapacitorClientProvider.js +144 -0
- package/src/client/runtime/apiRequestClient.js +136 -0
- package/src/client/runtime/globalCapacitorAppAdapter.js +136 -0
- package/src/client/runtime/mobileCapacitorRuntime.js +215 -0
- package/src/client/runtime/oauthLaunchClient.js +45 -0
- package/src/server/buildTemplateContext.js +231 -0
- package/templates/capacitor.config.json +10 -0
- package/templates/mobile-capacitor.md +47 -0
- package/test/provider.test.js +187 -0
- package/test/runtime.test.js +487 -0
package/README.md
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
# `@jskit-ai/mobile-capacitor`
|
|
2
|
+
|
|
3
|
+
Stage 1 Android mobile-shell support for JSKIT.
|
|
4
|
+
|
|
5
|
+
This package does not turn JSKIT into a native UI framework. It packages the
|
|
6
|
+
existing JSKIT web client into a Capacitor Android shell and owns the narrow
|
|
7
|
+
glue needed for:
|
|
8
|
+
|
|
9
|
+
- launch URL intake
|
|
10
|
+
- deep-link normalization into normal JSKIT routes
|
|
11
|
+
- auth callback completion on `/auth/login`
|
|
12
|
+
- Android shell file rendering from `config.mobile`
|
|
13
|
+
- `jskit mobile` add/sync/run/build/doctor/devices/tunnel/restart commands
|
|
14
|
+
- `jskit mobile` add/sync/run/build/doctor/devices/tunnel/restart/dev commands
|
|
15
|
+
|
|
16
|
+
## Stage 1 Scope
|
|
17
|
+
|
|
18
|
+
Stage 1 promises:
|
|
19
|
+
|
|
20
|
+
- Android only
|
|
21
|
+
- JSKIT web UI inside a Capacitor shell
|
|
22
|
+
- bundled production assets
|
|
23
|
+
- optional live `dev_server` runtime mode
|
|
24
|
+
- OAuth start opens the external browser/custom tab when running inside the
|
|
25
|
+
Capacitor shell
|
|
26
|
+
- browser-to-app auth callback completion through the existing JSKIT auth flow
|
|
27
|
+
- release AAB command path
|
|
28
|
+
|
|
29
|
+
Stage 1 does not promise:
|
|
30
|
+
|
|
31
|
+
- native UI abstractions
|
|
32
|
+
- iOS parity
|
|
33
|
+
- push notifications
|
|
34
|
+
- offline-first behavior
|
|
35
|
+
- billing integrations
|
|
36
|
+
- verified app links
|
|
37
|
+
- direct Capacitor imports as a normal feature-package pattern
|
|
38
|
+
|
|
39
|
+
## `config.mobile`
|
|
40
|
+
|
|
41
|
+
`config.mobile` is the source of truth.
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
config.mobile = {
|
|
45
|
+
enabled: true,
|
|
46
|
+
strategy: "capacitor",
|
|
47
|
+
appId: "ai.jskit.exampleapp",
|
|
48
|
+
appName: "Example App",
|
|
49
|
+
assetMode: "bundled", // bundled | dev_server
|
|
50
|
+
devServerUrl: "",
|
|
51
|
+
apiBaseUrl: "http://10.0.2.2:3000",
|
|
52
|
+
auth: {
|
|
53
|
+
callbackPath: "/auth/login",
|
|
54
|
+
customScheme: "exampleapp",
|
|
55
|
+
appLinkDomains: []
|
|
56
|
+
},
|
|
57
|
+
android: {
|
|
58
|
+
packageName: "ai.jskit.exampleapp",
|
|
59
|
+
minSdk: 26,
|
|
60
|
+
targetSdk: 35,
|
|
61
|
+
versionCode: 1,
|
|
62
|
+
versionName: "0.1.0"
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Important rules:
|
|
68
|
+
|
|
69
|
+
- `callbackPath` stays a normal JSKIT path. Stage 1 does not create a second
|
|
70
|
+
mobile-only route system.
|
|
71
|
+
- `assetMode="bundled"` is the release path.
|
|
72
|
+
- `assetMode="dev_server"` is for local development only and requires
|
|
73
|
+
`devServerUrl`.
|
|
74
|
+
|
|
75
|
+
## Generated Artifacts
|
|
76
|
+
|
|
77
|
+
`jskit mobile add capacitor` installs the package and manages:
|
|
78
|
+
|
|
79
|
+
- `capacitor.config.json`
|
|
80
|
+
- `android/` via `cap add android`
|
|
81
|
+
- `.jskit/mobile-capacitor.md`
|
|
82
|
+
- the managed deep-link filter in `android/app/src/main/AndroidManifest.xml`
|
|
83
|
+
- Android identity files refreshed from `config.mobile`:
|
|
84
|
+
- `android/app/build.gradle`
|
|
85
|
+
- `android/variables.gradle`
|
|
86
|
+
- `android/app/src/main/res/values/strings.xml`
|
|
87
|
+
- `MainActivity.java` or `MainActivity.kt`
|
|
88
|
+
|
|
89
|
+
These files are not app-owned drift zones. `jskit mobile sync android` is
|
|
90
|
+
expected to refresh them from `config.mobile`.
|
|
91
|
+
|
|
92
|
+
## `capacitor.config.json`
|
|
93
|
+
|
|
94
|
+
Stage 1 currently renders:
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"appId": "ai.jskit.exampleapp",
|
|
99
|
+
"appName": "Example App",
|
|
100
|
+
"webDir": "dist",
|
|
101
|
+
"plugins": {
|
|
102
|
+
"CapacitorHttp": {
|
|
103
|
+
"enabled": true
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
In `assetMode="dev_server"`, it additionally renders:
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"server": {
|
|
114
|
+
"url": "http://10.0.2.2:5173/",
|
|
115
|
+
"cleartext": true
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Notes:
|
|
121
|
+
|
|
122
|
+
- `webDir` is always `dist` in Stage 1.
|
|
123
|
+
- `server.url` is only present in `dev_server` mode.
|
|
124
|
+
- `cleartext: true` is only rendered when the dev server URL uses `http`.
|
|
125
|
+
- `CapacitorHttp.enabled = true` is always rendered so shell `fetch` / XHR
|
|
126
|
+
traffic can use the native HTTP bridge.
|
|
127
|
+
- JSKIT does not currently set `androidScheme` explicitly. Stage 1 relies on
|
|
128
|
+
Capacitor's default local Android scheme instead of inventing another app
|
|
129
|
+
config field.
|
|
130
|
+
|
|
131
|
+
## Command Contract
|
|
132
|
+
|
|
133
|
+
Install path:
|
|
134
|
+
|
|
135
|
+
- canonical package install:
|
|
136
|
+
- `jskit add package @jskit-ai/mobile-capacitor`
|
|
137
|
+
- convenience alias:
|
|
138
|
+
- `jskit mobile add capacitor`
|
|
139
|
+
- both paths now use the same package-owned install hooks:
|
|
140
|
+
- seed `config.mobile` when missing
|
|
141
|
+
- install Capacitor dependencies
|
|
142
|
+
- provision `android/` with `cap add android` when needed
|
|
143
|
+
- refresh the managed Android shell identity/deep-link files
|
|
144
|
+
|
|
145
|
+
`jskit mobile add capacitor`
|
|
146
|
+
|
|
147
|
+
- installs `@jskit-ai/mobile-capacitor`
|
|
148
|
+
- installs the required Capacitor packages
|
|
149
|
+
- adds managed npm scripts
|
|
150
|
+
- seeds `config.mobile` with working JSKIT local-dev defaults when it is missing
|
|
151
|
+
- renders managed config files
|
|
152
|
+
- runs `cap add android` if `android/` does not exist
|
|
153
|
+
|
|
154
|
+
`jskit mobile devices android`
|
|
155
|
+
|
|
156
|
+
- runs `adb devices -l`
|
|
157
|
+
- prints the Android targets currently visible to adb
|
|
158
|
+
|
|
159
|
+
`jskit mobile dev android [--target <device-id>]`
|
|
160
|
+
|
|
161
|
+
- runs the standard local-phone sequence in one command:
|
|
162
|
+
- `jskit mobile sync android`
|
|
163
|
+
- `jskit mobile run android --target <device-id>`
|
|
164
|
+
- `jskit mobile tunnel android --target <device-id>`
|
|
165
|
+
- prints each step and the full command before it runs
|
|
166
|
+
- if `--target` is omitted, uses the first Android device from `adb devices -l`
|
|
167
|
+
|
|
168
|
+
`jskit mobile sync android`
|
|
169
|
+
|
|
170
|
+
- refreshes managed mobile files from `config.mobile`
|
|
171
|
+
- refreshes Android shell identity from `config.mobile`
|
|
172
|
+
- builds the web app
|
|
173
|
+
- runs `cap sync android`
|
|
174
|
+
|
|
175
|
+
`jskit mobile tunnel android --target <device-id>`
|
|
176
|
+
|
|
177
|
+
- resolves the loopback port from `config.mobile.apiBaseUrl`, or uses `--port`
|
|
178
|
+
- runs `adb -s <device-id> reverse tcp:<port> tcp:<port>`
|
|
179
|
+
- prints `adb reverse --list` so the active tunnel is visible
|
|
180
|
+
|
|
181
|
+
`jskit mobile restart android --target <device-id>`
|
|
182
|
+
|
|
183
|
+
- clears app data with `pm clear`
|
|
184
|
+
- force-stops the Android app
|
|
185
|
+
- cold-starts `MainActivity`
|
|
186
|
+
|
|
187
|
+
`jskit mobile run android [--target <device-id>]`
|
|
188
|
+
|
|
189
|
+
- in `bundled` mode:
|
|
190
|
+
- runs the normal sync flow first
|
|
191
|
+
- in `dev_server` mode:
|
|
192
|
+
- refreshes managed config files
|
|
193
|
+
- syncs the Android shell without rebuilding bundled assets
|
|
194
|
+
- then runs `cap run android`
|
|
195
|
+
- forwards `--target` to Capacitor when you want a specific attached phone/emulator
|
|
196
|
+
|
|
197
|
+
`jskit mobile build android`
|
|
198
|
+
|
|
199
|
+
- requires `assetMode="bundled"`
|
|
200
|
+
- builds the web app
|
|
201
|
+
- syncs Android
|
|
202
|
+
- runs Gradle `bundleRelease`
|
|
203
|
+
|
|
204
|
+
`jskit mobile doctor`
|
|
205
|
+
|
|
206
|
+
- validates `config.mobile`
|
|
207
|
+
- validates managed file freshness
|
|
208
|
+
- validates the Android shell is installed
|
|
209
|
+
- validates the managed deep-link filter
|
|
210
|
+
- validates Android SDK basics needed by the shell path
|
|
211
|
+
|
|
212
|
+
## Stage 1 Modes
|
|
213
|
+
|
|
214
|
+
Stage 1 distinguishes only these modes:
|
|
215
|
+
|
|
216
|
+
- normal web app with no mobile shell
|
|
217
|
+
- mobile-shell packaged build
|
|
218
|
+
- mobile-shell dev-server build
|
|
219
|
+
|
|
220
|
+
That is enough target awareness for Stage 1. It is not a broad mobile runtime
|
|
221
|
+
target model.
|
|
222
|
+
|
|
223
|
+
## Auth And Routing Contract
|
|
224
|
+
|
|
225
|
+
Stage 1 keeps routing web-path based:
|
|
226
|
+
|
|
227
|
+
- custom-scheme URLs normalize into ordinary JSKIT router paths
|
|
228
|
+
- allowed HTTPS app links normalize into ordinary JSKIT router paths
|
|
229
|
+
- OAuth start stays the normal auth-web route on the server, but
|
|
230
|
+
`@jskit-ai/mobile-capacitor` launches it through `@capacitor/browser` when the
|
|
231
|
+
app is running inside the shell
|
|
232
|
+
- auth callback completion still happens through `/auth/login`
|
|
233
|
+
- intended destination is carried through OAuth start and OAuth completion
|
|
234
|
+
|
|
235
|
+
Unknown deep-link paths are still handed to the normal app router. Stage 1 does
|
|
236
|
+
not invent a parallel mobile fallback system.
|
|
237
|
+
|
|
238
|
+
## Icons And Splash Screens
|
|
239
|
+
|
|
240
|
+
Stage 1 uses a documented manual workflow:
|
|
241
|
+
|
|
242
|
+
- update the Android shell resources in `android/app/src/main/res/`
|
|
243
|
+
- regenerate launcher assets with Android Studio or your normal Android asset
|
|
244
|
+
pipeline
|
|
245
|
+
- keep the package/app identity values managed by JSKIT, but treat visual
|
|
246
|
+
launcher assets as Android-owned assets
|
|
247
|
+
|
|
248
|
+
JSKIT does not yet automate icon or splash asset generation.
|
|
249
|
+
|
|
250
|
+
## Release Notes
|
|
251
|
+
|
|
252
|
+
Release builds are expected to use:
|
|
253
|
+
|
|
254
|
+
- `config.mobile.assetMode = "bundled"`
|
|
255
|
+
- `jskit mobile build android`
|
|
256
|
+
|
|
257
|
+
That command is the Stage 1 AAB path. A working Android SDK still needs:
|
|
258
|
+
|
|
259
|
+
- platform `android-35`
|
|
260
|
+
- build-tools
|
|
261
|
+
- accepted SDK licenses
|
|
262
|
+
|
|
263
|
+
Signing remains an operational workflow:
|
|
264
|
+
|
|
265
|
+
- create an upload key
|
|
266
|
+
- configure Play App Signing
|
|
267
|
+
- keep signing secrets out of app source
|
|
268
|
+
|
|
269
|
+
## Play Store Operational Checklist
|
|
270
|
+
|
|
271
|
+
Before publishing, confirm:
|
|
272
|
+
|
|
273
|
+
- release output is an AAB
|
|
274
|
+
- target SDK matches current Play requirements
|
|
275
|
+
- Play App Signing is configured
|
|
276
|
+
- the Data safety form matches the app's actual behavior
|
|
277
|
+
- account deletion requirements are met if the app offers user accounts
|
|
278
|
+
|
|
279
|
+
These are operational docs, not a JSKIT runtime abstraction.
|
|
280
|
+
|
|
281
|
+
## Domain Notes
|
|
282
|
+
|
|
283
|
+
Fitness/health and billing are Stage 1 documentation concerns only:
|
|
284
|
+
|
|
285
|
+
- health/fitness apps still need to satisfy store policy for their domain
|
|
286
|
+
- rewarded-ad or monetized flows still need to satisfy provider/store policy
|
|
287
|
+
- billing decisions should be designed explicitly; Stage 1 mobile-shell support
|
|
288
|
+
does not solve billing architecture
|
|
289
|
+
|
|
290
|
+
## Manual QA Checklist
|
|
291
|
+
|
|
292
|
+
Minimum real-device or emulator QA for a Stage 1 app:
|
|
293
|
+
|
|
294
|
+
- app installs and launches
|
|
295
|
+
- bundled assets load in production mode
|
|
296
|
+
- API calls hit the real backend
|
|
297
|
+
- OAuth start opens the external browser/custom tab when the app is running in
|
|
298
|
+
the shell
|
|
299
|
+
- login returns through the custom scheme to `/auth/login`
|
|
300
|
+
- intended destination resumes after OAuth
|
|
301
|
+
- workspace routes open through deep links
|
|
302
|
+
|
|
303
|
+
## Local Phone Development
|
|
304
|
+
|
|
305
|
+
When `config.mobile.apiBaseUrl` points at a local loopback server like
|
|
306
|
+
`http://127.0.0.1:3000`, the phone cannot reach your laptop directly. The
|
|
307
|
+
required JSKIT workflow is:
|
|
308
|
+
|
|
309
|
+
1. run the local backend
|
|
310
|
+
2. `jskit mobile sync android`
|
|
311
|
+
3. install or launch the shell
|
|
312
|
+
4. `jskit mobile tunnel android --target <device-id>`
|
|
313
|
+
5. `jskit mobile restart android --target <device-id>` when you need a clean
|
|
314
|
+
signed-out state
|
|
315
|
+
|
|
316
|
+
If `config.mobile.apiBaseUrl` points at a real remote `https://...` backend,
|
|
317
|
+
the reverse tunnel is not needed.
|
|
318
|
+
- an unknown app deep link is handed to the normal router/404 path without
|
|
319
|
+
crashing the shell
|
|
320
|
+
- Android back button navigates back or exits cleanly at the app root
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
export default Object.freeze({
|
|
2
|
+
packageVersion: 1,
|
|
3
|
+
packageId: "@jskit-ai/mobile-capacitor",
|
|
4
|
+
version: "0.1.1",
|
|
5
|
+
kind: "runtime",
|
|
6
|
+
description: "Thin Capacitor client integration for JSKIT mobile-shell launch routing and auth callback completion.",
|
|
7
|
+
dependsOn: [
|
|
8
|
+
"@jskit-ai/kernel",
|
|
9
|
+
"@jskit-ai/shell-web"
|
|
10
|
+
],
|
|
11
|
+
capabilities: {
|
|
12
|
+
provides: ["mobile.capacitor"],
|
|
13
|
+
requires: []
|
|
14
|
+
},
|
|
15
|
+
runtime: {
|
|
16
|
+
server: {
|
|
17
|
+
providers: []
|
|
18
|
+
},
|
|
19
|
+
client: {
|
|
20
|
+
providers: [
|
|
21
|
+
{
|
|
22
|
+
entrypoint: "src/client/providers/MobileCapacitorClientProvider.js",
|
|
23
|
+
export: "MobileCapacitorClientProvider"
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
lifecycle: {
|
|
29
|
+
install: {
|
|
30
|
+
prepare: {
|
|
31
|
+
entrypoint: "src/server/buildTemplateContext.js",
|
|
32
|
+
export: "prepareInstallHook"
|
|
33
|
+
},
|
|
34
|
+
finalize: {
|
|
35
|
+
entrypoint: "src/server/buildTemplateContext.js",
|
|
36
|
+
export: "finalizeInstallHook",
|
|
37
|
+
managesNpmInstall: true
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
metadata: {
|
|
42
|
+
apiSummary: {
|
|
43
|
+
surfaces: [
|
|
44
|
+
{
|
|
45
|
+
subpath: "./client",
|
|
46
|
+
summary: "Exports the Capacitor client provider, launch adapter helpers, and mobile-shell runtime."
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
containerTokens: {
|
|
50
|
+
server: [],
|
|
51
|
+
client: [
|
|
52
|
+
"mobile.capacitor.client.runtime",
|
|
53
|
+
"mobile.capacitor.adapter.client"
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
mutations: {
|
|
59
|
+
dependencies: {
|
|
60
|
+
runtime: {
|
|
61
|
+
"@capacitor/android": "^7.4.3",
|
|
62
|
+
"@capacitor/app": "^7.1.0",
|
|
63
|
+
"@capacitor/browser": "^7.0.1",
|
|
64
|
+
"@capacitor/core": "^7.4.3",
|
|
65
|
+
"@jskit-ai/kernel": "0.1.64",
|
|
66
|
+
"@jskit-ai/shell-web": "0.1.63"
|
|
67
|
+
},
|
|
68
|
+
dev: {
|
|
69
|
+
"@capacitor/cli": "^7.4.3"
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
packageJson: {
|
|
73
|
+
scripts: {
|
|
74
|
+
"mobile:dev:android": "jskit mobile dev android",
|
|
75
|
+
"mobile:devices:android": "jskit mobile devices android",
|
|
76
|
+
"mobile:sync:android": "jskit mobile sync android",
|
|
77
|
+
"mobile:tunnel:android": "jskit mobile tunnel android",
|
|
78
|
+
"mobile:restart:android": "jskit mobile restart android",
|
|
79
|
+
"mobile:run:android": "jskit mobile run android",
|
|
80
|
+
"mobile:build:web": "npm run build",
|
|
81
|
+
"mobile:build:android": "jskit mobile build android"
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
procfile: {},
|
|
85
|
+
vite: {
|
|
86
|
+
proxy: []
|
|
87
|
+
},
|
|
88
|
+
text: [],
|
|
89
|
+
files: [
|
|
90
|
+
{
|
|
91
|
+
from: "templates/capacitor.config.json",
|
|
92
|
+
to: "capacitor.config.json",
|
|
93
|
+
reason: "Install Capacitor shell configuration rendered from config.mobile.",
|
|
94
|
+
category: "mobile-capacitor",
|
|
95
|
+
id: "mobile-capacitor-config",
|
|
96
|
+
templateContext: {
|
|
97
|
+
entrypoint: "src/server/buildTemplateContext.js",
|
|
98
|
+
export: "buildTemplateContext"
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
from: "templates/mobile-capacitor.md",
|
|
103
|
+
to: ".jskit/mobile-capacitor.md",
|
|
104
|
+
reason: "Install managed mobile-shell notes for the Capacitor Android integration.",
|
|
105
|
+
category: "mobile-capacitor",
|
|
106
|
+
id: "mobile-capacitor-notes",
|
|
107
|
+
templateContext: {
|
|
108
|
+
entrypoint: "src/server/buildTemplateContext.js",
|
|
109
|
+
export: "buildTemplateContext"
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jskit-ai/mobile-capacitor",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"test": "node --test"
|
|
7
|
+
},
|
|
8
|
+
"exports": {
|
|
9
|
+
"./client": "./src/client/index.js",
|
|
10
|
+
"./client/runtime/mobileCapacitorRuntime": "./src/client/runtime/mobileCapacitorRuntime.js",
|
|
11
|
+
"./client/runtime/globalCapacitorAppAdapter": "./src/client/runtime/globalCapacitorAppAdapter.js"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@capacitor/browser": "^7.0.1",
|
|
15
|
+
"@jskit-ai/kernel": "0.1.64"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { MobileCapacitorClientProvider } from "./providers/MobileCapacitorClientProvider.js";
|
|
2
|
+
|
|
3
|
+
export { MobileCapacitorClientProvider } from "./providers/MobileCapacitorClientProvider.js";
|
|
4
|
+
export { createMobileCapacitorRuntime } from "./runtime/mobileCapacitorRuntime.js";
|
|
5
|
+
export {
|
|
6
|
+
createGlobalCapacitorAppAdapter,
|
|
7
|
+
createNoopCapacitorAppAdapter,
|
|
8
|
+
resolveCapacitorAppPlugin
|
|
9
|
+
} from "./runtime/globalCapacitorAppAdapter.js";
|
|
10
|
+
|
|
11
|
+
const clientProviders = Object.freeze([MobileCapacitorClientProvider]);
|
|
12
|
+
|
|
13
|
+
export { clientProviders };
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { getClientAppConfig, resolveMobileConfig } from "@jskit-ai/kernel/client";
|
|
2
|
+
import { createGlobalCapacitorAppAdapter } from "../runtime/globalCapacitorAppAdapter.js";
|
|
3
|
+
import { createCapacitorAwareFetch } from "../runtime/apiRequestClient.js";
|
|
4
|
+
import { createMobileCapacitorRuntime } from "../runtime/mobileCapacitorRuntime.js";
|
|
5
|
+
import { createCapacitorAwareOAuthLaunchClient } from "../runtime/oauthLaunchClient.js";
|
|
6
|
+
|
|
7
|
+
const AUTH_OAUTH_LAUNCH_CLIENT_TOKEN = "auth.oauth-launch.client";
|
|
8
|
+
const GLOBAL_FETCH_RESTORE_KEY = Symbol.for("jskit.mobile.capacitor.restoreFetch");
|
|
9
|
+
|
|
10
|
+
function installCapacitorAwareGlobalFetch({ adapter = null, apiBaseUrl = "", globalObject = globalThis } = {}) {
|
|
11
|
+
if (!globalObject || typeof globalObject !== "object") {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
if (adapter?.available !== true) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
if (typeof globalObject.fetch !== "function") {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
if (typeof globalObject[GLOBAL_FETCH_RESTORE_KEY] === "function") {
|
|
21
|
+
return globalObject[GLOBAL_FETCH_RESTORE_KEY];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const originalFetch = globalObject.fetch;
|
|
25
|
+
const wrappedFetch = createCapacitorAwareFetch({
|
|
26
|
+
fetchImpl: (...args) => originalFetch(...args),
|
|
27
|
+
adapter,
|
|
28
|
+
apiBaseUrl
|
|
29
|
+
});
|
|
30
|
+
globalObject.fetch = wrappedFetch;
|
|
31
|
+
|
|
32
|
+
const restore = () => {
|
|
33
|
+
if (globalObject.fetch === wrappedFetch) {
|
|
34
|
+
globalObject.fetch = originalFetch;
|
|
35
|
+
}
|
|
36
|
+
if (globalObject[GLOBAL_FETCH_RESTORE_KEY] === restore) {
|
|
37
|
+
delete globalObject[GLOBAL_FETCH_RESTORE_KEY];
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
globalObject[GLOBAL_FETCH_RESTORE_KEY] = restore;
|
|
41
|
+
return restore;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
class MobileCapacitorClientProvider {
|
|
45
|
+
static id = "mobile.capacitor.client";
|
|
46
|
+
|
|
47
|
+
static dependsOn = ["shell.web.client"];
|
|
48
|
+
|
|
49
|
+
register(app) {
|
|
50
|
+
if (!app || typeof app.singleton !== "function") {
|
|
51
|
+
throw new Error("MobileCapacitorClientProvider requires application singleton().");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let adapterInstance = null;
|
|
55
|
+
if (!app.has || app.has("mobile.capacitor.adapter.client") !== true) {
|
|
56
|
+
adapterInstance = createGlobalCapacitorAppAdapter();
|
|
57
|
+
app.singleton("mobile.capacitor.adapter.client", () => adapterInstance);
|
|
58
|
+
} else if (typeof app.make === "function") {
|
|
59
|
+
adapterInstance = app.make("mobile.capacitor.adapter.client");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const mobileConfig = resolveMobileConfig(getClientAppConfig());
|
|
63
|
+
installCapacitorAwareGlobalFetch({
|
|
64
|
+
adapter: adapterInstance,
|
|
65
|
+
apiBaseUrl: mobileConfig.apiBaseUrl
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (!app.has || app.has(AUTH_OAUTH_LAUNCH_CLIENT_TOKEN) !== true) {
|
|
69
|
+
app.singleton(AUTH_OAUTH_LAUNCH_CLIENT_TOKEN, (scope) => {
|
|
70
|
+
return createCapacitorAwareOAuthLaunchClient({
|
|
71
|
+
adapter: scope.make("mobile.capacitor.adapter.client"),
|
|
72
|
+
apiBaseUrl: mobileConfig.apiBaseUrl
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
app.singleton("mobile.capacitor.client.runtime", (scope) => {
|
|
77
|
+
if (!scope.has("jskit.client.router")) {
|
|
78
|
+
throw new Error("MobileCapacitorClientProvider requires jskit.client.router.");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const placementRuntime = scope.has("runtime.web-placement.client")
|
|
82
|
+
? scope.make("runtime.web-placement.client")
|
|
83
|
+
: null;
|
|
84
|
+
const authCallbackCompleter = scope.has("auth.mobile-callback.client")
|
|
85
|
+
? scope.make("auth.mobile-callback.client")
|
|
86
|
+
: null;
|
|
87
|
+
const authGuardRuntime = scope.has("runtime.auth-guard.client")
|
|
88
|
+
? scope.make("runtime.auth-guard.client")
|
|
89
|
+
: null;
|
|
90
|
+
|
|
91
|
+
return createMobileCapacitorRuntime({
|
|
92
|
+
router: scope.make("jskit.client.router"),
|
|
93
|
+
mobileConfig: getClientAppConfig().mobile || {},
|
|
94
|
+
adapter: scope.make("mobile.capacitor.adapter.client"),
|
|
95
|
+
placementRuntime,
|
|
96
|
+
authCallbackCompleter,
|
|
97
|
+
authGuardRuntime
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async boot(app) {
|
|
103
|
+
if (!app || typeof app.make !== "function" || typeof app.has !== "function") {
|
|
104
|
+
throw new Error("MobileCapacitorClientProvider boot requires application make()/has().");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!app.has("mobile.capacitor.client.runtime")) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const mobileConfig = resolveMobileConfig(getClientAppConfig());
|
|
112
|
+
installCapacitorAwareGlobalFetch({
|
|
113
|
+
adapter: app.make("mobile.capacitor.adapter.client"),
|
|
114
|
+
apiBaseUrl: mobileConfig.apiBaseUrl
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const runtime = app.make("mobile.capacitor.client.runtime");
|
|
118
|
+
if (runtime && typeof runtime.initialize === "function") {
|
|
119
|
+
await runtime.initialize();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
shutdown(app) {
|
|
124
|
+
if (!app || typeof app.has !== "function" || typeof app.make !== "function") {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!app.has("mobile.capacitor.client.runtime")) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const restoreFetch = globalThis[GLOBAL_FETCH_RESTORE_KEY];
|
|
133
|
+
if (typeof restoreFetch === "function") {
|
|
134
|
+
restoreFetch();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const runtime = app.make("mobile.capacitor.client.runtime");
|
|
138
|
+
if (runtime && typeof runtime.dispose === "function") {
|
|
139
|
+
runtime.dispose();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export { MobileCapacitorClientProvider, installCapacitorAwareGlobalFetch };
|