cordova-plugin-mserviceinterface-2.0 1.0.99 → 1.0.100

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.
@@ -0,0 +1,1182 @@
1
+ "use strict";
2
+
3
+ var fs = require("fs");
4
+ var path = require("path");
5
+
6
+ function getPreferenceValue(configXml, preferenceName) {
7
+ var escapedName;
8
+ var pattern;
9
+ var match;
10
+
11
+ escapedName = preferenceName.replace(
12
+ /[.*+?^${}()|[\]\\]/g,
13
+ "\\$&"
14
+ );
15
+
16
+ pattern = new RegExp(
17
+ "<preference\\s+" +
18
+ "(?=[^>]*\\bname\\s*=\\s*[\"']" + escapedName + "[\"'])" +
19
+ "(?=[^>]*\\bvalue\\s*=\\s*[\"']([^\"']*)[\"'])" +
20
+ "[^>]*/?>",
21
+ "i"
22
+ );
23
+
24
+ match = configXml.match(pattern);
25
+
26
+ return match !== null ? match[1].trim() : "";
27
+ }
28
+
29
+ function normalizePath(deepLinkPath) {
30
+ if (deepLinkPath === "") {
31
+ return "/";
32
+ }
33
+
34
+ if (deepLinkPath.charAt(0) !== "/") {
35
+ return "/" + deepLinkPath;
36
+ }
37
+
38
+ return deepLinkPath;
39
+ }
40
+
41
+ function validateConfiguration(configuration) {
42
+ var errors = [];
43
+
44
+ if (configuration.scheme === "") {
45
+ errors.push(
46
+ "MServiceDeepLinkScheme is missing in config.xml"
47
+ );
48
+ }
49
+
50
+ if (configuration.host === "") {
51
+ errors.push(
52
+ "MServiceDeepLinkHost is missing in config.xml"
53
+ );
54
+ }
55
+
56
+ if (configuration.path === "") {
57
+ errors.push(
58
+ "MServiceDeepLinkPath is missing in config.xml"
59
+ );
60
+ }
61
+
62
+ if (
63
+ configuration.scheme !== "" &&
64
+ configuration.scheme !== "https"
65
+ ) {
66
+ errors.push(
67
+ "MServiceDeepLinkScheme must be https for verified App Links and Universal Links"
68
+ );
69
+ }
70
+
71
+ if (
72
+ configuration.host.indexOf("http://") === 0 ||
73
+ configuration.host.indexOf("https://") === 0
74
+ ) {
75
+ errors.push(
76
+ "MServiceDeepLinkHost must contain only the hostname, without https://"
77
+ );
78
+ }
79
+
80
+ if (
81
+ configuration.iosAssociatedDomain !== "" &&
82
+ configuration.iosAssociatedDomain.indexOf("applinks:") !== 0
83
+ ) {
84
+ errors.push(
85
+ "MServiceIOSAssociatedDomain must start with applinks:"
86
+ );
87
+ }
88
+
89
+ if (errors.length !== 0) {
90
+ throw new Error(
91
+ "Invalid deep-link configuration:\n- " +
92
+ errors.join("\n- ")
93
+ );
94
+ }
95
+ }
96
+ function escapeRegExp(value) {
97
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
98
+ }
99
+
100
+ function escapeXmlAttribute(value) {
101
+ return String(value)
102
+ .replace(/&/g, "&amp;")
103
+ .replace(/"/g, "&quot;")
104
+ .replace(/</g, "&lt;")
105
+ .replace(/>/g, "&gt;");
106
+ }
107
+
108
+ function getAndroidManifestPath(projectRoot) {
109
+ var possiblePaths = [
110
+ path.join(
111
+ projectRoot,
112
+ "platforms",
113
+ "android",
114
+ "app",
115
+ "src",
116
+ "main",
117
+ "AndroidManifest.xml"
118
+ ),
119
+ path.join(
120
+ projectRoot,
121
+ "platforms",
122
+ "android",
123
+ "AndroidManifest.xml"
124
+ )
125
+ ];
126
+
127
+ var index;
128
+
129
+ for (index = 0; index < possiblePaths.length; index += 1) {
130
+ if (fs.existsSync(possiblePaths[index])) {
131
+ return possiblePaths[index];
132
+ }
133
+ }
134
+
135
+ return "";
136
+ }
137
+
138
+ function setActivityAttribute(openingTag, attributeName, attributeValue) {
139
+ var attributePattern;
140
+
141
+ attributePattern = new RegExp(
142
+ "\\s+" +
143
+ escapeRegExp(attributeName) +
144
+ "\\s*=\\s*[\"'][^\"']*[\"']",
145
+ "i"
146
+ );
147
+
148
+ if (attributePattern.test(openingTag)) {
149
+ return openingTag.replace(
150
+ attributePattern,
151
+ " " +
152
+ attributeName +
153
+ "=\"" +
154
+ escapeXmlAttribute(attributeValue) +
155
+ "\""
156
+ );
157
+ }
158
+
159
+ return openingTag.replace(
160
+ />$/,
161
+ " " +
162
+ attributeName +
163
+ "=\"" +
164
+ escapeXmlAttribute(attributeValue) +
165
+ "\">"
166
+ );
167
+ }
168
+
169
+ function configureAndroidAppLink(projectRoot, configuration) {
170
+ var manifestPath;
171
+ var manifestContent;
172
+ var activityPattern;
173
+ var activityMatch;
174
+ var activityBlock;
175
+ var updatedActivityBlock;
176
+ var markerPattern;
177
+ var intentFilter;
178
+
179
+ manifestPath = getAndroidManifestPath(projectRoot);
180
+
181
+ if (manifestPath === "") {
182
+ throw new Error(
183
+ "AndroidManifest.xml could not be found under platforms/android."
184
+ );
185
+ }
186
+
187
+ manifestContent = fs.readFileSync(manifestPath, "utf8");
188
+
189
+ activityPattern =
190
+ /<activity\b[^>]*android:name\s*=\s*["'][^"']*MainActivity["'][^>]*>[\s\S]*?<\/activity>/i;
191
+
192
+ activityMatch = manifestContent.match(activityPattern);
193
+
194
+ if (activityMatch === null) {
195
+ throw new Error(
196
+ "MainActivity declaration could not be found in AndroidManifest.xml."
197
+ );
198
+ }
199
+
200
+ activityBlock = activityMatch[0];
201
+
202
+ /*
203
+ * Ensure the activity receives App Links when the app is already open.
204
+ */
205
+ updatedActivityBlock = activityBlock.replace(
206
+ /^<activity\b[^>]*>/i,
207
+ function (openingTag) {
208
+ var updatedOpeningTag;
209
+
210
+ updatedOpeningTag = setActivityAttribute(
211
+ openingTag,
212
+ "android:exported",
213
+ "true"
214
+ );
215
+
216
+ updatedOpeningTag = setActivityAttribute(
217
+ updatedOpeningTag,
218
+ "android:launchMode",
219
+ "singleTask"
220
+ );
221
+
222
+ return updatedOpeningTag;
223
+ }
224
+ );
225
+
226
+ intentFilter =
227
+ "\n <!-- MService App Link - START -->\n" +
228
+ " <intent-filter android:autoVerify=\"true\">\n" +
229
+ " <action android:name=\"android.intent.action.VIEW\" />\n" +
230
+ " <category android:name=\"android.intent.category.DEFAULT\" />\n" +
231
+ " <category android:name=\"android.intent.category.BROWSABLE\" />\n" +
232
+ " <data\n" +
233
+ " android:scheme=\"" +
234
+ escapeXmlAttribute(configuration.scheme) +
235
+ "\"\n" +
236
+ " android:host=\"" +
237
+ escapeXmlAttribute(configuration.host) +
238
+ "\"\n" +
239
+ " android:path=\"" +
240
+ escapeXmlAttribute(configuration.path) +
241
+ "\" />\n" +
242
+ " </intent-filter>\n" +
243
+ " <!-- MService App Link - END -->\n";
244
+
245
+ markerPattern =
246
+ /\s*<!-- MService App Link - START -->[\s\S]*?<!-- MService App Link - END -->\s*/i;
247
+
248
+ /*
249
+ * Replace an existing hook-generated filter when configuration changes.
250
+ */
251
+ if (markerPattern.test(updatedActivityBlock)) {
252
+ updatedActivityBlock = updatedActivityBlock.replace(
253
+ markerPattern,
254
+ intentFilter
255
+ );
256
+ } else {
257
+ updatedActivityBlock = updatedActivityBlock.replace(
258
+ /<\/activity>\s*$/i,
259
+ intentFilter + " </activity>"
260
+ );
261
+ }
262
+
263
+ manifestContent = manifestContent.replace(
264
+ activityPattern,
265
+ updatedActivityBlock
266
+ );
267
+
268
+ fs.writeFileSync(
269
+ manifestPath,
270
+ manifestContent,
271
+ "utf8"
272
+ );
273
+
274
+ console.log(
275
+ "[MService Deep Link] Android App Link configured: " +
276
+ configuration.scheme +
277
+ "://" +
278
+ configuration.host +
279
+ configuration.path
280
+ );
281
+
282
+ console.log(
283
+ "[MService Deep Link] Android manifest updated: " +
284
+ manifestPath
285
+ );
286
+ }
287
+
288
+ function findFileRecursively(directoryPath, fileName) {
289
+ var entries;
290
+ var index;
291
+ var entryPath;
292
+ var status;
293
+ var result;
294
+
295
+ if (!fs.existsSync(directoryPath)) {
296
+ return "";
297
+ }
298
+
299
+ entries = fs.readdirSync(directoryPath);
300
+
301
+ for (index = 0; index < entries.length; index += 1) {
302
+ entryPath = path.join(directoryPath, entries[index]);
303
+ status = fs.statSync(entryPath);
304
+
305
+ if (status.isFile() && entries[index] === fileName) {
306
+ return entryPath;
307
+ }
308
+
309
+ if (status.isDirectory()) {
310
+ result = findFileRecursively(entryPath, fileName);
311
+
312
+ if (result !== "") {
313
+ return result;
314
+ }
315
+ }
316
+ }
317
+
318
+ return "";
319
+ }
320
+
321
+ function ensureJavaImport(
322
+ javaContent,
323
+ importStatement,
324
+ newLine
325
+ ) {
326
+ var importPattern;
327
+ var importMatch;
328
+ var lastImportMatch;
329
+ var packageMatch;
330
+ var insertionIndex;
331
+
332
+ if (javaContent.indexOf(importStatement) !== -1) {
333
+ return javaContent;
334
+ }
335
+
336
+ importPattern = /^import[^\r\n]+;$/gm;
337
+ lastImportMatch = null;
338
+
339
+ while (
340
+ (importMatch = importPattern.exec(javaContent)) !== null
341
+ ) {
342
+ lastImportMatch = importMatch;
343
+ }
344
+
345
+ if (lastImportMatch !== null) {
346
+ insertionIndex =
347
+ lastImportMatch.index +
348
+ lastImportMatch[0].length;
349
+
350
+ return (
351
+ javaContent.substring(0, insertionIndex) +
352
+ newLine +
353
+ importStatement +
354
+ javaContent.substring(insertionIndex)
355
+ );
356
+ }
357
+
358
+ packageMatch =
359
+ /^package[^\r\n]+;$/m.exec(javaContent);
360
+
361
+ if (packageMatch === null) {
362
+ throw new Error(
363
+ "Package declaration was not found in MainActivity.java."
364
+ );
365
+ }
366
+
367
+ insertionIndex =
368
+ packageMatch.index +
369
+ packageMatch[0].length;
370
+
371
+ return (
372
+ javaContent.substring(0, insertionIndex) +
373
+ newLine +
374
+ newLine +
375
+ importStatement +
376
+ javaContent.substring(insertionIndex)
377
+ );
378
+ }
379
+
380
+ function insertBeforeFinalClassBrace(
381
+ javaContent,
382
+ javaBlock,
383
+ newLine
384
+ ) {
385
+ var finalBraceIndex;
386
+
387
+ finalBraceIndex = javaContent.lastIndexOf("}");
388
+
389
+ if (finalBraceIndex === -1) {
390
+ throw new Error(
391
+ "Final class closing brace was not found in MainActivity.java."
392
+ );
393
+ }
394
+
395
+ return (
396
+ javaContent.substring(0, finalBraceIndex) +
397
+ javaBlock +
398
+ newLine +
399
+ javaContent.substring(finalBraceIndex)
400
+ );
401
+ }
402
+
403
+ function configureAndroidMainActivity(projectRoot) {
404
+ var javaRoot;
405
+ var mainActivityPath;
406
+ var javaContent;
407
+ var originalContent;
408
+ var newLine;
409
+ var onCreateCall;
410
+ var onNewIntentBlock;
411
+ var helperMethodsBlock;
412
+ var loadUrlPattern;
413
+ var existingOnNewIntentPattern;
414
+ var updatedExistingMethod;
415
+
416
+ javaRoot = path.join(
417
+ projectRoot,
418
+ "platforms",
419
+ "android",
420
+ "app",
421
+ "src",
422
+ "main",
423
+ "java"
424
+ );
425
+
426
+ mainActivityPath = findFileRecursively(
427
+ javaRoot,
428
+ "MainActivity.java"
429
+ );
430
+
431
+ if (mainActivityPath === "") {
432
+ throw new Error(
433
+ "MainActivity.java could not be found under: " +
434
+ javaRoot
435
+ );
436
+ }
437
+
438
+ javaContent = fs.readFileSync(
439
+ mainActivityPath,
440
+ "utf8"
441
+ );
442
+
443
+ originalContent = javaContent;
444
+
445
+ newLine =
446
+ javaContent.indexOf("\r\n") !== -1
447
+ ? "\r\n"
448
+ : "\n";
449
+
450
+ /*
451
+ * Ensure required imports.
452
+ */
453
+ javaContent = ensureJavaImport(
454
+ javaContent,
455
+ "import android.content.Intent;",
456
+ newLine
457
+ );
458
+
459
+ javaContent = ensureJavaImport(
460
+ javaContent,
461
+ "import android.net.Uri;",
462
+ newLine
463
+ );
464
+
465
+ javaContent = ensureJavaImport(
466
+ javaContent,
467
+ "import org.json.JSONObject;",
468
+ newLine
469
+ );
470
+
471
+ /*
472
+ * Cold-start handling:
473
+ * App closed -> App Link opens the app.
474
+ */
475
+ if (
476
+ !/handleAppLinkIntent\s*\(\s*getIntent\s*\(\s*\)\s*\)\s*;/.test(
477
+ javaContent
478
+ )
479
+ ) {
480
+ onCreateCall =
481
+ newLine +
482
+ newLine +
483
+ " // MService App Link onCreate - START" +
484
+ newLine +
485
+ " handleAppLinkIntent(getIntent());" +
486
+ newLine +
487
+ " // MService App Link onCreate - END";
488
+
489
+ loadUrlPattern =
490
+ /loadUrl\s*\(\s*launchUrl\s*\)\s*;/;
491
+
492
+ if (!loadUrlPattern.test(javaContent)) {
493
+ throw new Error(
494
+ "loadUrl(launchUrl) was not found in MainActivity.java."
495
+ );
496
+ }
497
+
498
+ javaContent = javaContent.replace(
499
+ loadUrlPattern,
500
+ function (loadUrlStatement) {
501
+ return loadUrlStatement + onCreateCall;
502
+ }
503
+ );
504
+ }
505
+
506
+ /*
507
+ * Background/running handling:
508
+ * App already open -> App Link arrives through onNewIntent.
509
+ */
510
+ if (
511
+ javaContent.indexOf(
512
+ "protected void onNewIntent(Intent intent)"
513
+ ) === -1
514
+ ) {
515
+ onNewIntentBlock =
516
+ newLine +
517
+ " // MService App Link onNewIntent - START" +
518
+ newLine +
519
+ " @Override" +
520
+ newLine +
521
+ " protected void onNewIntent(Intent intent) {" +
522
+ newLine +
523
+ " super.onNewIntent(intent);" +
524
+ newLine +
525
+ " setIntent(intent);" +
526
+ newLine +
527
+ newLine +
528
+ " handleAppLinkIntent(intent);" +
529
+ newLine +
530
+ " }" +
531
+ newLine +
532
+ " // MService App Link onNewIntent - END" +
533
+ newLine;
534
+
535
+ javaContent = insertBeforeFinalClassBrace(
536
+ javaContent,
537
+ onNewIntentBlock,
538
+ newLine
539
+ );
540
+ } else if (
541
+ javaContent.indexOf(
542
+ "handleAppLinkIntent(intent);"
543
+ ) === -1
544
+ ) {
545
+ existingOnNewIntentPattern =
546
+ /(protected\s+void\s+onNewIntent\s*\(\s*Intent\s+intent\s*\)\s*\{[\s\S]*?setIntent\s*\(\s*intent\s*\)\s*;)/;
547
+
548
+ if (existingOnNewIntentPattern.test(javaContent)) {
549
+ javaContent = javaContent.replace(
550
+ existingOnNewIntentPattern,
551
+ function (methodStart) {
552
+ return (
553
+ methodStart +
554
+ newLine +
555
+ newLine +
556
+ " handleAppLinkIntent(intent);"
557
+ );
558
+ }
559
+ );
560
+ } else {
561
+ updatedExistingMethod = javaContent.replace(
562
+ /(protected\s+void\s+onNewIntent\s*\(\s*Intent\s+intent\s*\)\s*\{[\s\S]*?super\.onNewIntent\s*\(\s*intent\s*\)\s*;)/,
563
+ function (methodStart) {
564
+ return (
565
+ methodStart +
566
+ newLine +
567
+ " setIntent(intent);" +
568
+ newLine +
569
+ newLine +
570
+ " handleAppLinkIntent(intent);"
571
+ );
572
+ }
573
+ );
574
+
575
+ if (updatedExistingMethod === javaContent) {
576
+ throw new Error(
577
+ "Existing onNewIntent method could not be updated safely."
578
+ );
579
+ }
580
+
581
+ javaContent = updatedExistingMethod;
582
+ }
583
+ }
584
+
585
+ /*
586
+ * Add the common native-to-JavaScript bridge.
587
+ */
588
+ if (
589
+ javaContent.indexOf(
590
+ "private void handleAppLinkIntent(Intent intent)"
591
+ ) === -1
592
+ ) {
593
+ helperMethodsBlock =
594
+ newLine +
595
+ " // MService App Link Methods - START" +
596
+ newLine +
597
+ " private void handleAppLinkIntent(Intent intent) {" +
598
+ newLine +
599
+ " if (intent == null) {" +
600
+ newLine +
601
+ " return;" +
602
+ newLine +
603
+ " }" +
604
+ newLine +
605
+ newLine +
606
+ " if (!Intent.ACTION_VIEW.equals(intent.getAction())) {" +
607
+ newLine +
608
+ " return;" +
609
+ newLine +
610
+ " }" +
611
+ newLine +
612
+ newLine +
613
+ " Uri data = intent.getData();" +
614
+ newLine +
615
+ newLine +
616
+ " if (data == null) {" +
617
+ newLine +
618
+ " return;" +
619
+ newLine +
620
+ " }" +
621
+ newLine +
622
+ newLine +
623
+ " sendAppLinkToJavaScript(data.toString(), 0);" +
624
+ newLine +
625
+ " }" +
626
+ newLine +
627
+ newLine +
628
+ " private void sendAppLinkToJavaScript(" +
629
+ newLine +
630
+ " final String appLinkUrl," +
631
+ newLine +
632
+ " final int retryCount) {" +
633
+ newLine +
634
+ newLine +
635
+ " if (" +
636
+ newLine +
637
+ " appView == null ||" +
638
+ newLine +
639
+ " appView.getView() == null ||" +
640
+ newLine +
641
+ " appView.getEngine() == null) {" +
642
+ newLine +
643
+ newLine +
644
+ " if (retryCount < 30) {" +
645
+ newLine +
646
+ " getWindow().getDecorView().postDelayed(" +
647
+ newLine +
648
+ " new Runnable() {" +
649
+ newLine +
650
+ " @Override" +
651
+ newLine +
652
+ " public void run() {" +
653
+ newLine +
654
+ " sendAppLinkToJavaScript(" +
655
+ newLine +
656
+ " appLinkUrl," +
657
+ newLine +
658
+ " retryCount + 1" +
659
+ newLine +
660
+ " );" +
661
+ newLine +
662
+ " }" +
663
+ newLine +
664
+ " }," +
665
+ newLine +
666
+ " 300" +
667
+ newLine +
668
+ " );" +
669
+ newLine +
670
+ " }" +
671
+ newLine +
672
+ newLine +
673
+ " return;" +
674
+ newLine +
675
+ " }" +
676
+ newLine +
677
+ newLine +
678
+ " appView.getView().postDelayed(" +
679
+ newLine +
680
+ " new Runnable() {" +
681
+ newLine +
682
+ " @Override" +
683
+ newLine +
684
+ " public void run() {" +
685
+ newLine +
686
+ " String quotedUrl =" +
687
+ newLine +
688
+ " JSONObject.quote(appLinkUrl);" +
689
+ newLine +
690
+ newLine +
691
+ " String js =" +
692
+ newLine +
693
+ " \"window.mServicePendingUniversalLink = \" +" +
694
+ newLine +
695
+ " quotedUrl +" +
696
+ newLine +
697
+ " \";\" +" +
698
+ newLine +
699
+ " \"try {\"" +
700
+ newLine +
701
+ " + \"window.localStorage.setItem(\"" +
702
+ newLine +
703
+ " + \"'mServicePendingUniversalLink', \"" +
704
+ newLine +
705
+ " + quotedUrl" +
706
+ newLine +
707
+ " + \");\"" +
708
+ newLine +
709
+ " + \"} catch(e) {}\"" +
710
+ newLine +
711
+ " + \"if (window.handleOpenURL) {\"" +
712
+ newLine +
713
+ " + \"window.handleOpenURL(\"" +
714
+ newLine +
715
+ " + quotedUrl" +
716
+ newLine +
717
+ " + \");\"" +
718
+ newLine +
719
+ " + \"window.mServicePendingUniversalLink = '';\"" +
720
+ newLine +
721
+ " + \"try {\"" +
722
+ newLine +
723
+ " + \"window.localStorage.removeItem(\"" +
724
+ newLine +
725
+ " + \"'mServicePendingUniversalLink'\"" +
726
+ newLine +
727
+ " + \");\"" +
728
+ newLine +
729
+ " + \"} catch(e) {}\"" +
730
+ newLine +
731
+ " + \"}\";" +
732
+ newLine +
733
+ newLine +
734
+ " appView.getEngine()" +
735
+ newLine +
736
+ " .evaluateJavascript(js, null);" +
737
+ newLine +
738
+ " }" +
739
+ newLine +
740
+ " }," +
741
+ newLine +
742
+ " 300" +
743
+ newLine +
744
+ " );" +
745
+ newLine +
746
+ " }" +
747
+ newLine +
748
+ " // MService App Link Methods - END" +
749
+ newLine;
750
+
751
+ javaContent = insertBeforeFinalClassBrace(
752
+ javaContent,
753
+ helperMethodsBlock,
754
+ newLine
755
+ );
756
+ }
757
+
758
+ if (javaContent !== originalContent) {
759
+ fs.writeFileSync(
760
+ mainActivityPath,
761
+ javaContent,
762
+ "utf8"
763
+ );
764
+
765
+ console.log(
766
+ "[MService Deep Link] MainActivity.java configured: " +
767
+ mainActivityPath
768
+ );
769
+ } else {
770
+ console.log(
771
+ "[MService Deep Link] MainActivity.java already configured."
772
+ );
773
+ }
774
+ }
775
+ function escapeXmlText(value) {
776
+ return String(value)
777
+ .replace(/&/g, "&amp;")
778
+ .replace(/</g, "&lt;")
779
+ .replace(/>/g, "&gt;");
780
+ }
781
+
782
+ function getIOSEntitlementPaths(projectRoot) {
783
+ var iosRoot;
784
+ var debugEntitlementPath;
785
+ var releaseEntitlementPath;
786
+
787
+ iosRoot = path.join(
788
+ projectRoot,
789
+ "platforms",
790
+ "ios"
791
+ );
792
+
793
+ if (!fs.existsSync(iosRoot)) {
794
+ throw new Error(
795
+ "The iOS platform directory was not found: " +
796
+ iosRoot
797
+ );
798
+ }
799
+
800
+ /*
801
+ * findFileRecursively was added during the Android
802
+ * MainActivity automation step.
803
+ */
804
+ debugEntitlementPath = findFileRecursively(
805
+ iosRoot,
806
+ "Entitlements-Debug.plist"
807
+ );
808
+
809
+ releaseEntitlementPath = findFileRecursively(
810
+ iosRoot,
811
+ "Entitlements-Release.plist"
812
+ );
813
+
814
+ if (debugEntitlementPath === "") {
815
+ throw new Error(
816
+ "Entitlements-Debug.plist was not found under " +
817
+ iosRoot
818
+ );
819
+ }
820
+
821
+ if (releaseEntitlementPath === "") {
822
+ throw new Error(
823
+ "Entitlements-Release.plist was not found under " +
824
+ iosRoot
825
+ );
826
+ }
827
+
828
+ return [
829
+ debugEntitlementPath,
830
+ releaseEntitlementPath
831
+ ];
832
+ }
833
+
834
+ function updateAssociatedDomainsEntitlement(
835
+ entitlementPath,
836
+ associatedDomain
837
+ ) {
838
+ var plistContent;
839
+ var originalContent;
840
+ var newLine;
841
+ var entitlementBlock;
842
+ var entitlementPattern;
843
+ var closingDictIndex;
844
+
845
+ plistContent = fs.readFileSync(
846
+ entitlementPath,
847
+ "utf8"
848
+ );
849
+
850
+ originalContent = plistContent;
851
+
852
+ newLine =
853
+ plistContent.indexOf("\r\n") !== -1
854
+ ? "\r\n"
855
+ : "\n";
856
+
857
+ if (
858
+ plistContent.indexOf("<plist") === -1 ||
859
+ plistContent.indexOf("<dict>") === -1
860
+ ) {
861
+ throw new Error(
862
+ "Invalid entitlement plist format: " +
863
+ entitlementPath
864
+ );
865
+ }
866
+
867
+ entitlementBlock =
868
+ " <key>" +
869
+ "com.apple.developer.associated-domains" +
870
+ "</key>" +
871
+ newLine +
872
+ " <array>" +
873
+ newLine +
874
+ " <string>" +
875
+ escapeXmlText(associatedDomain) +
876
+ "</string>" +
877
+ newLine +
878
+ " </array>";
879
+
880
+ /*
881
+ * Replace an existing Associated Domains entitlement.
882
+ * This prevents duplicates when cordova prepare runs again.
883
+ */
884
+ entitlementPattern =
885
+ /[ \t]*<key>\s*com\.apple\.developer\.associated-domains\s*<\/key>\s*<array>[\s\S]*?<\/array>/i;
886
+
887
+ if (entitlementPattern.test(plistContent)) {
888
+ plistContent = plistContent.replace(
889
+ entitlementPattern,
890
+ entitlementBlock
891
+ );
892
+ } else {
893
+ closingDictIndex = plistContent.lastIndexOf(
894
+ "</dict>"
895
+ );
896
+
897
+ if (closingDictIndex === -1) {
898
+ throw new Error(
899
+ "The closing </dict> was not found in: " +
900
+ entitlementPath
901
+ );
902
+ }
903
+
904
+ plistContent =
905
+ plistContent.substring(
906
+ 0,
907
+ closingDictIndex
908
+ ) +
909
+ entitlementBlock +
910
+ newLine +
911
+ plistContent.substring(
912
+ closingDictIndex
913
+ );
914
+ }
915
+
916
+ if (plistContent !== originalContent) {
917
+ fs.writeFileSync(
918
+ entitlementPath,
919
+ plistContent,
920
+ "utf8"
921
+ );
922
+
923
+ console.log(
924
+ "[MService Deep Link] iOS entitlement updated: " +
925
+ entitlementPath
926
+ );
927
+ } else {
928
+ console.log(
929
+ "[MService Deep Link] iOS entitlement already configured: " +
930
+ entitlementPath
931
+ );
932
+ }
933
+ }
934
+
935
+ function configureIOSAssociatedDomains(
936
+ projectRoot,
937
+ configuration
938
+ ) {
939
+ var entitlementPaths;
940
+ var index;
941
+
942
+ if (
943
+ configuration.iosAssociatedDomain === undefined ||
944
+ configuration.iosAssociatedDomain === ""
945
+ ) {
946
+ throw new Error(
947
+ "MServiceIOSAssociatedDomain is required " +
948
+ "when preparing the iOS platform."
949
+ );
950
+ }
951
+
952
+ if (
953
+ configuration.iosAssociatedDomain.indexOf(
954
+ "applinks:"
955
+ ) !== 0
956
+ ) {
957
+ throw new Error(
958
+ "MServiceIOSAssociatedDomain must start " +
959
+ "with applinks:."
960
+ );
961
+ }
962
+
963
+ entitlementPaths = getIOSEntitlementPaths(
964
+ projectRoot
965
+ );
966
+
967
+ for (
968
+ index = 0;
969
+ index < entitlementPaths.length;
970
+ index += 1
971
+ ) {
972
+ updateAssociatedDomainsEntitlement(
973
+ entitlementPaths[index],
974
+ configuration.iosAssociatedDomain
975
+ );
976
+ }
977
+
978
+ console.log(
979
+ "[MService Deep Link] iOS Associated Domain configured: " +
980
+ configuration.iosAssociatedDomain
981
+ );
982
+ }
983
+ function configureIOSSceneDelegate(projectRoot) {
984
+ var pluginRoot;
985
+ var templatePath;
986
+ var iosRoot;
987
+ var sceneDelegatePath;
988
+ var templateContent;
989
+ var existingContent;
990
+
991
+ /*
992
+ * The hook file is located under:
993
+ * plugin-root/scripts/mservice-deeplink-setup.js
994
+ */
995
+ pluginRoot = path.resolve(
996
+ __dirname,
997
+ ".."
998
+ );
999
+
1000
+ templatePath = path.join(
1001
+ pluginRoot,
1002
+ "templates",
1003
+ "ios",
1004
+ "SceneDelegate.swift"
1005
+ );
1006
+
1007
+ if (!fs.existsSync(templatePath)) {
1008
+ throw new Error(
1009
+ "SceneDelegate.swift template was not found: " +
1010
+ templatePath
1011
+ );
1012
+ }
1013
+
1014
+ iosRoot = path.join(
1015
+ projectRoot,
1016
+ "platforms",
1017
+ "ios"
1018
+ );
1019
+
1020
+ sceneDelegatePath = findFileRecursively(
1021
+ iosRoot,
1022
+ "SceneDelegate.swift"
1023
+ );
1024
+
1025
+ if (sceneDelegatePath === "") {
1026
+ throw new Error(
1027
+ "Generated SceneDelegate.swift was not found under: " +
1028
+ iosRoot
1029
+ );
1030
+ }
1031
+
1032
+ templateContent = fs.readFileSync(
1033
+ templatePath,
1034
+ "utf8"
1035
+ );
1036
+
1037
+ existingContent = fs.readFileSync(
1038
+ sceneDelegatePath,
1039
+ "utf8"
1040
+ );
1041
+
1042
+ if (existingContent === templateContent) {
1043
+ console.log(
1044
+ "[MService Deep Link] SceneDelegate.swift " +
1045
+ "already configured: " +
1046
+ sceneDelegatePath
1047
+ );
1048
+
1049
+ return;
1050
+ }
1051
+
1052
+ fs.writeFileSync(
1053
+ sceneDelegatePath,
1054
+ templateContent,
1055
+ "utf8"
1056
+ );
1057
+
1058
+ console.log(
1059
+ "[MService Deep Link] SceneDelegate.swift configured: " +
1060
+ sceneDelegatePath
1061
+ );
1062
+ }
1063
+ module.exports = function (context) {
1064
+ var options;
1065
+ var cordovaOptions;
1066
+ var platforms;
1067
+ var projectRoot;
1068
+ var configXmlPath;
1069
+ var configXml;
1070
+ var configuration;
1071
+
1072
+ try {
1073
+ options = context.opts || {};
1074
+ cordovaOptions = options.cordova || {};
1075
+
1076
+ platforms =
1077
+ options.platforms ||
1078
+ cordovaOptions.platforms ||
1079
+ [];
1080
+
1081
+ projectRoot = options.projectRoot || "";
1082
+
1083
+ if (projectRoot === "") {
1084
+ throw new Error(
1085
+ "Cordova project root could not be determined."
1086
+ );
1087
+ }
1088
+
1089
+ configXmlPath = path.join(projectRoot, "config.xml");
1090
+
1091
+ if (!fs.existsSync(configXmlPath)) {
1092
+ throw new Error(
1093
+ "config.xml was not found at: " + configXmlPath
1094
+ );
1095
+ }
1096
+
1097
+ configXml = fs.readFileSync(configXmlPath, "utf8");
1098
+
1099
+ configuration = {
1100
+ scheme: getPreferenceValue(
1101
+ configXml,
1102
+ "MServiceDeepLinkScheme"
1103
+ ),
1104
+ host: getPreferenceValue(
1105
+ configXml,
1106
+ "MServiceDeepLinkHost"
1107
+ ),
1108
+ path: normalizePath(
1109
+ getPreferenceValue(
1110
+ configXml,
1111
+ "MServiceDeepLinkPath"
1112
+ )
1113
+ ),
1114
+ iosAssociatedDomain: getPreferenceValue(
1115
+ configXml,
1116
+ "MServiceIOSAssociatedDomain"
1117
+ )
1118
+ };
1119
+
1120
+ validateConfiguration(configuration);
1121
+
1122
+ if (platforms.indexOf("android") !== -1) {
1123
+ configureAndroidAppLink(
1124
+ projectRoot,
1125
+ configuration
1126
+ );
1127
+
1128
+ configureAndroidMainActivity(
1129
+ projectRoot
1130
+ );
1131
+ }
1132
+ if (platforms.indexOf("ios") !== -1) {
1133
+ configureIOSAssociatedDomains(
1134
+ projectRoot,
1135
+ configuration
1136
+ );
1137
+
1138
+ configureIOSSceneDelegate(
1139
+ projectRoot
1140
+ );
1141
+ }
1142
+ console.log("");
1143
+ console.log("================================================");
1144
+ console.log("[MService Deep Link] Configuration validated");
1145
+ console.log(
1146
+ "[MService Deep Link] Platforms: " +
1147
+ platforms.join(", ")
1148
+ );
1149
+ console.log(
1150
+ "[MService Deep Link] Scheme: " +
1151
+ configuration.scheme
1152
+ );
1153
+ console.log(
1154
+ "[MService Deep Link] Host: " +
1155
+ configuration.host
1156
+ );
1157
+ console.log(
1158
+ "[MService Deep Link] Path: " +
1159
+ configuration.path
1160
+ );
1161
+ console.log(
1162
+ "[MService Deep Link] iOS Associated Domain: " +
1163
+ (
1164
+ configuration.iosAssociatedDomain ||
1165
+ "Not configured"
1166
+ )
1167
+ );
1168
+ console.log("================================================");
1169
+ console.log("");
1170
+
1171
+ return Promise.resolve();
1172
+ } catch (exception) {
1173
+ console.error("");
1174
+ console.error(
1175
+ "[MService Deep Link] Hook failed: " +
1176
+ exception.message
1177
+ );
1178
+ console.error("");
1179
+
1180
+ return Promise.reject(exception);
1181
+ }
1182
+ };