ReviewOS

also looking at this

craft-native/craft

fix(ios): no gated dispatcher arm can hang the page any more (58 → 0)

#120
Merged glennmichael123 wants to merge fix/ios-gated-arms-reject into main
1 file +298 -106
packages/ios/templates/CraftApp.swiftmodified+298-106
Changes to packages/ios/templates/CraftApp.swift
@@ -561,13 +561,19 @@ struct CraftWebView: UIViewRepresentable {
561561 func dispatch(action: String, body: [String: Any], callbackId: String?) {
562562 switch action {
563563 case "startListening":
564 if config.enableSpeechRecognition { startSpeechRecognition() }
564 if config.enableSpeechRecognition {
565 startSpeechRecognition()
566 } else {
567 rejectCallback(callbackId, error: "Speech recognition is disabled", code: "CAPABILITY_DISABLED")
568 }
565569 case "stopListening":
566570 stopSpeechRecognition()
567571 case "haptic":
568572 if config.enableHaptics {
569573 let style = body["style"] as? String ?? "medium"
570574 triggerHaptic(style: style)
575 } else {
576 rejectCallback(callbackId, error: "Haptics is disabled", code: "CAPABILITY_DISABLED")
571577 }
572578 case "share":
573579 if config.enableShare {
@@ -585,39 +591,62 @@ struct CraftWebView: UIViewRepresentable {
585591 if config.enableCamera {
586592 pendingCallbackId = callbackId
587593 openCamera()
594 } else {
595 rejectCallback(callbackId, error: "Camera is disabled", code: "CAPABILITY_DISABLED")
588596 }
589597 case "pickImage":
590598 if config.enableCamera {
591599 pendingCallbackId = callbackId
592600 pickImage()
601 } else {
602 rejectCallback(callbackId, error: "Camera is disabled", code: "CAPABILITY_DISABLED")
593603 }
594604 case "authenticate":
595605 if config.enableBiometric {
596606 let reason = body["reason"] as? String ?? "Authenticate to continue"
597607 authenticate(reason: reason, callbackId: callbackId)
608 } else {
609 rejectCallback(callbackId, error: "Biometric authentication is disabled", code: "CAPABILITY_DISABLED")
598610 }
599611 case "registerPush":
600612 if config.enablePushNotifications {
601613 registerPushNotifications(callbackId: callbackId)
614 } else {
615 rejectCallback(callbackId, error: "Push notifications are disabled", code: "CAPABILITY_DISABLED")
602616 }
603617 case "secureSet":
604 if config.enableSecureStorage,
605 let key = body["key"] as? String,
606 let value = body["value"] as? String {
607 let success = secureStore(key: key, value: value)
608 resolveCallback(callbackId, result: success)
618 if config.enableSecureStorage {
619 if let key = body["key"] as? String,
620 let value = body["value"] as? String {
621 let success = secureStore(key: key, value: value)
622 resolveCallback(callbackId, result: success)
623 } else {
624 rejectCallback(callbackId, error: "secureSet was called without the values it needs", code: "INVALID_ARGUMENT")
625 }
626 } else {
627 rejectCallback(callbackId, error: "Secure storage is disabled", code: "CAPABILITY_DISABLED")
609628 }
610629 case "secureGet":
611 if config.enableSecureStorage,
612 let key = body["key"] as? String {
613 let value = secureRetrieve(key: key)
614 resolveCallback(callbackId, result: value as Any)
630 if config.enableSecureStorage {
631 if let key = body["key"] as? String {
632 let value = secureRetrieve(key: key)
633 resolveCallback(callbackId, result: value as Any)
634 } else {
635 rejectCallback(callbackId, error: "secureGet was called without the values it needs", code: "INVALID_ARGUMENT")
636 }
637 } else {
638 rejectCallback(callbackId, error: "Secure storage is disabled", code: "CAPABILITY_DISABLED")
615639 }
616640 case "secureRemove":
617 if config.enableSecureStorage,
618 let key = body["key"] as? String {
619 let success = secureRemove(key: key)
620 resolveCallback(callbackId, result: success)
641 if config.enableSecureStorage {
642 if let key = body["key"] as? String {
643 let success = secureRemove(key: key)
644 resolveCallback(callbackId, result: success)
645 } else {
646 rejectCallback(callbackId, error: "secureRemove was called without the values it needs", code: "INVALID_ARGUMENT")
647 }
648 } else {
649 rejectCallback(callbackId, error: "Secure storage is disabled", code: "CAPABILITY_DISABLED")
621650 }
622651 case "secureClear":
623652 if config.enableSecureStorage {
@@ -680,14 +709,22 @@ struct CraftWebView: UIViewRepresentable {
680709 readLocationRecording(callbackId: callbackId)
681710 // Clipboard
682711 case "clipboardWrite":
683 if config.enableClipboard, let text = body["text"] as? String {
684 UIPasteboard.general.string = text
685 resolveCallback(callbackId, result: true)
686 }
712 if config.enableClipboard {
713 if let text = body["text"] as? String {
714 UIPasteboard.general.string = text
715 resolveCallback(callbackId, result: true)
716 } else {
717 rejectCallback(callbackId, error: "clipboardWrite was called without the values it needs", code: "INVALID_ARGUMENT")
718 }
719 } else {
720 rejectCallback(callbackId, error: "Clipboard is disabled", code: "CAPABILITY_DISABLED")
721 }
687722 case "clipboardRead":
688723 if config.enableClipboard {
689724 let text = UIPasteboard.general.string ?? ""
690725 resolveCallback(callbackId, result: text)
726 } else {
727 rejectCallback(callbackId, error: "Clipboard is disabled", code: "CAPABILITY_DISABLED")
691728 }
692729 // Device Info
693730 case "getDeviceInfo":
@@ -735,49 +772,80 @@ struct CraftWebView: UIViewRepresentable {
735772 case "getContacts":
736773 if config.enableContacts {
737774 getContacts(callbackId: callbackId)
775 } else {
776 rejectCallback(callbackId, error: "Contacts access is disabled", code: "CAPABILITY_DISABLED")
738777 }
739778 case "addContact":
740 if config.enableContacts,
741 let contactData = body["contact"] as? [String: Any] {
742 addContact(contactData, callbackId: callbackId)
779 if config.enableContacts {
780 if let contactData = body["contact"] as? [String: Any] {
781 addContact(contactData, callbackId: callbackId)
782 } else {
783 rejectCallback(callbackId, error: "addContact was called without the values it needs", code: "INVALID_ARGUMENT")
784 }
785 } else {
786 rejectCallback(callbackId, error: "Contacts access is disabled", code: "CAPABILITY_DISABLED")
743787 }
744
745788 // MARK: - Calendar
746789 case "getCalendarEvents":
747790 if config.enableCalendar {
748791 let startDate = body["startDate"] as? Double
749792 let endDate = body["endDate"] as? Double
750793 getCalendarEvents(startDate: startDate, endDate: endDate, callbackId: callbackId)
794 } else {
795 rejectCallback(callbackId, error: "Calendar access is disabled", code: "CAPABILITY_DISABLED")
751796 }
752797 case "createCalendarEvent":
753 if config.enableCalendar,
754 let eventData = body["event"] as? [String: Any] {
755 createCalendarEvent(eventData, callbackId: callbackId)
798 if config.enableCalendar {
799 if let eventData = body["event"] as? [String: Any] {
800 createCalendarEvent(eventData, callbackId: callbackId)
801 } else {
802 rejectCallback(callbackId, error: "createCalendarEvent was called without the values it needs", code: "INVALID_ARGUMENT")
803 }
804 } else {
805 rejectCallback(callbackId, error: "Calendar access is disabled", code: "CAPABILITY_DISABLED")
756806 }
757807 case "deleteCalendarEvent":
758 if config.enableCalendar,
759 let eventId = body["eventId"] as? String {
760 deleteCalendarEvent(eventId, callbackId: callbackId)
808 if config.enableCalendar {
809 if let eventId = body["eventId"] as? String {
810 deleteCalendarEvent(eventId, callbackId: callbackId)
811 } else {
812 rejectCallback(callbackId, error: "deleteCalendarEvent was called without the values it needs", code: "INVALID_ARGUMENT")
813 }
814 } else {
815 rejectCallback(callbackId, error: "Calendar access is disabled", code: "CAPABILITY_DISABLED")
761816 }
762
763817 // MARK: - Local Notifications
764818 case "scheduleNotification":
765 if config.enableLocalNotifications,
766 let notifData = body["notification"] as? [String: Any] {
767 scheduleLocalNotification(notifData, callbackId: callbackId)
819 if config.enableLocalNotifications {
820 if let notifData = body["notification"] as? [String: Any] {
821 scheduleLocalNotification(notifData, callbackId: callbackId)
822 } else {
823 rejectCallback(callbackId, error: "scheduleNotification was called without the values it needs", code: "INVALID_ARGUMENT")
824 }
825 } else {
826 rejectCallback(callbackId, error: "Local notifications are disabled", code: "CAPABILITY_DISABLED")
768827 }
769828 case "cancelNotification":
770 if config.enableLocalNotifications,
771 let notifId = body["id"] as? String {
772 cancelLocalNotification(notifId, callbackId: callbackId)
829 if config.enableLocalNotifications {
830 if let notifId = body["id"] as? String {
831 cancelLocalNotification(notifId, callbackId: callbackId)
832 } else {
833 rejectCallback(callbackId, error: "cancelNotification was called without the values it needs", code: "INVALID_ARGUMENT")
834 }
835 } else {
836 rejectCallback(callbackId, error: "Local notifications are disabled", code: "CAPABILITY_DISABLED")
773837 }
774838 case "cancelAllNotifications":
775839 if config.enableLocalNotifications {
776840 cancelAllLocalNotifications(callbackId: callbackId)
841 } else {
842 rejectCallback(callbackId, error: "Local notifications are disabled", code: "CAPABILITY_DISABLED")
777843 }
778844 case "getPendingNotifications":
779845 if config.enableLocalNotifications {
780846 getPendingNotifications(callbackId: callbackId)
847 } else {
848 rejectCallback(callbackId, error: "Local notifications are disabled", code: "CAPABILITY_DISABLED")
781849 }
782850
783851 // MARK: - Deep Links
@@ -785,6 +853,8 @@ struct CraftWebView: UIViewRepresentable {
785853 if config.enableDeepLinks {
786854 // Handler is registered in JS
787855 resolveCallback(callbackId, result: true)
856 } else {
857 rejectCallback(callbackId, error: "Deep links are disabled", code: "CAPABILITY_DISABLED")
788858 }
789859 case "getInitialURL":
790860 if config.enableDeepLinks {
@@ -799,46 +869,73 @@ struct CraftWebView: UIViewRepresentable {
799869 } else {
800870 resolveCallback(callbackId, result: NSNull())
801871 }
872 } else {
873 rejectCallback(callbackId, error: "Deep links are disabled", code: "CAPABILITY_DISABLED")
802874 }
803875
804876 // MARK: - In-App Purchase
805877 case "getProducts":
806 if config.enableInAppPurchase,
807 let productIds = body["productIds"] as? [String] {
808 getProducts(productIds, callbackId: callbackId)
878 if config.enableInAppPurchase {
879 if let productIds = body["productIds"] as? [String] {
880 getProducts(productIds, callbackId: callbackId)
881 } else {
882 rejectCallback(callbackId, error: "getProducts was called without the values it needs", code: "INVALID_ARGUMENT")
883 }
884 } else {
885 rejectCallback(callbackId, error: "In-app purchase is disabled", code: "CAPABILITY_DISABLED")
809886 }
810887 case "purchase":
811 if config.enableInAppPurchase,
812 let productId = body["productId"] as? String {
813 purchaseProduct(productId, callbackId: callbackId)
888 if config.enableInAppPurchase {
889 if let productId = body["productId"] as? String {
890 purchaseProduct(productId, callbackId: callbackId)
891 } else {
892 rejectCallback(callbackId, error: "purchase was called without the values it needs", code: "INVALID_ARGUMENT")
893 }
894 } else {
895 rejectCallback(callbackId, error: "In-app purchase is disabled", code: "CAPABILITY_DISABLED")
814896 }
815897 case "restorePurchases":
816898 if config.enableInAppPurchase {
817899 restorePurchases(callbackId: callbackId)
900 } else {
901 rejectCallback(callbackId, error: "In-app purchase is disabled", code: "CAPABILITY_DISABLED")
818902 }
819903
820904 // MARK: - Keep Awake
821905 case "setKeepAwake":
822 if config.enableKeepAwake,
823 let enabled = body["enabled"] as? Bool {
824 setKeepAwake(enabled, callbackId: callbackId)
906 if config.enableKeepAwake {
907 if let enabled = body["enabled"] as? Bool {
908 setKeepAwake(enabled, callbackId: callbackId)
909 } else {
910 rejectCallback(callbackId, error: "setKeepAwake was called without the values it needs", code: "INVALID_ARGUMENT")
911 }
912 } else {
913 rejectCallback(callbackId, error: "Keep awake is disabled", code: "CAPABILITY_DISABLED")
825914 }
826
827915 // MARK: - Orientation Lock
828916 case "lockOrientation":
829 if config.enableOrientationLock,
830 let orientation = body["orientation"] as? String {
831 lockOrientation(orientation, callbackId: callbackId)
917 if config.enableOrientationLock {
918 if let orientation = body["orientation"] as? String {
919 lockOrientation(orientation, callbackId: callbackId)
920 } else {
921 rejectCallback(callbackId, error: "lockOrientation was called without the values it needs", code: "INVALID_ARGUMENT")
922 }
923 } else {
924 rejectCallback(callbackId, error: "Orientation lock is disabled", code: "CAPABILITY_DISABLED")
832925 }
833926 case "unlockOrientation":
834927 if config.enableOrientationLock {
835928 unlockOrientation(callbackId: callbackId)
929 } else {
930 rejectCallback(callbackId, error: "Orientation lock is disabled", code: "CAPABILITY_DISABLED")
836931 }
837932
838933 // MARK: - QR/Barcode Scanner
839934 case "scanQRCode":
840935 if config.enableQRScanner {
841936 scanQRCode(callbackId: callbackId)
937 } else {
938 rejectCallback(callbackId, error: "QR scanning is disabled", code: "CAPABILITY_DISABLED")
842939 }
843940
844941 // MARK: - File Picker
@@ -846,42 +943,59 @@ struct CraftWebView: UIViewRepresentable {
846943 if config.enableFilePicker {
847944 let types = body["types"] as? [String]
848945 pickFile(types: types, callbackId: callbackId)
946 } else {
947 rejectCallback(callbackId, error: "File picker is disabled", code: "CAPABILITY_DISABLED")
849948 }
850949
851950 // MARK: - File Download
852951 case "downloadFile":
853 if config.enableFileDownload,
854 let url = body["url"] as? String,
855 let filename = body["filename"] as? String {
856 downloadFile(url: url, filename: filename, callbackId: callbackId)
857 }
952 if config.enableFileDownload {
953 if let url = body["url"] as? String, let filename = body["filename"] as? String {
954 downloadFile(url: url, filename: filename, callbackId: callbackId)
955 } else {
956 rejectCallback(callbackId, error: "downloadFile was called without the values it needs", code: "INVALID_ARGUMENT")
957 }
958 } else {
959 rejectCallback(callbackId, error: "File download is disabled", code: "CAPABILITY_DISABLED")
960 }
858961 case "saveFile":
859 if config.enableFileDownload,
860 let data = body["data"] as? String,
861 let filename = body["filename"] as? String {
862 saveFile(data: data, filename: filename, callbackId: callbackId)
863 }
864
962 if config.enableFileDownload {
963 if let data = body["data"] as? String, let filename = body["filename"] as? String {
964 saveFile(data: data, filename: filename, callbackId: callbackId)
965 } else {
966 rejectCallback(callbackId, error: "saveFile was called without the values it needs", code: "INVALID_ARGUMENT")
967 }
968 } else {
969 rejectCallback(callbackId, error: "File download is disabled", code: "CAPABILITY_DISABLED")
970 }
865971 // MARK: - Social Auth
866972 case "signInWithApple":
867973 if config.enableSocialAuth {
868974 signInWithApple(callbackId: callbackId)
975 } else {
976 rejectCallback(callbackId, error: "Social sign-in is disabled", code: "CAPABILITY_DISABLED")
869977 }
870978
871979 // MARK: - Audio Recording
872980 case "startAudioRecording":
873981 if config.enableAudioRecording {
874982 startAudioRecording(callbackId: callbackId)
983 } else {
984 rejectCallback(callbackId, error: "Audio recording is disabled", code: "CAPABILITY_DISABLED")
875985 }
876986 case "stopAudioRecording":
877987 if config.enableAudioRecording {
878988 stopAudioRecording(callbackId: callbackId)
989 } else {
990 rejectCallback(callbackId, error: "Audio recording is disabled", code: "CAPABILITY_DISABLED")
879991 }
880992
881993 // MARK: - Video Recording
882994 case "startVideoRecording":
883995 if config.enableVideoRecording {
884996 startVideoRecording(callbackId: callbackId)
997 } else {
998 rejectCallback(callbackId, error: "Video recording is disabled", code: "CAPABILITY_DISABLED")
885999 }
8861000
8871001 // MARK: - Motion Sensors
@@ -889,6 +1003,8 @@ struct CraftWebView: UIViewRepresentable {
8891003 if config.enableMotionSensors {
8901004 let interval = body["interval"] as? Double ?? 100
8911005 startMotionUpdates(interval: interval, callbackId: callbackId)
1006 } else {
1007 rejectCallback(callbackId, error: "Motion sensors are disabled", code: "CAPABILITY_DISABLED")
8921008 }
8931009 case "stopMotionUpdates":
8941010 stopMotionUpdates()
@@ -896,22 +1012,33 @@ struct CraftWebView: UIViewRepresentable {
8961012
8971013 // MARK: - Local Database
8981014 case "dbExecute":
899 if config.enableLocalDatabase,
900 let sql = body["sql"] as? String {
901 let params = body["params"] as? [Any]
902 dbExecute(sql: sql, params: params, callbackId: callbackId)
1015 if config.enableLocalDatabase {
1016 if let sql = body["sql"] as? String {
1017 let params = body["params"] as? [Any]
1018 dbExecute(sql: sql, params: params, callbackId: callbackId)
1019 } else {
1020 rejectCallback(callbackId, error: "dbExecute was called without the values it needs", code: "INVALID_ARGUMENT")
1021 }
1022 } else {
1023 rejectCallback(callbackId, error: "Local database is disabled", code: "CAPABILITY_DISABLED")
9031024 }
9041025 case "dbQuery":
905 if config.enableLocalDatabase,
906 let sql = body["sql"] as? String {
907 let params = body["params"] as? [Any]
908 dbQuery(sql: sql, params: params, callbackId: callbackId)
1026 if config.enableLocalDatabase {
1027 if let sql = body["sql"] as? String {
1028 let params = body["params"] as? [Any]
1029 dbQuery(sql: sql, params: params, callbackId: callbackId)
1030 } else {
1031 rejectCallback(callbackId, error: "dbQuery was called without the values it needs", code: "INVALID_ARGUMENT")
1032 }
1033 } else {
1034 rejectCallback(callbackId, error: "Local database is disabled", code: "CAPABILITY_DISABLED")
9091035 }
910
9111036 // MARK: - Bluetooth
9121037 case "startBluetoothScan":
9131038 if config.enableBluetooth {
9141039 startBluetoothScan(callbackId: callbackId)
1040 } else {
1041 rejectCallback(callbackId, error: "Bluetooth is disabled", code: "CAPABILITY_DISABLED")
9151042 }
9161043 case "stopBluetoothScan":
9171044 stopBluetoothScan()
@@ -921,6 +1048,8 @@ struct CraftWebView: UIViewRepresentable {
9211048 case "scanNFC":
9221049 if config.enableNFC {
9231050 scanNFC(callbackId: callbackId)
1051 } else {
1052 rejectCallback(callbackId, error: "NFC is disabled", code: "CAPABILITY_DISABLED")
9241053 }
9251054
9261055 // MARK: - Health
@@ -928,13 +1057,20 @@ struct CraftWebView: UIViewRepresentable {
9281057 if config.enableHealthKit {
9291058 let types = body["types"] as? [String] ?? []
9301059 requestHealthAuthorization(types: types, callbackId: callbackId)
1060 } else {
1061 rejectCallback(callbackId, error: "HealthKit is disabled", code: "CAPABILITY_DISABLED")
9311062 }
9321063 case "getHealthData":
933 if config.enableHealthKit,
934 let dataType = body["type"] as? String {
935 let startDate = body["startDate"] as? Double
936 let endDate = body["endDate"] as? Double
937 getHealthData(type: dataType, startDate: startDate, endDate: endDate, callbackId: callbackId)
1064 if config.enableHealthKit {
1065 if let dataType = body["type"] as? String {
1066 let startDate = body["startDate"] as? Double
1067 let endDate = body["endDate"] as? Double
1068 getHealthData(type: dataType, startDate: startDate, endDate: endDate, callbackId: callbackId)
1069 } else {
1070 rejectCallback(callbackId, error: "getHealthData was called without the values it needs", code: "INVALID_ARGUMENT")
1071 }
1072 } else {
1073 rejectCallback(callbackId, error: "HealthKit is disabled", code: "CAPABILITY_DISABLED")
9381074 }
9391075 case "saveHealthWorkout":
9401076 if config.enableHealthKit {
@@ -959,38 +1095,62 @@ struct CraftWebView: UIViewRepresentable {
9591095 case "takeScreenshot":
9601096 if config.enableScreenCapture {
9611097 takeScreenshot(callbackId: callbackId)
1098 } else {
1099 rejectCallback(callbackId, error: "Screen capture is disabled", code: "CAPABILITY_DISABLED")
9621100 }
9631101
9641102 // MARK: - Background Tasks
9651103 case "registerBackgroundTask":
966 if config.enableBackgroundTasks,
967 let taskId = body["taskId"] as? String {
968 registerBackgroundTask(taskId: taskId, callbackId: callbackId)
1104 if config.enableBackgroundTasks {
1105 if let taskId = body["taskId"] as? String {
1106 registerBackgroundTask(taskId: taskId, callbackId: callbackId)
1107 } else {
1108 rejectCallback(callbackId, error: "registerBackgroundTask was called without the values it needs", code: "INVALID_ARGUMENT")
1109 }
1110 } else {
1111 rejectCallback(callbackId, error: "Background tasks are disabled", code: "CAPABILITY_DISABLED")
9691112 }
9701113 case "scheduleBackgroundTask":
971 if config.enableBackgroundTasks,
972 let taskId = body["taskId"] as? String {
973 let delay = body["delay"] as? Double ?? 900 // 15 minutes default
974 let requiresNetwork = body["requiresNetwork"] as? Bool ?? false
975 let requiresCharging = body["requiresCharging"] as? Bool ?? false
976 scheduleBackgroundTask(taskId: taskId, delay: delay, requiresNetwork: requiresNetwork, requiresCharging: requiresCharging, callbackId: callbackId)
1114 if config.enableBackgroundTasks {
1115 if let taskId = body["taskId"] as? String {
1116 let delay = body["delay"] as? Double ?? 900 // 15 minutes default
1117 let requiresNetwork = body["requiresNetwork"] as? Bool ?? false
1118 let requiresCharging = body["requiresCharging"] as? Bool ?? false
1119 scheduleBackgroundTask(taskId: taskId, delay: delay, requiresNetwork: requiresNetwork, requiresCharging: requiresCharging, callbackId: callbackId)
1120 } else {
1121 rejectCallback(callbackId, error: "scheduleBackgroundTask was called without the values it needs", code: "INVALID_ARGUMENT")
1122 }
1123 } else {
1124 rejectCallback(callbackId, error: "Background tasks are disabled", code: "CAPABILITY_DISABLED")
9771125 }
9781126 case "cancelBackgroundTask":
979 if config.enableBackgroundTasks,
980 let taskId = body["taskId"] as? String {
981 cancelBackgroundTask(taskId: taskId, callbackId: callbackId)
1127 if config.enableBackgroundTasks {
1128 if let taskId = body["taskId"] as? String {
1129 cancelBackgroundTask(taskId: taskId, callbackId: callbackId)
1130 } else {
1131 rejectCallback(callbackId, error: "cancelBackgroundTask was called without the values it needs", code: "INVALID_ARGUMENT")
1132 }
1133 } else {
1134 rejectCallback(callbackId, error: "Background tasks are disabled", code: "CAPABILITY_DISABLED")
9821135 }
9831136 case "cancelAllBackgroundTasks":
9841137 if config.enableBackgroundTasks {
9851138 cancelAllBackgroundTasks(callbackId: callbackId)
1139 } else {
1140 rejectCallback(callbackId, error: "Background tasks are disabled", code: "CAPABILITY_DISABLED")
9861141 }
9871142
9881143 // MARK: - PDF Viewer
9891144 case "openPDF":
990 if config.enablePDFViewer,
991 let source = body["source"] as? String {
992 let page = body["page"] as? Int ?? 0
993 openPDF(source: source, page: page, callbackId: callbackId)
1145 if config.enablePDFViewer {
1146 if let source = body["source"] as? String {
1147 let page = body["page"] as? Int ?? 0
1148 openPDF(source: source, page: page, callbackId: callbackId)
1149 } else {
1150 rejectCallback(callbackId, error: "openPDF was called without the values it needs", code: "INVALID_ARGUMENT")
1151 }
1152 } else {
1153 rejectCallback(callbackId, error: "PDF viewing is disabled", code: "CAPABILITY_DISABLED")
9941154 }
9951155 case "closePDF":
9961156 closePDF(callbackId: callbackId)
@@ -1000,6 +1160,8 @@ struct CraftWebView: UIViewRepresentable {
10001160 if config.enableContacts {
10011161 let multiple = body["multiple"] as? Bool ?? false
10021162 pickContact(multiple: multiple, callbackId: callbackId)
1163 } else {
1164 rejectCallback(callbackId, error: "Contacts access is disabled", code: "CAPABILITY_DISABLED")
10031165 }
10041166
10051167 // MARK: - App Shortcuts
@@ -1044,44 +1206,74 @@ struct CraftWebView: UIViewRepresentable {
10441206 if config.enableAR {
10451207 let options = body["options"] as? [String: Any] ?? [:]
10461208 startAR(options: options, callbackId: callbackId)
1209 } else {
1210 rejectCallback(callbackId, error: "AR is disabled", code: "CAPABILITY_DISABLED")
10471211 }
10481212 case "stopAR":
10491213 if config.enableAR {
10501214 stopAR(callbackId: callbackId)
1215 } else {
1216 rejectCallback(callbackId, error: "AR is disabled", code: "CAPABILITY_DISABLED")
10511217 }
10521218 case "placeARObject":
1053 if config.enableAR,
1054 let model = body["model"] as? String {
1055 let position = body["position"] as? [String: Double]
1056 placeARObject(model: model, position: position, callbackId: callbackId)
1219 if config.enableAR {
1220 if let model = body["model"] as? String {
1221 let position = body["position"] as? [String: Double]
1222 placeARObject(model: model, position: position, callbackId: callbackId)
1223 } else {
1224 rejectCallback(callbackId, error: "placeARObject was called without the values it needs", code: "INVALID_ARGUMENT")
1225 }
1226 } else {
1227 rejectCallback(callbackId, error: "AR is disabled", code: "CAPABILITY_DISABLED")
10571228 }
10581229 case "removeARObject":
1059 if config.enableAR,
1060 let objectId = body["objectId"] as? String {
1061 removeARObject(objectId: objectId, callbackId: callbackId)
1230 if config.enableAR {
1231 if let objectId = body["objectId"] as? String {
1232 removeARObject(objectId: objectId, callbackId: callbackId)
1233 } else {
1234 rejectCallback(callbackId, error: "removeARObject was called without the values it needs", code: "INVALID_ARGUMENT")
1235 }
1236 } else {
1237 rejectCallback(callbackId, error: "AR is disabled", code: "CAPABILITY_DISABLED")
10621238 }
10631239 case "getARPlanes":
10641240 if config.enableAR {
10651241 getARPlanes(callbackId: callbackId)
1242 } else {
1243 rejectCallback(callbackId, error: "AR is disabled", code: "CAPABILITY_DISABLED")
10661244 }
10671245
10681246 // MARK: - ML (Core ML / Vision)
10691247 case "classifyImage":
1070 if config.enableMLKit,
1071 let imageBase64 = body["image"] as? String {
1072 classifyImage(imageBase64: imageBase64, callbackId: callbackId)
1248 if config.enableMLKit {
1249 if let imageBase64 = body["image"] as? String {
1250 classifyImage(imageBase64: imageBase64, callbackId: callbackId)
1251 } else {
1252 rejectCallback(callbackId, error: "classifyImage was called without the values it needs", code: "INVALID_ARGUMENT")
1253 }
1254 } else {
1255 rejectCallback(callbackId, error: "Vision is disabled", code: "CAPABILITY_DISABLED")
10731256 }
10741257 case "detectObjects":
1075 if config.enableMLKit,
1076 let imageBase64 = body["image"] as? String {
1077 detectObjects(imageBase64: imageBase64, callbackId: callbackId)
1258 if config.enableMLKit {
1259 if let imageBase64 = body["image"] as? String {
1260 detectObjects(imageBase64: imageBase64, callbackId: callbackId)
1261 } else {
1262 rejectCallback(callbackId, error: "detectObjects was called without the values it needs", code: "INVALID_ARGUMENT")
1263 }
1264 } else {
1265 rejectCallback(callbackId, error: "Vision is disabled", code: "CAPABILITY_DISABLED")
10781266 }
10791267 case "recognizeText":
1080 if config.enableMLKit,
1081 let imageBase64 = body["image"] as? String {
1082 recognizeText(imageBase64: imageBase64, callbackId: callbackId)
1268 if config.enableMLKit {
1269 if let imageBase64 = body["image"] as? String {
1270 recognizeText(imageBase64: imageBase64, callbackId: callbackId)
1271 } else {
1272 rejectCallback(callbackId, error: "recognizeText was called without the values it needs", code: "INVALID_ARGUMENT")
1273 }
1274 } else {
1275 rejectCallback(callbackId, error: "Vision is disabled", code: "CAPABILITY_DISABLED")
10831276 }
1084
10851277 // MARK: - Widget
10861278 case "updateWidget":
10871279 if let data = body["data"] as? [String: Any] {