@craft-native/android 0.0.449 → 0.0.464
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 +19 -1
- package/dist/index.d.ts +1 -350
- package/dist/index.js +228 -31
- package/dist/index.js.map +7 -5
- package/dist/internal/android/src/index.d.ts +358 -0
- package/dist/internal/android/src/testing.d.ts +2 -0
- package/dist/internal/typescript/src/native-notifications.d.ts +17 -0
- package/dist/internal/typescript/src/native-routing.d.ts +26 -0
- package/package.json +1 -1
- package/templates/CraftNativeBackground.kt.template +65 -25
- package/templates/CraftNativeCapabilities.kt.template +3 -1
- package/templates/CraftNativeMessagingService.kt.template +8 -28
- package/templates/CraftNativeNotificationReceiver.kt.template +213 -15
- package/templates/CraftNativeServices.kt.template +6 -4
- package/templates/CraftNativeShell.kt.template +72 -21
- package/templates/MainActivityNative.kt.template +227 -12
- package/dist/testing.d.ts +0 -2
- /package/dist/{emulator.d.ts → internal/android/src/emulator.d.ts} +0 -0
- /package/dist/{native-map.d.ts → internal/android/src/native-map.d.ts} +0 -0
- /package/dist/{promise-runtime.d.ts → internal/android/src/promise-runtime.d.ts} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,4 +1,212 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
// ../typescript/src/native-notifications.ts
|
|
3
|
+
function nativeNotificationProblems(categories, nativeApp, renderer) {
|
|
4
|
+
if (categories === undefined)
|
|
5
|
+
return [];
|
|
6
|
+
if (!Array.isArray(categories))
|
|
7
|
+
return ["notificationCategories must be an array."];
|
|
8
|
+
const problems = [];
|
|
9
|
+
const categoryIds = new Set;
|
|
10
|
+
const object = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
11
|
+
const text = (value) => typeof value === "string" && value.trim().length > 0;
|
|
12
|
+
for (const [index, category] of categories.entries()) {
|
|
13
|
+
const field = `notificationCategories[${index}]`;
|
|
14
|
+
if (!object(category) || !text(category.id)) {
|
|
15
|
+
problems.push(`${field} needs a nonempty id.`);
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (categoryIds.has(category.id))
|
|
19
|
+
problems.push(`${field}.id must be unique.`);
|
|
20
|
+
categoryIds.add(category.id);
|
|
21
|
+
if (!Array.isArray(category.actions) || category.actions.length < 1 || category.actions.length > 3) {
|
|
22
|
+
problems.push(`${field}.actions must contain 1\u20133 actions for both platforms.`);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const actionIds = new Set;
|
|
26
|
+
for (const [position, action] of category.actions.entries()) {
|
|
27
|
+
const path = `${field}.actions[${position}]`;
|
|
28
|
+
if (!object(action) || !text(action.id) || !text(action.title)) {
|
|
29
|
+
problems.push(`${path} needs a nonempty id and title.`);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (action.id.startsWith("com.apple.") || actionIds.has(action.id))
|
|
33
|
+
problems.push(`${path}.id must be unique and must not use Apple's reserved prefix.`);
|
|
34
|
+
actionIds.add(action.id);
|
|
35
|
+
for (const flag of ["destructive", "foreground", "authenticationRequired"]) {
|
|
36
|
+
if (action[flag] !== undefined && typeof action[flag] !== "boolean")
|
|
37
|
+
problems.push(`${path}.${flag} must be a boolean.`);
|
|
38
|
+
}
|
|
39
|
+
if (!nativeApp?.trim() && (action.foreground !== true || renderer === "native"))
|
|
40
|
+
problems.push(`${path} requires nativeApp to receive native/background action events.`);
|
|
41
|
+
if (action.textInput !== undefined && (!object(action.textInput) || !text(action.textInput.buttonTitle) || action.textInput.placeholder !== undefined && typeof action.textInput.placeholder !== "string")) {
|
|
42
|
+
problems.push(`${path}.textInput needs a buttonTitle and an optional string placeholder.`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return problems;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ../typescript/src/native-routing.ts
|
|
50
|
+
function applyNativeParameterConstraints(params, rules) {
|
|
51
|
+
if (rules === undefined)
|
|
52
|
+
return true;
|
|
53
|
+
if (!rules || typeof rules !== "object" || Array.isArray(rules))
|
|
54
|
+
return false;
|
|
55
|
+
for (const [name, rule] of Object.entries(rules)) {
|
|
56
|
+
let value = params[name];
|
|
57
|
+
if (typeof value !== "string" || !rule || typeof rule !== "object" || Array.isArray(rule))
|
|
58
|
+
return false;
|
|
59
|
+
if (Object.keys(rule).some((key) => !["pattern", "exclude", "normalize"].includes(key)))
|
|
60
|
+
return false;
|
|
61
|
+
if (rule.normalize !== undefined && rule.normalize !== "lowercase")
|
|
62
|
+
return false;
|
|
63
|
+
if (rule.normalize === "lowercase")
|
|
64
|
+
value = value.toLowerCase();
|
|
65
|
+
if (rule.exclude !== undefined) {
|
|
66
|
+
if (!Array.isArray(rule.exclude) || rule.exclude.some((item) => typeof item !== "string"))
|
|
67
|
+
return false;
|
|
68
|
+
if (rule.exclude.some((item) => (rule.normalize === "lowercase" ? item.toLowerCase() : item) === value))
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
if (rule.pattern !== undefined) {
|
|
72
|
+
if (typeof rule.pattern !== "string")
|
|
73
|
+
return false;
|
|
74
|
+
try {
|
|
75
|
+
const matched = new RegExp(`^(?:${rule.pattern})$`).exec(value);
|
|
76
|
+
if (!matched || matched[0] !== value)
|
|
77
|
+
return false;
|
|
78
|
+
} catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
params[name] = value;
|
|
83
|
+
}
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
function nativeRouteConstraintProblems(screens, constraints) {
|
|
87
|
+
const problems = [];
|
|
88
|
+
if (constraints === undefined)
|
|
89
|
+
return problems;
|
|
90
|
+
if (!constraints || typeof constraints !== "object" || Array.isArray(constraints))
|
|
91
|
+
return ["nativeScreenConstraints must name route parameter constraints."];
|
|
92
|
+
for (const [route, rules] of Object.entries(constraints)) {
|
|
93
|
+
const prefix = `nativeScreenConstraints[${JSON.stringify(route)}]`;
|
|
94
|
+
if (!Object.prototype.hasOwnProperty.call(screens ?? {}, route))
|
|
95
|
+
problems.push(`${prefix} names no native screen route.`);
|
|
96
|
+
if (!rules || typeof rules !== "object" || Array.isArray(rules)) {
|
|
97
|
+
problems.push(`${prefix} must name parameter constraints.`);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const names = route.split("/").filter((part) => part.startsWith(":")).map((part) => part.slice(1));
|
|
101
|
+
for (const [name, rule] of Object.entries(rules)) {
|
|
102
|
+
const field = `${prefix}[${JSON.stringify(name)}]`;
|
|
103
|
+
if (!names.includes(name))
|
|
104
|
+
problems.push(`${field} names no path parameter.`);
|
|
105
|
+
if (!rule || typeof rule !== "object" || Array.isArray(rule)) {
|
|
106
|
+
problems.push(`${field} must be a parameter constraint.`);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (Object.keys(rule).some((key) => !["pattern", "exclude", "normalize"].includes(key)))
|
|
110
|
+
problems.push(`${field} has an unknown constraint field.`);
|
|
111
|
+
if (rule.exclude !== undefined && (!Array.isArray(rule.exclude) || rule.exclude.some((value) => typeof value !== "string")))
|
|
112
|
+
problems.push(`${field}.exclude must contain strings.`);
|
|
113
|
+
if (rule.normalize !== undefined && rule.normalize !== "lowercase")
|
|
114
|
+
problems.push(`${field}.normalize must be lowercase.`);
|
|
115
|
+
if (rule.pattern !== undefined) {
|
|
116
|
+
try {
|
|
117
|
+
if (typeof rule.pattern !== "string")
|
|
118
|
+
throw new Error("not a string");
|
|
119
|
+
new RegExp(`^(?:${rule.pattern})$`);
|
|
120
|
+
} catch {
|
|
121
|
+
problems.push(`${field}.pattern must be a valid regular expression.`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return problems;
|
|
127
|
+
}
|
|
128
|
+
function normalizeNativePath(raw) {
|
|
129
|
+
if (typeof raw !== "string")
|
|
130
|
+
return null;
|
|
131
|
+
let path = raw;
|
|
132
|
+
const cut = path.search(/[?#]/);
|
|
133
|
+
if (cut >= 0)
|
|
134
|
+
path = path.slice(0, cut);
|
|
135
|
+
if (!path.startsWith("/"))
|
|
136
|
+
return null;
|
|
137
|
+
path = path.replace(/\/{2,}/g, "/");
|
|
138
|
+
if (path.length > 1 && path.endsWith("/"))
|
|
139
|
+
path = path.slice(0, -1);
|
|
140
|
+
return path;
|
|
141
|
+
}
|
|
142
|
+
function routeSegments(path) {
|
|
143
|
+
return path === "/" ? [] : path.slice(1).split("/");
|
|
144
|
+
}
|
|
145
|
+
function matchNativePath(nativeScreens, raw, constraints) {
|
|
146
|
+
if (constraints !== undefined && (!constraints || typeof constraints !== "object" || Array.isArray(constraints)))
|
|
147
|
+
return null;
|
|
148
|
+
const path = normalizeNativePath(raw);
|
|
149
|
+
if (path === null)
|
|
150
|
+
return null;
|
|
151
|
+
const routes = Object.entries(nativeScreens ?? {}).map(([pattern, screen]) => ({ key: pattern, pattern: normalizeNativePath(pattern), screen })).filter((route) => route.pattern !== null && !!route.screen).map((route) => ({ ...route, segments: routeSegments(route.pattern) }));
|
|
152
|
+
const wild = (segments) => segments.at(-1) === "*" ? 1 : 0;
|
|
153
|
+
const fixed = (segments) => segments.filter((s) => s !== "*" && !s.startsWith(":")).length;
|
|
154
|
+
routes.sort((a, b) => wild(a.segments) - wild(b.segments) || fixed(b.segments) - fixed(a.segments) || b.segments.length - a.segments.length || (a.pattern < b.pattern ? -1 : a.pattern > b.pattern ? 1 : 0));
|
|
155
|
+
const decode = (value) => {
|
|
156
|
+
try {
|
|
157
|
+
return decodeURIComponent(value);
|
|
158
|
+
} catch {
|
|
159
|
+
return value;
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
const parts = routeSegments(path);
|
|
163
|
+
for (const route of routes) {
|
|
164
|
+
const params = {};
|
|
165
|
+
let matched = true;
|
|
166
|
+
for (let i = 0;i < route.segments.length; i++) {
|
|
167
|
+
const segment = route.segments[i];
|
|
168
|
+
if (segment === "*") {
|
|
169
|
+
params["*"] = parts.slice(i).join("/");
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
if (i >= parts.length) {
|
|
173
|
+
matched = false;
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
if (segment.startsWith(":")) {
|
|
177
|
+
if (!parts[i]) {
|
|
178
|
+
matched = false;
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
params[segment.slice(1)] = decode(parts[i]);
|
|
182
|
+
} else if (segment !== parts[i]) {
|
|
183
|
+
matched = false;
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (!matched)
|
|
188
|
+
continue;
|
|
189
|
+
if (route.segments.at(-1) !== "*" && route.segments.length !== parts.length)
|
|
190
|
+
continue;
|
|
191
|
+
if (!applyNativeParameterConstraints(params, constraints?.[route.key]))
|
|
192
|
+
continue;
|
|
193
|
+
const query = raw.indexOf("?");
|
|
194
|
+
if (query >= 0) {
|
|
195
|
+
for (const item of raw.slice(query + 1).split("#")[0].split("&")) {
|
|
196
|
+
if (!item)
|
|
197
|
+
continue;
|
|
198
|
+
const [rawKey = "", ...rest] = item.split("=");
|
|
199
|
+
const key = decode(rawKey);
|
|
200
|
+
if (!key || key in params)
|
|
201
|
+
continue;
|
|
202
|
+
params[key] = decode(rest.join("=").replace(/\+/g, " "));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return { path, screen: route.screen, params };
|
|
206
|
+
}
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
|
|
2
210
|
// src/index.ts
|
|
3
211
|
import { gzipSync } from "zlib";
|
|
4
212
|
import { cpSync, existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, readdirSync, rmSync as rmSync2, statSync, writeFileSync } from "fs";
|
|
@@ -128,7 +336,7 @@ import { readFileSync } from "fs";
|
|
|
128
336
|
import { mkdtemp, rm, writeFile } from "fs/promises";
|
|
129
337
|
import { tmpdir } from "os";
|
|
130
338
|
import { dirname, join } from "path";
|
|
131
|
-
async function runKotlinFixture(source) {
|
|
339
|
+
async function runKotlinFixture(source, libraries = []) {
|
|
132
340
|
const gradle = Bun.which("gradle");
|
|
133
341
|
if (!gradle)
|
|
134
342
|
throw new Error("Gradle is required to run native Kotlin fixtures");
|
|
@@ -152,6 +360,8 @@ configurations {
|
|
|
152
360
|
dependencies {
|
|
153
361
|
kotlinCompiler 'org.jetbrains.kotlin:kotlin-compiler-embeddable:${version}'
|
|
154
362
|
kotlinRuntime 'org.jetbrains.kotlin:kotlin-stdlib:${version}'
|
|
363
|
+
${libraries.map((library) => ` kotlinRuntime ${JSON.stringify(library)}`).join(`
|
|
364
|
+
`)}
|
|
155
365
|
}
|
|
156
366
|
tasks.register('compileFixture', JavaExec) {
|
|
157
367
|
classpath = configurations.kotlinCompiler
|
|
@@ -932,32 +1142,8 @@ function normalizeNativeRoutePath(raw) {
|
|
|
932
1142
|
function nativeRouteSegments(path) {
|
|
933
1143
|
return path === "/" ? [] : path.slice(1).split("/");
|
|
934
1144
|
}
|
|
935
|
-
function matchesNativeRoute(nativeScreens, raw) {
|
|
936
|
-
|
|
937
|
-
if (path === null)
|
|
938
|
-
return false;
|
|
939
|
-
const parts = nativeRouteSegments(path);
|
|
940
|
-
return Object.entries(nativeScreens ?? {}).some(([pattern, screen]) => {
|
|
941
|
-
if (!screen)
|
|
942
|
-
return false;
|
|
943
|
-
const normalized = normalizeNativeRoutePath(pattern);
|
|
944
|
-
if (normalized === null)
|
|
945
|
-
return false;
|
|
946
|
-
const route = nativeRouteSegments(normalized);
|
|
947
|
-
for (let index = 0;index < route.length; index++) {
|
|
948
|
-
const segment = route[index];
|
|
949
|
-
if (segment === "*")
|
|
950
|
-
return index === route.length - 1;
|
|
951
|
-
if (index >= parts.length)
|
|
952
|
-
return false;
|
|
953
|
-
if (segment.startsWith(":")) {
|
|
954
|
-
if (!parts[index])
|
|
955
|
-
return false;
|
|
956
|
-
} else if (segment !== parts[index])
|
|
957
|
-
return false;
|
|
958
|
-
}
|
|
959
|
-
return route.length === parts.length;
|
|
960
|
-
});
|
|
1145
|
+
function matchesNativeRoute(nativeScreens, raw, constraints) {
|
|
1146
|
+
return matchNativePath(nativeScreens, raw, constraints) !== null;
|
|
961
1147
|
}
|
|
962
1148
|
function nativeStartPath(config) {
|
|
963
1149
|
if (!config.devServerURL)
|
|
@@ -970,6 +1156,15 @@ function nativeStartPath(config) {
|
|
|
970
1156
|
}
|
|
971
1157
|
}
|
|
972
1158
|
function validateNativeRouteConfig(config) {
|
|
1159
|
+
const constraintProblems = [
|
|
1160
|
+
...nativeRouteConstraintProblems(config.nativeScreens, config.nativeScreenConstraints),
|
|
1161
|
+
...nativeNotificationProblems(config.notificationCategories, config.nativeApp, config.renderer)
|
|
1162
|
+
];
|
|
1163
|
+
if ((config.notificationCategories?.length ?? 0) > 0 && config.renderer !== "native")
|
|
1164
|
+
constraintProblems.push("notificationCategories currently requires the native Android renderer.");
|
|
1165
|
+
if (constraintProblems.length)
|
|
1166
|
+
throw new Error(constraintProblems.join(`
|
|
1167
|
+
`));
|
|
973
1168
|
const screens = Object.entries(config.nativeScreens ?? {});
|
|
974
1169
|
for (const [pattern, screen] of screens) {
|
|
975
1170
|
if (normalizeNativeRoutePath(pattern) !== pattern || /[?#]/.test(pattern))
|
|
@@ -989,11 +1184,11 @@ function validateNativeRouteConfig(config) {
|
|
|
989
1184
|
}
|
|
990
1185
|
if (config.renderer === "native" && screens.length > 0) {
|
|
991
1186
|
for (const id of ids) {
|
|
992
|
-
if (!matchesNativeRoute(config.nativeScreens, id))
|
|
1187
|
+
if (!matchesNativeRoute(config.nativeScreens, id, config.nativeScreenConstraints))
|
|
993
1188
|
throw new Error(`Tab "${id}" is not a nativeScreens path; with renderer: "native" every tab must be a native screen.`);
|
|
994
1189
|
}
|
|
995
1190
|
const start = nativeStartPath(config);
|
|
996
|
-
if (!matchesNativeRoute(config.nativeScreens, start))
|
|
1191
|
+
if (!matchesNativeRoute(config.nativeScreens, start, config.nativeScreenConstraints))
|
|
997
1192
|
throw new Error(`The start path "${start}" (from devServerURL) is not a nativeScreens path; with renderer: "native" the app must start on a native screen.`);
|
|
998
1193
|
}
|
|
999
1194
|
if (config.nativeApp !== undefined) {
|
|
@@ -1059,7 +1254,7 @@ function renderAndroidPermissions(config) {
|
|
|
1059
1254
|
permissions.add("android.permission.FOREGROUND_SERVICE");
|
|
1060
1255
|
permissions.add("android.permission.FOREGROUND_SERVICE_LOCATION");
|
|
1061
1256
|
}
|
|
1062
|
-
if (config.enablePushNotifications)
|
|
1257
|
+
if (config.enablePushNotifications || config.enableLocalNotifications)
|
|
1063
1258
|
permissions.add("android.permission.POST_NOTIFICATIONS");
|
|
1064
1259
|
if (config.enableKeepAwake || config.enableBarometer && config.enableBackgroundLocation)
|
|
1065
1260
|
permissions.add("android.permission.WAKE_LOCK");
|
|
@@ -1767,6 +1962,7 @@ async function run(options) {
|
|
|
1767
1962
|
throw error;
|
|
1768
1963
|
}
|
|
1769
1964
|
}
|
|
1965
|
+
var nativeRouteConstraints = true;
|
|
1770
1966
|
export {
|
|
1771
1967
|
ANDROID_MIN_SDK,
|
|
1772
1968
|
HEALTH_CONNECT_PERMISSIONS,
|
|
@@ -1781,6 +1977,7 @@ export {
|
|
|
1781
1977
|
nativeMapProviders,
|
|
1782
1978
|
nativeMessages,
|
|
1783
1979
|
nativeRouteBundles,
|
|
1980
|
+
nativeRouteConstraints,
|
|
1784
1981
|
nativeRouteRuntime,
|
|
1785
1982
|
nativeSensorCapabilities,
|
|
1786
1983
|
open,
|
|
@@ -1794,4 +1991,4 @@ export {
|
|
|
1794
1991
|
syncNativeFonts
|
|
1795
1992
|
};
|
|
1796
1993
|
|
|
1797
|
-
//# debugId=
|
|
1994
|
+
//# debugId=1EFE2D8AB406306964756E2164756E21
|