@craft-native/ios 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/README.md +63 -0
- package/dist/cli.js +291 -81
- package/dist/index.d.ts +1 -685
- package/dist/index.js +293 -81
- package/dist/internal/ios/src/index.d.ts +684 -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/CraftApp.swift +118 -16
- package/templates/CraftHybrid.swift +75 -5
- package/templates/CraftNativeActions.swift +105 -14
- package/templates/CraftNativeFiles.swift +230 -18
- package/templates/CraftNativeMediaOwnership.swift +92 -0
- package/templates/CraftNativeScreen.swift +136 -6
- package/templates/CraftNotificationService.swift +199 -0
- package/templates/CraftVoiceRecording.swift +29 -1
- package/templates/testing/DriverTests.swift.template +44 -5
- /package/dist/{app-shortcuts.d.ts → internal/ios/src/app-shortcuts.d.ts} +0 -0
- /package/dist/{runtime-profile.d.ts → internal/ios/src/runtime-profile.d.ts} +0 -0
- /package/dist/{source-audit.d.ts → internal/ios/src/source-audit.d.ts} +0 -0
- /package/dist/{testing.d.ts → internal/ios/src/testing.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
|
|
|
@@ -479,83 +687,6 @@ function resolveCraftVersion(startDir = import.meta.dir) {
|
|
|
479
687
|
return "0.0.0";
|
|
480
688
|
}
|
|
481
689
|
var CRAFT_VERSION = resolveCraftVersion();
|
|
482
|
-
function normalizeNativePath(raw) {
|
|
483
|
-
if (typeof raw !== "string")
|
|
484
|
-
return null;
|
|
485
|
-
let path = raw;
|
|
486
|
-
const cut = path.search(/[?#]/);
|
|
487
|
-
if (cut >= 0)
|
|
488
|
-
path = path.slice(0, cut);
|
|
489
|
-
if (!path.startsWith("/"))
|
|
490
|
-
return null;
|
|
491
|
-
path = path.replace(/\/{2,}/g, "/");
|
|
492
|
-
if (path.length > 1 && path.endsWith("/"))
|
|
493
|
-
path = path.slice(0, -1);
|
|
494
|
-
return path;
|
|
495
|
-
}
|
|
496
|
-
function routeSegments(path) {
|
|
497
|
-
return path === "/" ? [] : path.slice(1).split("/");
|
|
498
|
-
}
|
|
499
|
-
function matchNativePath(nativeScreens, raw) {
|
|
500
|
-
const path = normalizeNativePath(raw);
|
|
501
|
-
if (path === null)
|
|
502
|
-
return null;
|
|
503
|
-
const routes = Object.entries(nativeScreens ?? {}).map(([pattern, screen]) => ({ pattern: normalizeNativePath(pattern), screen })).filter((route) => route.pattern !== null && !!route.screen).map((route) => ({ ...route, segments: routeSegments(route.pattern) }));
|
|
504
|
-
const wild = (segments) => segments.at(-1) === "*" ? 1 : 0;
|
|
505
|
-
const fixed = (segments) => segments.filter((s) => s !== "*" && !s.startsWith(":")).length;
|
|
506
|
-
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));
|
|
507
|
-
const decode = (value) => {
|
|
508
|
-
try {
|
|
509
|
-
return decodeURIComponent(value);
|
|
510
|
-
} catch {
|
|
511
|
-
return value;
|
|
512
|
-
}
|
|
513
|
-
};
|
|
514
|
-
const parts = routeSegments(path);
|
|
515
|
-
for (const route of routes) {
|
|
516
|
-
const params = {};
|
|
517
|
-
let matched = true;
|
|
518
|
-
for (let i = 0;i < route.segments.length; i++) {
|
|
519
|
-
const segment = route.segments[i];
|
|
520
|
-
if (segment === "*") {
|
|
521
|
-
params["*"] = parts.slice(i).join("/");
|
|
522
|
-
break;
|
|
523
|
-
}
|
|
524
|
-
if (i >= parts.length) {
|
|
525
|
-
matched = false;
|
|
526
|
-
break;
|
|
527
|
-
}
|
|
528
|
-
if (segment.startsWith(":")) {
|
|
529
|
-
if (!parts[i]) {
|
|
530
|
-
matched = false;
|
|
531
|
-
break;
|
|
532
|
-
}
|
|
533
|
-
params[segment.slice(1)] = decode(parts[i]);
|
|
534
|
-
} else if (segment !== parts[i]) {
|
|
535
|
-
matched = false;
|
|
536
|
-
break;
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
if (!matched)
|
|
540
|
-
continue;
|
|
541
|
-
if (route.segments.at(-1) !== "*" && route.segments.length !== parts.length)
|
|
542
|
-
continue;
|
|
543
|
-
const query = raw.indexOf("?");
|
|
544
|
-
if (query >= 0) {
|
|
545
|
-
for (const item of raw.slice(query + 1).split("#")[0].split("&")) {
|
|
546
|
-
if (!item)
|
|
547
|
-
continue;
|
|
548
|
-
const [rawKey = "", ...rest] = item.split("=");
|
|
549
|
-
const key = decode(rawKey);
|
|
550
|
-
if (!key || key in params)
|
|
551
|
-
continue;
|
|
552
|
-
params[key] = decode(rest.join("=").replace(/\+/g, " "));
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
return { path, screen: route.screen, params };
|
|
556
|
-
}
|
|
557
|
-
return null;
|
|
558
|
-
}
|
|
559
690
|
function isHybrid(config) {
|
|
560
691
|
return (config.renderer ?? "web") === "web" && Object.keys(config.nativeScreens ?? {}).length > 0;
|
|
561
692
|
}
|
|
@@ -576,8 +707,9 @@ function nativeStartPath(config) {
|
|
|
576
707
|
}
|
|
577
708
|
}
|
|
578
709
|
function hybridConfigProblems(config) {
|
|
579
|
-
const problems =
|
|
710
|
+
const problems = nativeNotificationProblems(config.notificationCategories, config.nativeApp, config.renderer);
|
|
580
711
|
const screens = Object.entries(config.nativeScreens ?? {});
|
|
712
|
+
problems.push(...nativeRouteConstraintProblems(config.nativeScreens, config.nativeScreenConstraints));
|
|
581
713
|
for (const [pattern, screen] of screens) {
|
|
582
714
|
if (normalizeNativePath(pattern) !== pattern || /[?#]/.test(pattern))
|
|
583
715
|
problems.push(`nativeScreens path "${pattern}" must be an absolute path with no query, hash or trailing slash.`);
|
|
@@ -601,11 +733,11 @@ function hybridConfigProblems(config) {
|
|
|
601
733
|
}
|
|
602
734
|
if (hasNativeRoutes(config)) {
|
|
603
735
|
for (const id of ids) {
|
|
604
|
-
if (!matchNativePath(config.nativeScreens, id))
|
|
736
|
+
if (!matchNativePath(config.nativeScreens, id, config.nativeScreenConstraints))
|
|
605
737
|
problems.push(`Tab "${id}" is not a nativeScreens path; with renderer: "native" every tab must be a native screen.`);
|
|
606
738
|
}
|
|
607
739
|
const start = nativeStartPath(config);
|
|
608
|
-
if (!matchNativePath(config.nativeScreens, start))
|
|
740
|
+
if (!matchNativePath(config.nativeScreens, start, config.nativeScreenConstraints))
|
|
609
741
|
problems.push(`The start path "${start}" (from devServerURL) is not a nativeScreens path; with renderer: "native" the app must start on a native screen.`);
|
|
610
742
|
}
|
|
611
743
|
if (config.nativeApp !== undefined) {
|
|
@@ -889,6 +1021,78 @@ function validateHealthKitBackground(config) {
|
|
|
889
1021
|
if (!["immediate", "hourly", "daily", "weekly"].includes(config.healthKitBackgroundFrequency ?? "hourly"))
|
|
890
1022
|
throw new Error("healthKitBackgroundFrequency must be immediate, hourly, daily or weekly");
|
|
891
1023
|
}
|
|
1024
|
+
function validateNotificationAttachments(config) {
|
|
1025
|
+
if (config.enableNotificationAttachments !== undefined && typeof config.enableNotificationAttachments !== "boolean")
|
|
1026
|
+
throw new Error("enableNotificationAttachments must be a boolean");
|
|
1027
|
+
if (config.enableNotificationAttachments && config.enablePushNotifications !== true)
|
|
1028
|
+
throw new Error("enableNotificationAttachments requires enablePushNotifications");
|
|
1029
|
+
}
|
|
1030
|
+
var notificationServiceInclude = ` - path: CraftNotificationService.yml
|
|
1031
|
+
`;
|
|
1032
|
+
function syncNotificationService(output, config) {
|
|
1033
|
+
validateNotificationAttachments(config);
|
|
1034
|
+
const spec = join4(output, "CraftNotificationService.yml");
|
|
1035
|
+
if (!config.enableNotificationAttachments) {
|
|
1036
|
+
writeFileSync3(spec, `{}
|
|
1037
|
+
`);
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
const name = productName(config.appName);
|
|
1041
|
+
const folder = join4(output, "NotificationService");
|
|
1042
|
+
mkdirSync2(folder, { recursive: true });
|
|
1043
|
+
const source = readFileSync3(join4(TEMPLATES_DIR, "CraftNotificationService.swift"), "utf8");
|
|
1044
|
+
const destination = join4(folder, "CraftNotificationService.swift");
|
|
1045
|
+
if (!existsSync2(destination) || readFileSync3(destination, "utf8") !== source)
|
|
1046
|
+
writeFileSync3(destination, source);
|
|
1047
|
+
writeFileSync3(join4(folder, "Info.plist"), `<?xml version="1.0" encoding="UTF-8"?>
|
|
1048
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
1049
|
+
<plist version="1.0"><dict>
|
|
1050
|
+
<key>CFBundleExecutable</key><string>$(EXECUTABLE_NAME)</string>
|
|
1051
|
+
<key>CFBundleIdentifier</key><string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
|
1052
|
+
<key>CFBundleName</key><string>$(PRODUCT_NAME)</string>
|
|
1053
|
+
<key>CFBundlePackageType</key><string>XPC!</string>
|
|
1054
|
+
<key>CFBundleShortVersionString</key><string>$(MARKETING_VERSION)</string>
|
|
1055
|
+
<key>CFBundleVersion</key><string>$(CURRENT_PROJECT_VERSION)</string>
|
|
1056
|
+
<key>NSExtension</key><dict>
|
|
1057
|
+
<key>NSExtensionPointIdentifier</key><string>com.apple.usernotifications.service</string>
|
|
1058
|
+
<key>NSExtensionPrincipalClass</key><string>$(PRODUCT_MODULE_NAME).CraftNotificationService</string>
|
|
1059
|
+
</dict>
|
|
1060
|
+
</dict></plist>
|
|
1061
|
+
`);
|
|
1062
|
+
writeFileSync3(spec, `targets:
|
|
1063
|
+
${name}:
|
|
1064
|
+
dependencies:
|
|
1065
|
+
- target: ${name}NotificationService
|
|
1066
|
+
embed: true
|
|
1067
|
+
${name}NotificationService:
|
|
1068
|
+
type: app-extension
|
|
1069
|
+
platform: iOS
|
|
1070
|
+
deploymentTarget: "${config.iosVersion || "15.0"}"
|
|
1071
|
+
sources:
|
|
1072
|
+
- NotificationService
|
|
1073
|
+
settings:
|
|
1074
|
+
INFOPLIST_FILE: NotificationService/Info.plist
|
|
1075
|
+
PRODUCT_NAME: "$(TARGET_NAME)"
|
|
1076
|
+
PRODUCT_BUNDLE_IDENTIFIER: ${config.bundleId}.notificationservice
|
|
1077
|
+
SWIFT_VERSION: "5.0"
|
|
1078
|
+
APPLICATION_EXTENSION_API_ONLY: YES
|
|
1079
|
+
SKIP_INSTALL: YES
|
|
1080
|
+
`);
|
|
1081
|
+
const project = join4(output, "project.yml");
|
|
1082
|
+
if (existsSync2(project)) {
|
|
1083
|
+
const text = readFileSync3(project, "utf8");
|
|
1084
|
+
const include = Bun.YAML.parse(text).include;
|
|
1085
|
+
const includes = Array.isArray(include) ? include : [include];
|
|
1086
|
+
const present = includes.some((entry) => entry === "CraftNotificationService.yml" || entry && typeof entry === "object" && ("path" in entry) && entry.path === "CraftNotificationService.yml");
|
|
1087
|
+
if (!present) {
|
|
1088
|
+
if (/^include:/m.test(text) && !/^include:[ \t]*\r?\n[ \t]+-/m.test(text))
|
|
1089
|
+
throw new Error("Add a CraftNotificationService.yml entry to the project.yml include list");
|
|
1090
|
+
writeFileSync3(project, /^include:/m.test(text) ? text.replace(/^include:[ \t]*\r?\n/m, `include:
|
|
1091
|
+
${notificationServiceInclude}`) : `include:
|
|
1092
|
+
${notificationServiceInclude}${text}`);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
892
1096
|
function renderEntitlements(config) {
|
|
893
1097
|
validateHealthKitBackground(config);
|
|
894
1098
|
const entries = [];
|
|
@@ -1375,6 +1579,7 @@ function restoreShell(output) {
|
|
|
1375
1579
|
writeFileSync3(file, JSON.stringify(config, null, 2));
|
|
1376
1580
|
}
|
|
1377
1581
|
function writeShell(output, config) {
|
|
1582
|
+
validateNotificationAttachments(config);
|
|
1378
1583
|
if (!config.appName || !/^[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+$/.test(config.bundleId))
|
|
1379
1584
|
throw new Error("The iOS shell needs an appName and a valid bundleId in craft.config.json.");
|
|
1380
1585
|
const writeSource = (path, source) => {
|
|
@@ -1406,6 +1611,7 @@ function writeShell(output, config) {
|
|
|
1406
1611
|
writeSource(join4(output, "Sources", "CraftNativeUI.swift"), readFileSync3(join4(TEMPLATES_DIR, "CraftNativeUI.swift"), "utf8"));
|
|
1407
1612
|
writeSource(join4(output, "Sources", "CraftNativeServices.swift"), readFileSync3(join4(TEMPLATES_DIR, "CraftNativeServices.swift"), "utf8"));
|
|
1408
1613
|
writeSource(join4(output, "Sources", "CraftNativeFiles.swift"), readFileSync3(join4(TEMPLATES_DIR, "CraftNativeFiles.swift"), "utf8"));
|
|
1614
|
+
writeSource(join4(output, "Sources", "CraftNativeMediaOwnership.swift"), readFileSync3(join4(TEMPLATES_DIR, "CraftNativeMediaOwnership.swift"), "utf8"));
|
|
1409
1615
|
writeSource(join4(output, "Sources", "CraftVoiceRecording.swift"), readFileSync3(join4(TEMPLATES_DIR, "CraftVoiceRecording.swift"), "utf8"));
|
|
1410
1616
|
writeSource(join4(output, "Sources", "CraftWebAudio.swift"), readFileSync3(join4(TEMPLATES_DIR, "CraftWebAudio.swift"), "utf8"));
|
|
1411
1617
|
writeSource(join4(output, "Sources", "CraftHybrid.swift"), readFileSync3(join4(TEMPLATES_DIR, "CraftHybrid.swift"), "utf8"));
|
|
@@ -1422,6 +1628,7 @@ function writeShell(output, config) {
|
|
|
1422
1628
|
const source = readFileSync3(join4(TEMPLATES_DIR, template), "utf8").replace(/\{\{APP_NAME\}\}/g, name).replace(/\{\{HEALTH_KIT_ENABLED\}\}/g, config.enableHealthKit ? "true" : "false");
|
|
1423
1629
|
writeSource(join4(output, folder, filename), source);
|
|
1424
1630
|
}
|
|
1631
|
+
syncNotificationService(output, config);
|
|
1425
1632
|
}
|
|
1426
1633
|
function refreshUsageDescriptions(output, config) {
|
|
1427
1634
|
const path = join4(output, "Info.plist");
|
|
@@ -1462,6 +1669,7 @@ async function init(options) {
|
|
|
1462
1669
|
...given
|
|
1463
1670
|
};
|
|
1464
1671
|
validateHealthKitBackground(config);
|
|
1672
|
+
validateNotificationAttachments(config);
|
|
1465
1673
|
if (config.renderer !== "web" && config.renderer !== "native") {
|
|
1466
1674
|
throw new Error(`Unknown iOS renderer: ${config.renderer}. Expected web or native.`);
|
|
1467
1675
|
}
|
|
@@ -1563,7 +1771,8 @@ ${plistArray(nativeFontFiles, 2)}
|
|
|
1563
1771
|
${nativeDependencies.join(`
|
|
1564
1772
|
`)}` : "").replace(/\{\{NATIVE_TARGETS\}\}/g, nativeTargets.join(`
|
|
1565
1773
|
`));
|
|
1566
|
-
writeFileSync3(join4(output, "project.yml"),
|
|
1774
|
+
writeFileSync3(join4(output, "project.yml"), `include:
|
|
1775
|
+
${notificationServiceInclude}${projectYml}`);
|
|
1567
1776
|
writeFileSync3(join4(output, "Craft.entitlements"), renderEntitlements(config));
|
|
1568
1777
|
writeFileSync3(join4(output, "PrivacyInfo.xcprivacy"), renderPrivacyManifest(config));
|
|
1569
1778
|
renderAssetCatalog(output, config);
|
|
@@ -1613,6 +1822,7 @@ async function build(options) {
|
|
|
1613
1822
|
throw new Error(`No craft.config.json found in ${output}. Run 'craft ios init' first.`);
|
|
1614
1823
|
}
|
|
1615
1824
|
const config = JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
1825
|
+
validateNotificationAttachments(config);
|
|
1616
1826
|
if (config.renderer !== undefined && config.renderer !== "web" && config.renderer !== "native") {
|
|
1617
1827
|
throw new Error(`Unknown iOS renderer: ${config.renderer}. Expected web or native.`);
|
|
1618
1828
|
}
|
|
@@ -1802,6 +2012,7 @@ async function run(options) {
|
|
|
1802
2012
|
console.log(" 4. Click Run (\u25B6\uFE0F)");
|
|
1803
2013
|
}
|
|
1804
2014
|
}
|
|
2015
|
+
var nativeRouteConstraints = true;
|
|
1805
2016
|
export {
|
|
1806
2017
|
LAUNCH_LOGO,
|
|
1807
2018
|
assertNoApplicationSwiftSources,
|
|
@@ -1821,6 +2032,7 @@ export {
|
|
|
1821
2032
|
matchNativePath,
|
|
1822
2033
|
nativeMessages,
|
|
1823
2034
|
nativeRouteBundles,
|
|
2035
|
+
nativeRouteConstraints,
|
|
1824
2036
|
nativeRouteRuntime,
|
|
1825
2037
|
nativeSensorCapabilities,
|
|
1826
2038
|
nativeStartPath,
|