@jskit-ai/mobile-capacitor 0.1.94 → 0.1.96
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/package.json +11 -76
- package/patterns/android-application/PATTERN.md +71 -0
- package/src/client/index.js +4 -7
- package/src/client/providers/MobileCapacitorClientProvider.js +65 -102
- package/test/provider.test.js +108 -136
- package/src/server/buildTemplateContext.js +0 -231
- package/templates/mobile-capacitor.md +0 -46
- /package/{templates → patterns/android-application/example}/capacitor.config.json +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/mobile-capacitor",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.96",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@capacitor/browser": "^7.0.1",
|
|
15
15
|
"@capacitor/core": "^7.4.3",
|
|
16
|
-
"@jskit-ai/kernel": "0.1.
|
|
17
|
-
"@jskit-ai/shell-web": "0.1.
|
|
16
|
+
"@jskit-ai/kernel": "0.1.161",
|
|
17
|
+
"@jskit-ai/shell-web": "0.1.165"
|
|
18
18
|
},
|
|
19
19
|
"description": "Thin Capacitor client integration for JSKIT mobile-shell launch routing and auth callback completion.",
|
|
20
20
|
"jskit": {
|
|
@@ -34,23 +34,14 @@
|
|
|
34
34
|
{
|
|
35
35
|
"entrypoint": "src/client/providers/MobileCapacitorClientProvider.js",
|
|
36
36
|
"export": "MobileCapacitorClientProvider"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"entrypoint": "src/client/providers/MobileCapacitorClientProvider.js",
|
|
40
|
+
"export": "MobileCapacitorRuntimeProvider"
|
|
37
41
|
}
|
|
38
42
|
]
|
|
39
43
|
}
|
|
40
44
|
},
|
|
41
|
-
"lifecycle": {
|
|
42
|
-
"install": {
|
|
43
|
-
"prepare": {
|
|
44
|
-
"entrypoint": "src/server/buildTemplateContext.js",
|
|
45
|
-
"export": "prepareInstallHook"
|
|
46
|
-
},
|
|
47
|
-
"finalize": {
|
|
48
|
-
"entrypoint": "src/server/buildTemplateContext.js",
|
|
49
|
-
"export": "finalizeInstallHook",
|
|
50
|
-
"managesNpmInstall": true
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
},
|
|
54
45
|
"metadata": {
|
|
55
46
|
"apiSummary": {
|
|
56
47
|
"surfaces": [
|
|
@@ -59,67 +50,11 @@
|
|
|
59
50
|
"summary": "Exports the Capacitor client provider, launch adapter helpers, and mobile-shell runtime."
|
|
60
51
|
}
|
|
61
52
|
],
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"client"
|
|
65
|
-
|
|
66
|
-
"mobile.capacitor.adapter.client"
|
|
67
|
-
]
|
|
68
|
-
}
|
|
53
|
+
"clientCapabilities": [
|
|
54
|
+
"client.mobile",
|
|
55
|
+
"client.mobile-runtime"
|
|
56
|
+
]
|
|
69
57
|
}
|
|
70
|
-
},
|
|
71
|
-
"mutations": {
|
|
72
|
-
"dependencies": {
|
|
73
|
-
"runtime": {
|
|
74
|
-
"@capacitor/android": "^7.4.3",
|
|
75
|
-
"@capacitor/app": "^7.1.0",
|
|
76
|
-
"@capacitor/browser": "^7.0.1",
|
|
77
|
-
"@capacitor/core": "^7.4.3",
|
|
78
|
-
"@jskit-ai/kernel": "0.1.159",
|
|
79
|
-
"@jskit-ai/shell-web": "0.1.163"
|
|
80
|
-
},
|
|
81
|
-
"dev": {
|
|
82
|
-
"@capacitor/cli": "^7.4.3"
|
|
83
|
-
}
|
|
84
|
-
},
|
|
85
|
-
"packageJson": {
|
|
86
|
-
"scripts": {
|
|
87
|
-
"mobile:dev:android": "jskit mobile android dev",
|
|
88
|
-
"mobile:devices:android": "jskit mobile android devices",
|
|
89
|
-
"mobile:sync:android": "jskit mobile android sync",
|
|
90
|
-
"mobile:tunnel:android": "jskit mobile android tunnel",
|
|
91
|
-
"mobile:restart:android": "jskit mobile android restart",
|
|
92
|
-
"mobile:run:android": "jskit mobile android run",
|
|
93
|
-
"mobile:build:web": "npm run build",
|
|
94
|
-
"mobile:build:android": "jskit mobile android build"
|
|
95
|
-
}
|
|
96
|
-
},
|
|
97
|
-
"procfile": {},
|
|
98
|
-
"text": [],
|
|
99
|
-
"files": [
|
|
100
|
-
{
|
|
101
|
-
"from": "templates/capacitor.config.json",
|
|
102
|
-
"to": "capacitor.config.json",
|
|
103
|
-
"reason": "Install Capacitor shell configuration rendered from config.mobile.",
|
|
104
|
-
"category": "mobile-capacitor",
|
|
105
|
-
"id": "mobile-capacitor-config",
|
|
106
|
-
"templateContext": {
|
|
107
|
-
"entrypoint": "src/server/buildTemplateContext.js",
|
|
108
|
-
"export": "buildTemplateContext"
|
|
109
|
-
}
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
"from": "templates/mobile-capacitor.md",
|
|
113
|
-
"to": ".jskit/mobile-capacitor.md",
|
|
114
|
-
"reason": "Install managed mobile-shell notes for the Capacitor Android integration.",
|
|
115
|
-
"category": "mobile-capacitor",
|
|
116
|
-
"id": "mobile-capacitor-notes",
|
|
117
|
-
"templateContext": {
|
|
118
|
-
"entrypoint": "src/server/buildTemplateContext.js",
|
|
119
|
-
"export": "buildTemplateContext"
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
]
|
|
123
58
|
}
|
|
124
59
|
}
|
|
125
60
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: mobile/android-application
|
|
3
|
+
title: Capacitor Android application
|
|
4
|
+
summary: Wrap a JSKIT web application in a Capacitor Android shell using native Capacitor and Android commands.
|
|
5
|
+
keywords: android, capacitor, device, mobile, native, shell, webview
|
|
6
|
+
requires: @jskit-ai/mobile-capacitor, @capacitor/android, @capacitor/app, @capacitor/cli
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Capacitor Android application
|
|
10
|
+
|
|
11
|
+
## Use when
|
|
12
|
+
|
|
13
|
+
Use this pattern when the same JSKIT application should run inside a Capacitor
|
|
14
|
+
Android shell and the product accepts a WebView-based native application.
|
|
15
|
+
|
|
16
|
+
## Do not use when
|
|
17
|
+
|
|
18
|
+
Do not use this pattern for a browser-only app, for a native UI product, or when
|
|
19
|
+
the target platform is not supported by the selected Capacitor packages.
|
|
20
|
+
|
|
21
|
+
## Product decisions
|
|
22
|
+
|
|
23
|
+
Choose the stable application id, display name, built web directory, callback
|
|
24
|
+
URLs, development server policy, and native permissions before creating the
|
|
25
|
+
Android project.
|
|
26
|
+
|
|
27
|
+
## Framework APIs
|
|
28
|
+
|
|
29
|
+
Install the declared npm packages normally. Adapt `example/capacitor.config.json`
|
|
30
|
+
into valid JSON, then use Capacitor directly:
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
npx cap add android
|
|
34
|
+
npm run build
|
|
35
|
+
npx cap sync android
|
|
36
|
+
npx cap run android
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Use `adb devices`, `adb reverse`, Android Studio, and Gradle directly for device
|
|
40
|
+
selection, tunnelling, diagnostics, and release builds. JSKIT does not wrap
|
|
41
|
+
those tools in another command language.
|
|
42
|
+
|
|
43
|
+
## Example files
|
|
44
|
+
|
|
45
|
+
`example/capacitor.config.json` shows the small Capacitor configuration shape.
|
|
46
|
+
Replace its obvious values directly; it is not an interpolation template.
|
|
47
|
+
|
|
48
|
+
## Variation points
|
|
49
|
+
|
|
50
|
+
Change application id/name, build directory, development server, callback URLs,
|
|
51
|
+
plugins, native permissions, and platform-specific release configuration.
|
|
52
|
+
|
|
53
|
+
## Invariants
|
|
54
|
+
|
|
55
|
+
- `webDir` is the actual output of the application's production build.
|
|
56
|
+
- Development origins are explicit and never leak into release configuration.
|
|
57
|
+
- Browser navigation that leaves the app uses the package's public mobile
|
|
58
|
+
routing capability.
|
|
59
|
+
- Native projects and signing configuration are ordinary app-owned source.
|
|
60
|
+
|
|
61
|
+
## Verification
|
|
62
|
+
|
|
63
|
+
Build the web application, sync Android, launch on an explicit device, test
|
|
64
|
+
callback routing and external links, then build the native release variant.
|
|
65
|
+
|
|
66
|
+
## Avoid
|
|
67
|
+
|
|
68
|
+
- hidden device selection
|
|
69
|
+
- checking secrets or signing keys into source
|
|
70
|
+
- a JSKIT-specific wrapper around Capacitor, adb, or Gradle
|
|
71
|
+
- generated-operation receipts
|
package/src/client/index.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
export {
|
|
2
|
+
MobileCapacitorClientProvider,
|
|
3
|
+
MobileCapacitorRuntimeProvider
|
|
4
|
+
} from "./providers/MobileCapacitorClientProvider.js";
|
|
4
5
|
export { createMobileCapacitorRuntime } from "./runtime/mobileCapacitorRuntime.js";
|
|
5
6
|
export {
|
|
6
7
|
createGlobalCapacitorAppAdapter,
|
|
7
8
|
createNoopCapacitorAppAdapter,
|
|
8
9
|
resolveCapacitorAppPlugin
|
|
9
10
|
} from "./runtime/globalCapacitorAppAdapter.js";
|
|
10
|
-
|
|
11
|
-
const clientProviders = Object.freeze([MobileCapacitorClientProvider]);
|
|
12
|
-
|
|
13
|
-
export { clientProviders };
|
|
@@ -1,17 +1,14 @@
|
|
|
1
|
+
import { defineProvider } from "@jskit-ai/kernel/shared/capabilities";
|
|
1
2
|
import { getClientAppConfig, resolveMobileConfig } from "@jskit-ai/kernel/client";
|
|
2
3
|
import { createGlobalCapacitorAppAdapter } from "../runtime/globalCapacitorAppAdapter.js";
|
|
3
4
|
import { createCapacitorAwareFetch } from "../runtime/apiRequestClient.js";
|
|
4
5
|
import { createMobileCapacitorRuntime } from "../runtime/mobileCapacitorRuntime.js";
|
|
5
6
|
import { createCapacitorAwareOAuthLaunchClient } from "../runtime/oauthLaunchClient.js";
|
|
6
7
|
|
|
7
|
-
const AUTH_OAUTH_LAUNCH_CLIENT_TOKEN = "auth.oauth-launch.client";
|
|
8
8
|
const GLOBAL_FETCH_RESTORE_KEY = Symbol.for("jskit.mobile.capacitor.restoreFetch");
|
|
9
9
|
|
|
10
10
|
function installCapacitorAwareGlobalFetch({ adapter = null, apiBaseUrl = "", globalObject = globalThis } = {}) {
|
|
11
|
-
if (!globalObject || typeof globalObject !== "object") {
|
|
12
|
-
return null;
|
|
13
|
-
}
|
|
14
|
-
if (adapter?.available !== true) {
|
|
11
|
+
if (!globalObject || typeof globalObject !== "object" || adapter?.available !== true) {
|
|
15
12
|
return null;
|
|
16
13
|
}
|
|
17
14
|
if (typeof globalObject.fetch !== "function") {
|
|
@@ -41,104 +38,70 @@ function installCapacitorAwareGlobalFetch({ adapter = null, apiBaseUrl = "", glo
|
|
|
41
38
|
return restore;
|
|
42
39
|
}
|
|
43
40
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
});
|
|
41
|
+
const MobileCapacitorClientProvider = defineProvider({
|
|
42
|
+
id: "mobile.capacitor.client",
|
|
43
|
+
provides: {
|
|
44
|
+
mobile: "client.mobile"
|
|
45
|
+
},
|
|
46
|
+
setup() {
|
|
47
|
+
const adapter = createGlobalCapacitorAppAdapter();
|
|
48
|
+
const config = resolveMobileConfig(getClientAppConfig());
|
|
49
|
+
const restoreFetch = installCapacitorAwareGlobalFetch({
|
|
50
|
+
adapter,
|
|
51
|
+
apiBaseUrl: config.apiBaseUrl
|
|
99
52
|
});
|
|
53
|
+
return {
|
|
54
|
+
mobile: Object.freeze({
|
|
55
|
+
adapter,
|
|
56
|
+
config,
|
|
57
|
+
oauthLaunch: createCapacitorAwareOAuthLaunchClient({
|
|
58
|
+
adapter,
|
|
59
|
+
apiBaseUrl: config.apiBaseUrl
|
|
60
|
+
}),
|
|
61
|
+
restoreFetch
|
|
62
|
+
})
|
|
63
|
+
};
|
|
64
|
+
},
|
|
65
|
+
shutdown(_dependencies, { outputs }) {
|
|
66
|
+
outputs.mobile.restoreFetch?.();
|
|
100
67
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const MobileCapacitorRuntimeProvider = defineProvider({
|
|
71
|
+
id: "mobile.capacitor.runtime.client",
|
|
72
|
+
requires: {
|
|
73
|
+
mobile: "client.mobile",
|
|
74
|
+
router: "client.router"
|
|
75
|
+
},
|
|
76
|
+
optional: {
|
|
77
|
+
auth: "client.auth",
|
|
78
|
+
shell: "client.shell"
|
|
79
|
+
},
|
|
80
|
+
provides: {
|
|
81
|
+
mobileRuntime: "client.mobile-runtime"
|
|
82
|
+
},
|
|
83
|
+
setup({ auth, mobile, router, shell }) {
|
|
84
|
+
return {
|
|
85
|
+
mobileRuntime: createMobileCapacitorRuntime({
|
|
86
|
+
router,
|
|
87
|
+
mobileConfig: getClientAppConfig().mobile || {},
|
|
88
|
+
adapter: mobile.adapter,
|
|
89
|
+
placementRuntime: shell?.placement || null,
|
|
90
|
+
authCallbackCompleter: auth?.mobileCallback || null,
|
|
91
|
+
authGuardRuntime: auth?.guard || null
|
|
92
|
+
})
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
async boot(_dependencies, { outputs }) {
|
|
96
|
+
await outputs.mobileRuntime.initialize();
|
|
97
|
+
},
|
|
98
|
+
shutdown(_dependencies, { outputs }) {
|
|
99
|
+
outputs.mobileRuntime.dispose?.();
|
|
141
100
|
}
|
|
142
|
-
}
|
|
101
|
+
});
|
|
143
102
|
|
|
144
|
-
export {
|
|
103
|
+
export {
|
|
104
|
+
MobileCapacitorClientProvider,
|
|
105
|
+
MobileCapacitorRuntimeProvider,
|
|
106
|
+
installCapacitorAwareGlobalFetch
|
|
107
|
+
};
|
package/test/provider.test.js
CHANGED
|
@@ -1,88 +1,44 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
|
-
import {
|
|
3
|
+
import { createCapabilityRuntime, defineProvider } from "@jskit-ai/kernel/shared/capabilities";
|
|
4
|
+
import {
|
|
5
|
+
MobileCapacitorClientProvider,
|
|
6
|
+
MobileCapacitorRuntimeProvider
|
|
7
|
+
} from "../src/client/providers/MobileCapacitorClientProvider.js";
|
|
4
8
|
|
|
5
|
-
function
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
} = {}) {
|
|
11
|
-
const singletons = new Map();
|
|
12
|
-
const singletonInstances = new Map();
|
|
13
|
-
const runtime = {
|
|
14
|
-
initializeCalls: 0,
|
|
15
|
-
disposeCalls: 0
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const app = {
|
|
19
|
-
singleton(token, factory) {
|
|
20
|
-
singletons.set(token, factory);
|
|
21
|
-
},
|
|
22
|
-
has(token) {
|
|
23
|
-
if (token === "jskit.client.router") {
|
|
24
|
-
return true;
|
|
25
|
-
}
|
|
26
|
-
if (token === "runtime.web-placement.client") {
|
|
27
|
-
return Boolean(placementRuntime);
|
|
28
|
-
}
|
|
29
|
-
if (token === "auth.mobile-callback.client") {
|
|
30
|
-
return Boolean(authCallbackCompleter);
|
|
31
|
-
}
|
|
32
|
-
if (token === "runtime.auth-guard.client") {
|
|
33
|
-
return Boolean(authGuardRuntime);
|
|
34
|
-
}
|
|
35
|
-
if (token === "auth.oauth-launch.client") {
|
|
36
|
-
return singletons.has(token) || singletonInstances.has(token);
|
|
9
|
+
function createRouter() {
|
|
10
|
+
return {
|
|
11
|
+
currentRoute: {
|
|
12
|
+
value: {
|
|
13
|
+
fullPath: "/home"
|
|
37
14
|
}
|
|
38
|
-
return singletons.has(token) || singletonInstances.has(token);
|
|
39
15
|
},
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return {
|
|
43
|
-
currentRoute: {
|
|
44
|
-
value: {
|
|
45
|
-
fullPath: "/home"
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
async replace() {}
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
if (token === "runtime.web-placement.client") {
|
|
52
|
-
return placementRuntime;
|
|
53
|
-
}
|
|
54
|
-
if (token === "auth.mobile-callback.client") {
|
|
55
|
-
return authCallbackCompleter;
|
|
56
|
-
}
|
|
57
|
-
if (token === "runtime.auth-guard.client") {
|
|
58
|
-
return authGuardRuntime;
|
|
59
|
-
}
|
|
60
|
-
if (singletonInstances.has(token)) {
|
|
61
|
-
return singletonInstances.get(token);
|
|
62
|
-
}
|
|
63
|
-
const factory = singletons.get(token);
|
|
64
|
-
if (!factory) {
|
|
65
|
-
throw new Error(`Unknown token ${String(token)}`);
|
|
66
|
-
}
|
|
67
|
-
const instance = factory(this);
|
|
68
|
-
singletonInstances.set(token, instance);
|
|
69
|
-
return instance;
|
|
70
|
-
}
|
|
16
|
+
async replace() {},
|
|
17
|
+
back() {}
|
|
71
18
|
};
|
|
19
|
+
}
|
|
72
20
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
21
|
+
function createMobileProbe(onCapabilities) {
|
|
22
|
+
return defineProvider({
|
|
23
|
+
id: "test.mobile.capacitor.probe",
|
|
24
|
+
requires: {
|
|
25
|
+
mobile: "client.mobile",
|
|
26
|
+
mobileRuntime: "client.mobile-runtime"
|
|
27
|
+
},
|
|
28
|
+
setup(capabilities) {
|
|
29
|
+
onCapabilities(capabilities);
|
|
30
|
+
return {};
|
|
31
|
+
}
|
|
32
|
+
});
|
|
83
33
|
}
|
|
84
34
|
|
|
85
|
-
test("
|
|
35
|
+
test("mobile providers publish, boot, and dispose their declared capabilities", async () => {
|
|
36
|
+
const originalCapacitor = globalThis.Capacitor;
|
|
37
|
+
const originalConfig = globalThis.__JSKIT_CLIENT_APP_CONFIG__;
|
|
38
|
+
const listenerEvents = [];
|
|
39
|
+
const removedListeners = [];
|
|
40
|
+
let capabilities = null;
|
|
41
|
+
|
|
86
42
|
globalThis.__JSKIT_CLIENT_APP_CONFIG__ = {
|
|
87
43
|
mobile: {
|
|
88
44
|
enabled: true,
|
|
@@ -91,52 +47,67 @@ test("MobileCapacitorClientProvider registers and boots the mobile runtime", asy
|
|
|
91
47
|
}
|
|
92
48
|
}
|
|
93
49
|
};
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
50
|
+
globalThis.Capacitor = {
|
|
51
|
+
Plugins: {
|
|
52
|
+
App: {
|
|
53
|
+
async getLaunchUrl() {
|
|
54
|
+
return { url: "" };
|
|
55
|
+
},
|
|
56
|
+
async addListener(eventName) {
|
|
57
|
+
listenerEvents.push(eventName);
|
|
58
|
+
return {
|
|
59
|
+
remove() {
|
|
60
|
+
removedListeners.push(eventName);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
103
64
|
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const provider = new MobileCapacitorClientProvider();
|
|
107
|
-
|
|
108
|
-
provider.register(app);
|
|
109
|
-
|
|
110
|
-
assert.equal(app.has("mobile.capacitor.adapter.client"), true);
|
|
111
|
-
assert.equal(app.has("mobile.capacitor.client.runtime"), true);
|
|
112
|
-
assert.equal(app.has("auth.oauth-launch.client"), true);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
113
67
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
68
|
+
const runtime = createCapabilityRuntime({
|
|
69
|
+
inputs: {
|
|
70
|
+
"client.router": createRouter()
|
|
71
|
+
},
|
|
72
|
+
providers: [
|
|
73
|
+
MobileCapacitorClientProvider,
|
|
74
|
+
MobileCapacitorRuntimeProvider,
|
|
75
|
+
createMobileProbe((value) => {
|
|
76
|
+
capabilities = value;
|
|
77
|
+
})
|
|
78
|
+
]
|
|
79
|
+
});
|
|
117
80
|
|
|
118
|
-
|
|
119
|
-
|
|
81
|
+
try {
|
|
82
|
+
await runtime.start();
|
|
83
|
+
assert.equal(capabilities.mobile.adapter.available, true);
|
|
84
|
+
assert.equal(typeof capabilities.mobile.oauthLaunch.open, "function");
|
|
85
|
+
assert.deepEqual(capabilities.mobileRuntime.getState(), {
|
|
86
|
+
initialized: true,
|
|
87
|
+
available: true,
|
|
88
|
+
enabled: true,
|
|
89
|
+
lastAppliedPath: ""
|
|
90
|
+
});
|
|
91
|
+
assert.deepEqual(listenerEvents.sort(), ["appUrlOpen", "backButton"]);
|
|
120
92
|
|
|
121
|
-
|
|
122
|
-
assert.equal(
|
|
93
|
+
await runtime.shutdown();
|
|
94
|
+
assert.equal(capabilities.mobileRuntime.getState().initialized, false);
|
|
95
|
+
await Promise.resolve();
|
|
96
|
+
assert.deepEqual(removedListeners.sort(), ["appUrlOpen", "backButton"]);
|
|
123
97
|
} finally {
|
|
124
|
-
|
|
98
|
+
globalThis.Capacitor = originalCapacitor;
|
|
99
|
+
globalThis.__JSKIT_CLIENT_APP_CONFIG__ = originalConfig;
|
|
125
100
|
}
|
|
126
101
|
});
|
|
127
102
|
|
|
128
|
-
test("
|
|
103
|
+
test("mobile provider installs and restores the Capacitor-aware fetch wrapper", async () => {
|
|
104
|
+
const originalCapacitor = globalThis.Capacitor;
|
|
129
105
|
const originalConfig = globalThis.__JSKIT_CLIENT_APP_CONFIG__;
|
|
130
106
|
const originalFetch = globalThis.fetch;
|
|
131
107
|
const fetchCalls = [];
|
|
132
108
|
const stubFetch = async (url, options) => {
|
|
133
|
-
fetchCalls.push({
|
|
134
|
-
|
|
135
|
-
options
|
|
136
|
-
});
|
|
137
|
-
return {
|
|
138
|
-
ok: true
|
|
139
|
-
};
|
|
109
|
+
fetchCalls.push({ url, options });
|
|
110
|
+
return { ok: true };
|
|
140
111
|
};
|
|
141
112
|
|
|
142
113
|
globalThis.__JSKIT_CLIENT_APP_CONFIG__ = {
|
|
@@ -148,39 +119,40 @@ test("MobileCapacitorClientProvider installs and restores the Capacitor fetch wr
|
|
|
148
119
|
}
|
|
149
120
|
}
|
|
150
121
|
};
|
|
151
|
-
globalThis.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
available: true,
|
|
157
|
-
async getInitialLaunchUrl() {
|
|
158
|
-
return "";
|
|
122
|
+
globalThis.Capacitor = {
|
|
123
|
+
Plugins: {
|
|
124
|
+
App: {
|
|
125
|
+
async getLaunchUrl() {
|
|
126
|
+
return { url: "" };
|
|
159
127
|
},
|
|
160
|
-
|
|
161
|
-
return ()
|
|
128
|
+
async addListener() {
|
|
129
|
+
return { remove() {} };
|
|
162
130
|
}
|
|
163
131
|
}
|
|
164
|
-
}
|
|
165
|
-
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
globalThis.fetch = stubFetch;
|
|
166
135
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
|
|
136
|
+
const runtime = createCapabilityRuntime({
|
|
137
|
+
inputs: {
|
|
138
|
+
"client.router": createRouter()
|
|
139
|
+
},
|
|
140
|
+
providers: [MobileCapacitorClientProvider, MobileCapacitorRuntimeProvider]
|
|
141
|
+
});
|
|
173
142
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
143
|
+
try {
|
|
144
|
+
await runtime.start();
|
|
145
|
+
assert.notEqual(globalThis.fetch, stubFetch);
|
|
146
|
+
await globalThis.fetch("/api/session", { method: "GET" });
|
|
147
|
+
await runtime.shutdown();
|
|
148
|
+
|
|
149
|
+
assert.deepEqual(fetchCalls, [{
|
|
150
|
+
url: "http://127.0.0.1:3000/api/session",
|
|
151
|
+
options: { method: "GET" }
|
|
152
|
+
}]);
|
|
182
153
|
assert.equal(globalThis.fetch, stubFetch);
|
|
183
154
|
} finally {
|
|
155
|
+
globalThis.Capacitor = originalCapacitor;
|
|
184
156
|
globalThis.__JSKIT_CLIENT_APP_CONFIG__ = originalConfig;
|
|
185
157
|
globalThis.fetch = originalFetch;
|
|
186
158
|
}
|
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { readdir, rm } from "node:fs/promises";
|
|
3
|
-
import { loadAppConfigFromAppRoot, resolveMobileConfig } from "@jskit-ai/kernel/server/support";
|
|
4
|
-
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
5
|
-
|
|
6
|
-
function requireNonEmptyText(value, label = "value") {
|
|
7
|
-
const normalized = normalizeText(value);
|
|
8
|
-
if (!normalized) {
|
|
9
|
-
throw new Error(`${label} is required in config.mobile before installing Capacitor support.`);
|
|
10
|
-
}
|
|
11
|
-
return normalized;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function requireUrl(value, label = "value", { allowHttp = true, allowHttps = true } = {}) {
|
|
15
|
-
const normalized = requireNonEmptyText(value, label);
|
|
16
|
-
let parsed = null;
|
|
17
|
-
try {
|
|
18
|
-
parsed = new URL(normalized);
|
|
19
|
-
} catch {
|
|
20
|
-
throw new Error(`${label} must be a valid absolute URL in config.mobile.`);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const protocol = String(parsed.protocol || "").toLowerCase();
|
|
24
|
-
if ((protocol === "http:" && allowHttp !== true) || (protocol === "https:" && allowHttps !== true)) {
|
|
25
|
-
throw new Error(`${label} must use an allowed protocol in config.mobile.`);
|
|
26
|
-
}
|
|
27
|
-
if (protocol !== "http:" && protocol !== "https:") {
|
|
28
|
-
throw new Error(`${label} must use http or https in config.mobile.`);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return parsed.toString();
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function buildCapacitorServerBlock(mobileConfig = {}) {
|
|
35
|
-
if (mobileConfig.assetMode !== "dev_server") {
|
|
36
|
-
return "";
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const devServerUrl = requireUrl(mobileConfig.devServerUrl, "config.mobile.devServerUrl");
|
|
40
|
-
const cleartext = String(new URL(devServerUrl).protocol || "").toLowerCase() === "http:";
|
|
41
|
-
|
|
42
|
-
return [
|
|
43
|
-
",",
|
|
44
|
-
' "server": {',
|
|
45
|
-
` "url": ${JSON.stringify(devServerUrl)},`,
|
|
46
|
-
` "cleartext": ${cleartext}`,
|
|
47
|
-
" }"
|
|
48
|
-
].join("\n");
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function buildAppLinkDomainsValue(appLinkDomains = []) {
|
|
52
|
-
const domains = Array.isArray(appLinkDomains) ? appLinkDomains : [];
|
|
53
|
-
if (domains.length < 1) {
|
|
54
|
-
return "(none)";
|
|
55
|
-
}
|
|
56
|
-
return domains.join(", ");
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async function directoryContainsAnyFiles(directoryPath = "") {
|
|
60
|
-
const entries = await readdir(directoryPath, { withFileTypes: true });
|
|
61
|
-
for (const entry of entries) {
|
|
62
|
-
const absoluteChildPath = path.join(directoryPath, entry.name);
|
|
63
|
-
if (entry.isFile() || entry.isSymbolicLink()) {
|
|
64
|
-
return true;
|
|
65
|
-
}
|
|
66
|
-
if (entry.isDirectory() && await directoryContainsAnyFiles(absoluteChildPath)) {
|
|
67
|
-
return true;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
async function buildTemplateContext({ appRoot } = {}) {
|
|
74
|
-
const mergedConfig = await loadAppConfigFromAppRoot({
|
|
75
|
-
appRoot: path.resolve(String(appRoot || ""))
|
|
76
|
-
});
|
|
77
|
-
const mobileConfig = resolveMobileConfig({
|
|
78
|
-
mobile: mergedConfig.mobile
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
if (mobileConfig.enabled !== true) {
|
|
82
|
-
throw new Error("config.mobile.enabled must be true before installing Capacitor support.");
|
|
83
|
-
}
|
|
84
|
-
if (mobileConfig.strategy !== "capacitor") {
|
|
85
|
-
throw new Error('config.mobile.strategy must be "capacitor" before installing Capacitor support.');
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const appId = requireNonEmptyText(mobileConfig.appId, "config.mobile.appId");
|
|
89
|
-
const appName = requireNonEmptyText(mobileConfig.appName, "config.mobile.appName");
|
|
90
|
-
const apiBaseUrl = requireUrl(mobileConfig.apiBaseUrl, "config.mobile.apiBaseUrl");
|
|
91
|
-
const callbackPath = requireNonEmptyText(mobileConfig.auth.callbackPath, "config.mobile.auth.callbackPath");
|
|
92
|
-
const customScheme = requireNonEmptyText(mobileConfig.auth.customScheme, "config.mobile.auth.customScheme");
|
|
93
|
-
const androidPackageName = requireNonEmptyText(
|
|
94
|
-
mobileConfig.android.packageName,
|
|
95
|
-
"config.mobile.android.packageName"
|
|
96
|
-
);
|
|
97
|
-
const androidVersionName = requireNonEmptyText(
|
|
98
|
-
mobileConfig.android.versionName,
|
|
99
|
-
"config.mobile.android.versionName"
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
return {
|
|
103
|
-
"__JSKIT_MOBILE_CAPACITOR_APP_ID__": appId,
|
|
104
|
-
"__JSKIT_MOBILE_CAPACITOR_APP_NAME__": appName,
|
|
105
|
-
"__JSKIT_MOBILE_CAPACITOR_WEB_DIR__": "dist",
|
|
106
|
-
"__JSKIT_MOBILE_CAPACITOR_SERVER_BLOCK__": buildCapacitorServerBlock(mobileConfig),
|
|
107
|
-
"__JSKIT_MOBILE_CAPACITOR_ASSET_MODE__": mobileConfig.assetMode,
|
|
108
|
-
"__JSKIT_MOBILE_CAPACITOR_DEV_SERVER_URL__": mobileConfig.devServerUrl || "(unused)",
|
|
109
|
-
"__JSKIT_MOBILE_CAPACITOR_API_BASE_URL__": apiBaseUrl,
|
|
110
|
-
"__JSKIT_MOBILE_CAPACITOR_CALLBACK_PATH__": callbackPath,
|
|
111
|
-
"__JSKIT_MOBILE_CAPACITOR_CUSTOM_SCHEME__": customScheme,
|
|
112
|
-
"__JSKIT_MOBILE_CAPACITOR_APP_LINK_DOMAINS__": buildAppLinkDomainsValue(mobileConfig.auth.appLinkDomains),
|
|
113
|
-
"__JSKIT_MOBILE_CAPACITOR_ANDROID_PACKAGE_NAME__": androidPackageName,
|
|
114
|
-
"__JSKIT_MOBILE_CAPACITOR_ANDROID_MIN_SDK__": String(mobileConfig.android.minSdk),
|
|
115
|
-
"__JSKIT_MOBILE_CAPACITOR_ANDROID_TARGET_SDK__": String(mobileConfig.android.targetSdk),
|
|
116
|
-
"__JSKIT_MOBILE_CAPACITOR_ANDROID_VERSION_CODE__": String(mobileConfig.android.versionCode),
|
|
117
|
-
"__JSKIT_MOBILE_CAPACITOR_ANDROID_VERSION_NAME__": androidVersionName
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
async function prepareInstallHook({
|
|
122
|
-
appRoot,
|
|
123
|
-
appPackageJson = {},
|
|
124
|
-
io,
|
|
125
|
-
dryRun = false,
|
|
126
|
-
helpers = {}
|
|
127
|
-
} = {}) {
|
|
128
|
-
const ensureManagedMobileConfig = helpers?.ensureManagedMobileConfig;
|
|
129
|
-
if (typeof ensureManagedMobileConfig !== "function") {
|
|
130
|
-
throw new Error("install hook helpers.ensureManagedMobileConfig is required.");
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const addedConfigStub = await ensureManagedMobileConfig({
|
|
134
|
-
dryRun
|
|
135
|
-
});
|
|
136
|
-
if (dryRun === true && addedConfigStub) {
|
|
137
|
-
return {
|
|
138
|
-
stopInstall: true,
|
|
139
|
-
touchedFiles: ["config/public.js"],
|
|
140
|
-
stopMessage:
|
|
141
|
-
"[dry-run] mobile package install preview stops after the config.mobile stub because rendered Capacitor files depend on those values."
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return {};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
async function finalizeInstallHook({
|
|
149
|
-
appRoot,
|
|
150
|
-
io,
|
|
151
|
-
dryRun = false,
|
|
152
|
-
skipManagedFinalize = false,
|
|
153
|
-
helpers = {}
|
|
154
|
-
} = {}) {
|
|
155
|
-
if (skipManagedFinalize === true) {
|
|
156
|
-
return {};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const installAppDependencies = helpers?.installAppDependencies;
|
|
160
|
-
const runProjectBinary = helpers?.runProjectBinary;
|
|
161
|
-
const helperFileExists = helpers?.fileExists;
|
|
162
|
-
const collectShellInstallIssues = helpers?.collectCapacitorShellInstallIssues;
|
|
163
|
-
const ensureDeepLinks = helpers?.ensureAndroidManifestDeepLinks;
|
|
164
|
-
const ensureNativeShellIdentity = helpers?.ensureAndroidNativeShellIdentity;
|
|
165
|
-
if (typeof installAppDependencies !== "function") {
|
|
166
|
-
throw new Error("install hook helpers.installAppDependencies is required.");
|
|
167
|
-
}
|
|
168
|
-
if (typeof runProjectBinary !== "function") {
|
|
169
|
-
throw new Error("install hook helpers.runProjectBinary is required.");
|
|
170
|
-
}
|
|
171
|
-
if (typeof helperFileExists !== "function") {
|
|
172
|
-
throw new Error("install hook helpers.fileExists is required.");
|
|
173
|
-
}
|
|
174
|
-
if (typeof collectShellInstallIssues !== "function") {
|
|
175
|
-
throw new Error("install hook helpers.collectCapacitorShellInstallIssues is required.");
|
|
176
|
-
}
|
|
177
|
-
if (typeof ensureDeepLinks !== "function") {
|
|
178
|
-
throw new Error("install hook helpers.ensureAndroidManifestDeepLinks is required.");
|
|
179
|
-
}
|
|
180
|
-
if (typeof ensureNativeShellIdentity !== "function") {
|
|
181
|
-
throw new Error("install hook helpers.ensureAndroidNativeShellIdentity is required.");
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
await installAppDependencies({
|
|
185
|
-
dryRun
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
const androidDirectoryPath = path.join(appRoot, "android");
|
|
189
|
-
const shellInstallIssues = await collectShellInstallIssues();
|
|
190
|
-
if (shellInstallIssues.length < 1) {
|
|
191
|
-
io?.stdout?.write("[mobile] Android shell is already installed. Skipping cap add android.\n");
|
|
192
|
-
} else {
|
|
193
|
-
if (await helperFileExists(androidDirectoryPath)) {
|
|
194
|
-
io?.stdout?.write("[mobile] Android shell is partial or stale. Reprovisioning it with Capacitor CLI because these artifacts are missing:\n");
|
|
195
|
-
for (const issue of shellInstallIssues) {
|
|
196
|
-
io?.stdout?.write(`- ${issue}\n`);
|
|
197
|
-
}
|
|
198
|
-
const androidDirectoryHasFiles = await directoryContainsAnyFiles(androidDirectoryPath);
|
|
199
|
-
if (!androidDirectoryHasFiles) {
|
|
200
|
-
io?.stdout?.write("[mobile] android/ exists but contains no files. Removing the empty partial shell before reprovisioning.\n");
|
|
201
|
-
if (!dryRun) {
|
|
202
|
-
await rm(androidDirectoryPath, { recursive: true, force: true });
|
|
203
|
-
}
|
|
204
|
-
} else {
|
|
205
|
-
throw new Error(
|
|
206
|
-
"android/ exists but the Capacitor shell is incomplete. JSKIT will not delete a non-empty native tree automatically. Remove android/ and rerun the install."
|
|
207
|
-
);
|
|
208
|
-
}
|
|
209
|
-
} else {
|
|
210
|
-
io?.stdout?.write("[mobile] Android shell is not installed yet. Provisioning it with Capacitor CLI.\n");
|
|
211
|
-
}
|
|
212
|
-
await runProjectBinary("cap", ["add", "android"], {
|
|
213
|
-
explanation: "[mobile] Provisioning Android shell with Capacitor CLI:",
|
|
214
|
-
dryRun
|
|
215
|
-
});
|
|
216
|
-
if (!dryRun) {
|
|
217
|
-
io?.stdout?.write("[mobile] Added Android shell with Capacitor CLI.\n");
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
await ensureDeepLinks({
|
|
221
|
-
dryRun
|
|
222
|
-
});
|
|
223
|
-
await ensureNativeShellIdentity({
|
|
224
|
-
dryRun
|
|
225
|
-
});
|
|
226
|
-
return {
|
|
227
|
-
touchedFiles: ["android"]
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
export { buildTemplateContext, prepareInstallHook, finalizeInstallHook };
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
# Mobile Capacitor
|
|
2
|
-
|
|
3
|
-
This file is managed by `@jskit-ai/mobile-capacitor`.
|
|
4
|
-
|
|
5
|
-
Installed contract:
|
|
6
|
-
|
|
7
|
-
- strategy: `capacitor`
|
|
8
|
-
- asset mode: `__JSKIT_MOBILE_CAPACITOR_ASSET_MODE__`
|
|
9
|
-
- dev server url: `__JSKIT_MOBILE_CAPACITOR_DEV_SERVER_URL__`
|
|
10
|
-
- API base URL: `__JSKIT_MOBILE_CAPACITOR_API_BASE_URL__`
|
|
11
|
-
- auth callback path: `__JSKIT_MOBILE_CAPACITOR_CALLBACK_PATH__`
|
|
12
|
-
- custom scheme: `__JSKIT_MOBILE_CAPACITOR_CUSTOM_SCHEME__`
|
|
13
|
-
- app link domains: `__JSKIT_MOBILE_CAPACITOR_APP_LINK_DOMAINS__`
|
|
14
|
-
- Capacitor app id: `__JSKIT_MOBILE_CAPACITOR_APP_ID__`
|
|
15
|
-
- Capacitor app name: `__JSKIT_MOBILE_CAPACITOR_APP_NAME__`
|
|
16
|
-
- Android package name: `__JSKIT_MOBILE_CAPACITOR_ANDROID_PACKAGE_NAME__`
|
|
17
|
-
- Android min SDK: `__JSKIT_MOBILE_CAPACITOR_ANDROID_MIN_SDK__`
|
|
18
|
-
- Android target SDK: `__JSKIT_MOBILE_CAPACITOR_ANDROID_TARGET_SDK__`
|
|
19
|
-
- Android version code: `__JSKIT_MOBILE_CAPACITOR_ANDROID_VERSION_CODE__`
|
|
20
|
-
- Android version name: `__JSKIT_MOBILE_CAPACITOR_ANDROID_VERSION_NAME__`
|
|
21
|
-
|
|
22
|
-
Owned artifacts:
|
|
23
|
-
|
|
24
|
-
- `capacitor.config.json`
|
|
25
|
-
- `android/` after `jskit add package @jskit-ai/mobile-capacitor` runs `cap add android`
|
|
26
|
-
- `android/app/src/main/AndroidManifest.xml` managed deep-link intent filter for the custom scheme
|
|
27
|
-
|
|
28
|
-
Managed commands:
|
|
29
|
-
|
|
30
|
-
- `jskit add package @jskit-ai/mobile-capacitor`
|
|
31
|
-
- `jskit mobile android dev [--target <device-id>]`
|
|
32
|
-
- `jskit mobile android devices`
|
|
33
|
-
- `jskit mobile android sync`
|
|
34
|
-
- `jskit mobile android tunnel --target <device-id>`
|
|
35
|
-
- `jskit mobile android restart --target <device-id>`
|
|
36
|
-
- `jskit mobile android run [--target <device-id>]`
|
|
37
|
-
- `jskit mobile android build`
|
|
38
|
-
- `jskit mobile android doctor`
|
|
39
|
-
|
|
40
|
-
Current Stage 1 limits:
|
|
41
|
-
|
|
42
|
-
- Android only
|
|
43
|
-
- web assets stay the JSKIT web client
|
|
44
|
-
- OAuth start uses the external browser/custom tab only when the app is running inside the Capacitor shell
|
|
45
|
-
- auth/deep-link handling stays routed through normal JSKIT paths
|
|
46
|
-
- native app-link verification is still out of scope for Stage 1
|
|
File without changes
|