@@ -2,6 +2,7 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs";
22import { resolve } from "node:path" ;
33
44const ROOT_DIR = resolve ( process . cwd ( ) ) ;
5+ const PACKAGE_FILE = resolve ( ROOT_DIR , "node_modules/@capacitor/local-notifications/package.json" ) ;
56const 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 [
@@ -34,6 +52,22 @@ const REPLACEMENTS: ReadonlyArray<[string, string]> = [
3452 ] . join ( "\n" ) ,
3553 ] ,
3654] ;
55+ const LEGACY_PLUGIN_PATTERNS = [
56+ 'call.getArray("notifications", JSObject.self)' ,
57+ 'call.getArray("notifications")?.compactMap({ $0 as? JSObject })' ,
58+ "call.reject(" ,
59+ ] as const ;
60+ const REQUIRED_PATCHED_PATTERNS = [
61+ 'call.getArray("notifications", []).compactMap({ $0 as? JSObject })' ,
62+ 'call.getArray("types", []).compactMap({ $0 as? JSObject })' ,
63+ ] as const ;
64+ const FORBIDDEN_PATCHED_PATTERNS = [
65+ "call.reject(" ,
66+ 'call.getArray("notifications", JSObject.self)' ,
67+ 'call.getArray("notifications")?.compactMap({ $0 as? JSObject })' ,
68+ 'call.getArray("types", JSObject.self)' ,
69+ 'call.getArray("types")?.compactMap({ $0 as? JSObject })' ,
70+ ] as const ;
3771
3872const HANDLER_PATCH = {
3973 search :
@@ -165,32 +199,87 @@ const HANDLER_PATCH = {
165199 ` }\n` ,
166200} ;
167201
168- if ( ! existsSync ( TARGET_FILE ) ) {
169- console . log ( `Skipping Capacitor local-notifications patch; missing file: ${ TARGET_FILE } ` ) ;
170- process . exit ( 0 ) ;
202+ function replaceAll ( source : string , search : string , replacement : string ) : { updated : string ; count : number } {
203+ const sourceParts = source . split ( search ) ;
204+ const count = sourceParts . length - 1 ;
205+ if ( count <= 0 ) {
206+ return { updated : source , count : 0 } ;
207+ }
208+
209+ return { updated : sourceParts . join ( replacement ) , count } ;
171210}
172211
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 ;
212+ function assert ( condition : unknown , message : string ) : asserts condition {
213+ if ( ! condition ) {
214+ throw new Error ( message ) ;
177215 }
216+ }
178217
179- const original = readFileSync ( targetFile , "utf8" ) ;
180- let updated = original ;
218+ function isPluginSwiftPatched ( source : string ) : boolean {
219+ return (
220+ REQUIRED_PATCHED_PATTERNS . every ( ( pattern ) => source . includes ( pattern ) ) &&
221+ FORBIDDEN_PATCHED_PATTERNS . every ( ( pattern ) => ! source . includes ( pattern ) )
222+ ) ;
223+ }
181224
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- }
225+ assert ( existsSync ( PACKAGE_FILE ) , `Missing local-notifications package: ${ PACKAGE_FILE } ` ) ;
226+ assert ( existsSync ( TARGET_FILE ) , `Missing local-notifications Swift source: ${ TARGET_FILE } ` ) ;
227+ assert ( existsSync ( HANDLER_FILE ) , `Missing local-notifications Swift source: ${ HANDLER_FILE } ` ) ;
189228
190- if ( updated !== original ) {
191- writeFileSync ( targetFile , updated ) ;
192- console . log ( `Patched ${ targetFile } ` ) ;
193- } else {
194- console . log ( `No patch needed for ${ targetFile } ` ) ;
195- }
229+ const pluginPackage = JSON . parse ( readFileSync ( PACKAGE_FILE , "utf8" ) ) as { version ?: string } ;
230+ const pluginVersion = pluginPackage . version ?? "unknown" ;
231+ const pluginMajor = Number . parseInt ( pluginVersion . split ( "." ) [ 0 ] ?? "" , 10 ) ;
232+
233+ assert ( Number . isFinite ( pluginMajor ) , `Unable to parse @capacitor/local-notifications version: ${ pluginVersion } ` ) ;
234+ assert ( pluginMajor === 8 , `Unsupported @capacitor/local-notifications major version ${ pluginMajor } ; expected 8.x` ) ;
235+
236+ let pluginOriginal = readFileSync ( TARGET_FILE , "utf8" ) ;
237+ let pluginUpdated = pluginOriginal ;
238+ let pluginChanges = 0 ;
239+ for ( const [ search , replacement ] of SWIFT_REPLACEMENTS ) {
240+ const result = replaceAll ( pluginUpdated , search , replacement ) ;
241+ pluginUpdated = result . updated ;
242+ pluginChanges += result . count ;
196243}
244+
245+ const pluginHadKnownLegacyPattern = LEGACY_PLUGIN_PATTERNS . some ( ( pattern ) =>
246+ pluginOriginal . includes ( pattern ) ,
247+ ) ;
248+ const pluginLooksPatched = isPluginSwiftPatched ( pluginUpdated ) ;
249+
250+ assert (
251+ pluginHadKnownLegacyPattern || pluginLooksPatched ,
252+ "Unsupported LocalNotificationsPlugin.swift layout; patch script needs to be updated for the installed plugin version." ,
253+ ) ;
254+
255+ if ( pluginUpdated !== pluginOriginal ) {
256+ writeFileSync ( TARGET_FILE , pluginUpdated ) ;
257+ console . log ( `Patched ${ TARGET_FILE } (${ pluginChanges } replacements, @capacitor/local-notifications ${ pluginVersion } )` ) ;
258+ } else {
259+ console . log ( `No LocalNotificationsPlugin.swift changes needed (@capacitor/local-notifications ${ pluginVersion } )` ) ;
260+ }
261+
262+ assert ( pluginLooksPatched , "LocalNotificationsPlugin.swift patch verification failed." ) ;
263+
264+ const handlerOriginal = readFileSync ( HANDLER_FILE , "utf8" ) ;
265+ let handlerUpdated = handlerOriginal ;
266+ if ( ! handlerUpdated . includes ( "private func makeJSObject(from" ) ) {
267+ const result = replaceAll ( handlerUpdated , HANDLER_PATCH . search , HANDLER_PATCH . replace ) ;
268+ assert (
269+ result . count > 0 ,
270+ "Unable to patch LocalNotificationsHandler.swift; expected source pattern was not found." ,
271+ ) ;
272+ handlerUpdated = result . updated ;
273+ }
274+
275+ if ( handlerUpdated !== handlerOriginal ) {
276+ writeFileSync ( HANDLER_FILE , handlerUpdated ) ;
277+ console . log ( `Patched ${ HANDLER_FILE } ` ) ;
278+ } else {
279+ console . log ( `No LocalNotificationsHandler.swift changes needed` ) ;
280+ }
281+
282+ assert (
283+ handlerUpdated . includes ( "private func makeJSObject(from" ) ,
284+ "LocalNotificationsHandler.swift patch verification failed." ,
285+ ) ;
0 commit comments