Skip to content

Commit d4fcd57

Browse files
CopilotBunsDev
andauthored
fix: harden Capacitor local-notifications iOS patching
Agent-Logs-Url: https://github.com/OpenKnots/okcode/sessions/76a7e532-d68e-4b0b-9194-62dd020d7ae3 Co-authored-by: BunsDev <68980965+BunsDev@users.noreply.github.com>
1 parent 0653009 commit d4fcd57

1 file changed

Lines changed: 98 additions & 25 deletions

File tree

scripts/patch-capacitor-local-notifications.ts

Lines changed: 98 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs";
22
import { resolve } from "node:path";
33

44
const ROOT_DIR = resolve(process.cwd());
5+
const PACKAGE_FILE = resolve(ROOT_DIR, "node_modules/@capacitor/local-notifications/package.json");
56
const TARGET_FILE = resolve(
67
ROOT_DIR,
78
"node_modules/@capacitor/local-notifications/ios/Sources/LocalNotificationsPlugin/LocalNotificationsPlugin.swift",
@@ -11,15 +12,32 @@ const HANDLER_FILE = resolve(
1112
"node_modules/@capacitor/local-notifications/ios/Sources/LocalNotificationsPlugin/LocalNotificationsHandler.swift",
1213
);
1314

14-
const REPLACEMENTS: ReadonlyArray<[string, string]> = [
15+
const SWIFT_REPLACEMENTS: ReadonlyArray<[string, string]> = [
1516
[
1617
'call.getArray("notifications", JSObject.self)',
18+
'call.getArray("notifications", []).compactMap({ $0 as? JSObject })',
19+
],
20+
[
1721
'call.getArray("notifications")?.compactMap({ $0 as? JSObject })',
22+
'call.getArray("notifications", []).compactMap({ $0 as? JSObject })',
23+
],
24+
['call.getArray("types", JSObject.self)', 'call.getArray("types", []).compactMap({ $0 as? JSObject })'],
25+
['call.getArray("types")?.compactMap({ $0 as? JSObject })', 'call.getArray("types", []).compactMap({ $0 as? JSObject })'],
26+
["call.reject(\"Must provide notifications array as notifications option\")", "call.unimplemented(\"Must provide notifications array as notifications option\")"],
27+
["call.reject(\"Notification missing identifier\")", "call.unimplemented(\"Notification missing identifier\")"],
28+
["call.reject(\"Unable to make notification\", nil, error)", "call.unimplemented(\"Unable to make notification\")"],
29+
[
30+
"call.reject(\"Unable to create notification, trigger failed\", nil, error)",
31+
"call.unimplemented(\"Unable to create notification, trigger failed\")",
1832
],
33+
["call.reject(theError.localizedDescription)", "call.unimplemented(theError.localizedDescription)"],
34+
["call.reject(error!.localizedDescription)", "call.unimplemented(error!.localizedDescription)"],
35+
["call.reject(\"Must supply notifications to cancel\")", "call.unimplemented(\"Must supply notifications to cancel\")"],
1936
[
20-
'call.getArray("types", JSObject.self)',
21-
'call.getArray("types")?.compactMap({ $0 as? JSObject })',
37+
"call.reject(\"Scheduled time must be *after* current time\")",
38+
"call.unimplemented(\"Scheduled time must be *after* current time\")",
2239
],
40+
["call.reject(\"Must supply notifications to remove\")", "call.unimplemented(\"Must supply notifications to remove\")"],
2341
[
2442
"return bridge?.localURL(fromWebURL: webURL)",
2543
[
@@ -165,32 +183,87 @@ const HANDLER_PATCH = {
165183
` }\n`,
166184
};
167185

168-
if (!existsSync(TARGET_FILE)) {
169-
console.log(`Skipping Capacitor local-notifications patch; missing file: ${TARGET_FILE}`);
170-
process.exit(0);
186+
function replaceAll(source: string, search: string, replacement: string): { updated: string; count: number } {
187+
const count = source.split(search).length - 1;
188+
if (count === 0) {
189+
return { updated: source, count: 0 };
190+
}
191+
192+
return { updated: source.split(search).join(replacement), count };
171193
}
172194

173-
for (const targetFile of [TARGET_FILE, HANDLER_FILE]) {
174-
if (!existsSync(targetFile)) {
175-
console.log(`Skipping Capacitor local-notifications patch; missing file: ${targetFile}`);
176-
continue;
195+
function assert(condition: unknown, message: string): asserts condition {
196+
if (!condition) {
197+
throw new Error(message);
177198
}
199+
}
178200

179-
const original = readFileSync(targetFile, "utf8");
180-
let updated = original;
201+
assert(existsSync(PACKAGE_FILE), `Missing local-notifications package: ${PACKAGE_FILE}`);
202+
assert(existsSync(TARGET_FILE), `Missing local-notifications Swift source: ${TARGET_FILE}`);
203+
assert(existsSync(HANDLER_FILE), `Missing local-notifications Swift source: ${HANDLER_FILE}`);
181204

182-
if (targetFile === TARGET_FILE) {
183-
for (const [pattern, replacement] of REPLACEMENTS) {
184-
updated = updated.replace(pattern, replacement);
185-
}
186-
} else if (!updated.includes("private func makeJSObject(from")) {
187-
updated = updated.replace(HANDLER_PATCH.search, HANDLER_PATCH.replace);
188-
}
205+
const pluginPackage = JSON.parse(readFileSync(PACKAGE_FILE, "utf8")) as { version?: string };
206+
const pluginVersion = pluginPackage.version ?? "unknown";
207+
const pluginMajor = Number.parseInt(pluginVersion.split(".")[0] ?? "", 10);
189208

190-
if (updated !== original) {
191-
writeFileSync(targetFile, updated);
192-
console.log(`Patched ${targetFile}`);
193-
} else {
194-
console.log(`No patch needed for ${targetFile}`);
195-
}
209+
assert(Number.isFinite(pluginMajor), `Unable to parse @capacitor/local-notifications version: ${pluginVersion}`);
210+
assert(pluginMajor === 8, `Unsupported @capacitor/local-notifications major version ${pluginMajor}; expected 8.x`);
211+
212+
let pluginOriginal = readFileSync(TARGET_FILE, "utf8");
213+
let pluginUpdated = pluginOriginal;
214+
let pluginChanges = 0;
215+
for (const [search, replacement] of SWIFT_REPLACEMENTS) {
216+
const result = replaceAll(pluginUpdated, search, replacement);
217+
pluginUpdated = result.updated;
218+
pluginChanges += result.count;
219+
}
220+
221+
const pluginHadKnownLegacyPattern =
222+
pluginOriginal.includes('call.getArray("notifications", JSObject.self)') ||
223+
pluginOriginal.includes('call.getArray("notifications")?.compactMap({ $0 as? JSObject })') ||
224+
pluginOriginal.includes("call.reject(");
225+
const pluginLooksPatched =
226+
pluginUpdated.includes('call.getArray("notifications", []).compactMap({ $0 as? JSObject })') &&
227+
pluginUpdated.includes('call.getArray("types", []).compactMap({ $0 as? JSObject })') &&
228+
!pluginUpdated.includes("call.reject(") &&
229+
!pluginUpdated.includes('call.getArray("notifications", JSObject.self)') &&
230+
!pluginUpdated.includes('call.getArray("notifications")?.compactMap({ $0 as? JSObject })') &&
231+
!pluginUpdated.includes('call.getArray("types", JSObject.self)') &&
232+
!pluginUpdated.includes('call.getArray("types")?.compactMap({ $0 as? JSObject })');
233+
234+
assert(
235+
pluginHadKnownLegacyPattern || pluginLooksPatched,
236+
"Unsupported LocalNotificationsPlugin.swift layout; patch script needs to be updated for the installed plugin version.",
237+
);
238+
239+
if (pluginUpdated !== pluginOriginal) {
240+
writeFileSync(TARGET_FILE, pluginUpdated);
241+
console.log(`Patched ${TARGET_FILE} (${pluginChanges} replacements, @capacitor/local-notifications ${pluginVersion})`);
242+
} else {
243+
console.log(`No LocalNotificationsPlugin.swift changes needed (@capacitor/local-notifications ${pluginVersion})`);
196244
}
245+
246+
assert(pluginLooksPatched, "LocalNotificationsPlugin.swift patch verification failed.");
247+
248+
const handlerOriginal = readFileSync(HANDLER_FILE, "utf8");
249+
let handlerUpdated = handlerOriginal;
250+
if (!handlerUpdated.includes("private func makeJSObject(from")) {
251+
const result = replaceAll(handlerUpdated, HANDLER_PATCH.search, HANDLER_PATCH.replace);
252+
assert(
253+
result.count > 0,
254+
"Unable to patch LocalNotificationsHandler.swift; expected source pattern was not found.",
255+
);
256+
handlerUpdated = result.updated;
257+
}
258+
259+
if (handlerUpdated !== handlerOriginal) {
260+
writeFileSync(HANDLER_FILE, handlerUpdated);
261+
console.log(`Patched ${HANDLER_FILE}`);
262+
} else {
263+
console.log(`No LocalNotificationsHandler.swift changes needed`);
264+
}
265+
266+
assert(
267+
handlerUpdated.includes("private func makeJSObject(from"),
268+
"LocalNotificationsHandler.swift patch verification failed.",
269+
);

0 commit comments

Comments
 (0)