@craft-native/android 0.0.451 → 0.0.467

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/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
- const path = normalizeNativeRoutePath(raw);
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=607F99B9A5B4A43764756E2164756E21
1994
+ //# debugId=1EFE2D8AB406306964756E2164756E21