also looking at this
fix(ios): no gated dispatcher arm can hang the page any more (58 → 0)
#120
1 file
+298
-106
| @@ -561,13 +561,19 @@ struct CraftWebView: UIViewRepresentable { | ||
| 561 | 561 | func dispatch(action: String, body: [String: Any], callbackId: String?) { |
| 562 | 562 | switch action { |
| 563 | 563 | 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 | } | |
| 565 | 569 | case "stopListening": |
| 566 | 570 | stopSpeechRecognition() |
| 567 | 571 | case "haptic": |
| 568 | 572 | if config.enableHaptics { |
| 569 | 573 | let style = body["style"] as? String ?? "medium" |
| 570 | 574 | triggerHaptic(style: style) |
| 575 | } else { | |
| 576 | rejectCallback(callbackId, error: "Haptics is disabled", code: "CAPABILITY_DISABLED") | |
| 571 | 577 | } |
| 572 | 578 | case "share": |
| 573 | 579 | if config.enableShare { |
| @@ -585,39 +591,62 @@ struct CraftWebView: UIViewRepresentable { | ||
| 585 | 591 | if config.enableCamera { |
| 586 | 592 | pendingCallbackId = callbackId |
| 587 | 593 | openCamera() |
| 594 | } else { | |
| 595 | rejectCallback(callbackId, error: "Camera is disabled", code: "CAPABILITY_DISABLED") | |
| 588 | 596 | } |
| 589 | 597 | case "pickImage": |
| 590 | 598 | if config.enableCamera { |
| 591 | 599 | pendingCallbackId = callbackId |
| 592 | 600 | pickImage() |
| 601 | } else { | |
| 602 | rejectCallback(callbackId, error: "Camera is disabled", code: "CAPABILITY_DISABLED") | |
| 593 | 603 | } |
| 594 | 604 | case "authenticate": |
| 595 | 605 | if config.enableBiometric { |
| 596 | 606 | let reason = body["reason"] as? String ?? "Authenticate to continue" |
| 597 | 607 | authenticate(reason: reason, callbackId: callbackId) |
| 608 | } else { | |
| 609 | rejectCallback(callbackId, error: "Biometric authentication is disabled", code: "CAPABILITY_DISABLED") | |
| 598 | 610 | } |
| 599 | 611 | case "registerPush": |
| 600 | 612 | if config.enablePushNotifications { |
| 601 | 613 | registerPushNotifications(callbackId: callbackId) |
| 614 | } else { | |
| 615 | rejectCallback(callbackId, error: "Push notifications are disabled", code: "CAPABILITY_DISABLED") | |
| 602 | 616 | } |
| 603 | 617 | 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") | |
| 609 | 628 | } |
| 610 | 629 | 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") | |
| 615 | 639 | } |
| 616 | 640 | 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") | |
| 621 | 650 | } |
| 622 | 651 | case "secureClear": |
| 623 | 652 | if config.enableSecureStorage { |
| @@ -680,14 +709,22 @@ struct CraftWebView: UIViewRepresentable { | ||
| 680 | 709 | readLocationRecording(callbackId: callbackId) |
| 681 | 710 | // Clipboard |
| 682 | 711 | 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 | } | |
| 687 | 722 | case "clipboardRead": |
| 688 | 723 | if config.enableClipboard { |
| 689 | 724 | let text = UIPasteboard.general.string ?? "" |
| 690 | 725 | resolveCallback(callbackId, result: text) |
| 726 | } else { | |
| 727 | rejectCallback(callbackId, error: "Clipboard is disabled", code: "CAPABILITY_DISABLED") | |
| 691 | 728 | } |
| 692 | 729 | // Device Info |
| 693 | 730 | case "getDeviceInfo": |
| @@ -735,49 +772,80 @@ struct CraftWebView: UIViewRepresentable { | ||
| 735 | 772 | case "getContacts": |
| 736 | 773 | if config.enableContacts { |
| 737 | 774 | getContacts(callbackId: callbackId) |
| 775 | } else { | |
| 776 | rejectCallback(callbackId, error: "Contacts access is disabled", code: "CAPABILITY_DISABLED") | |
| 738 | 777 | } |
| 739 | 778 | 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") | |
| 743 | 787 | } |
| 744 | ||
| 745 | 788 | // MARK: - Calendar |
| 746 | 789 | case "getCalendarEvents": |
| 747 | 790 | if config.enableCalendar { |
| 748 | 791 | let startDate = body["startDate"] as? Double |
| 749 | 792 | let endDate = body["endDate"] as? Double |
| 750 | 793 | getCalendarEvents(startDate: startDate, endDate: endDate, callbackId: callbackId) |
| 794 | } else { | |
| 795 | rejectCallback(callbackId, error: "Calendar access is disabled", code: "CAPABILITY_DISABLED") | |
| 751 | 796 | } |
| 752 | 797 | 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") | |
| 756 | 806 | } |
| 757 | 807 | 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") | |
| 761 | 816 | } |
| 762 | ||
| 763 | 817 | // MARK: - Local Notifications |
| 764 | 818 | 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") | |
| 768 | 827 | } |
| 769 | 828 | 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") | |
| 773 | 837 | } |
| 774 | 838 | case "cancelAllNotifications": |
| 775 | 839 | if config.enableLocalNotifications { |
| 776 | 840 | cancelAllLocalNotifications(callbackId: callbackId) |
| 841 | } else { | |
| 842 | rejectCallback(callbackId, error: "Local notifications are disabled", code: "CAPABILITY_DISABLED") | |
| 777 | 843 | } |
| 778 | 844 | case "getPendingNotifications": |
| 779 | 845 | if config.enableLocalNotifications { |
| 780 | 846 | getPendingNotifications(callbackId: callbackId) |
| 847 | } else { | |
| 848 | rejectCallback(callbackId, error: "Local notifications are disabled", code: "CAPABILITY_DISABLED") | |
| 781 | 849 | } |
| 782 | 850 | |
| 783 | 851 | // MARK: - Deep Links |
| @@ -785,6 +853,8 @@ struct CraftWebView: UIViewRepresentable { | ||
| 785 | 853 | if config.enableDeepLinks { |
| 786 | 854 | // Handler is registered in JS |
| 787 | 855 | resolveCallback(callbackId, result: true) |
| 856 | } else { | |
| 857 | rejectCallback(callbackId, error: "Deep links are disabled", code: "CAPABILITY_DISABLED") | |
| 788 | 858 | } |
| 789 | 859 | case "getInitialURL": |
| 790 | 860 | if config.enableDeepLinks { |
| @@ -799,46 +869,73 @@ struct CraftWebView: UIViewRepresentable { | ||
| 799 | 869 | } else { |
| 800 | 870 | resolveCallback(callbackId, result: NSNull()) |
| 801 | 871 | } |
| 872 | } else { | |
| 873 | rejectCallback(callbackId, error: "Deep links are disabled", code: "CAPABILITY_DISABLED") | |
| 802 | 874 | } |
| 803 | 875 | |
| 804 | 876 | // MARK: - In-App Purchase |
| 805 | 877 | 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") | |
| 809 | 886 | } |
| 810 | 887 | 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") | |
| 814 | 896 | } |
| 815 | 897 | case "restorePurchases": |
| 816 | 898 | if config.enableInAppPurchase { |
| 817 | 899 | restorePurchases(callbackId: callbackId) |
| 900 | } else { | |
| 901 | rejectCallback(callbackId, error: "In-app purchase is disabled", code: "CAPABILITY_DISABLED") | |
| 818 | 902 | } |
| 819 | 903 | |
| 820 | 904 | // MARK: - Keep Awake |
| 821 | 905 | 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") | |
| 825 | 914 | } |
| 826 | ||
| 827 | 915 | // MARK: - Orientation Lock |
| 828 | 916 | 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") | |
| 832 | 925 | } |
| 833 | 926 | case "unlockOrientation": |
| 834 | 927 | if config.enableOrientationLock { |
| 835 | 928 | unlockOrientation(callbackId: callbackId) |
| 929 | } else { | |
| 930 | rejectCallback(callbackId, error: "Orientation lock is disabled", code: "CAPABILITY_DISABLED") | |
| 836 | 931 | } |
| 837 | 932 | |
| 838 | 933 | // MARK: - QR/Barcode Scanner |
| 839 | 934 | case "scanQRCode": |
| 840 | 935 | if config.enableQRScanner { |
| 841 | 936 | scanQRCode(callbackId: callbackId) |
| 937 | } else { | |
| 938 | rejectCallback(callbackId, error: "QR scanning is disabled", code: "CAPABILITY_DISABLED") | |
| 842 | 939 | } |
| 843 | 940 | |
| 844 | 941 | // MARK: - File Picker |
| @@ -846,42 +943,59 @@ struct CraftWebView: UIViewRepresentable { | ||
| 846 | 943 | if config.enableFilePicker { |
| 847 | 944 | let types = body["types"] as? [String] |
| 848 | 945 | pickFile(types: types, callbackId: callbackId) |
| 946 | } else { | |
| 947 | rejectCallback(callbackId, error: "File picker is disabled", code: "CAPABILITY_DISABLED") | |
| 849 | 948 | } |
| 850 | 949 | |
| 851 | 950 | // MARK: - File Download |
| 852 | 951 | 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 | } | |
| 858 | 961 | 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 | } | |
| 865 | 971 | // MARK: - Social Auth |
| 866 | 972 | case "signInWithApple": |
| 867 | 973 | if config.enableSocialAuth { |
| 868 | 974 | signInWithApple(callbackId: callbackId) |
| 975 | } else { | |
| 976 | rejectCallback(callbackId, error: "Social sign-in is disabled", code: "CAPABILITY_DISABLED") | |
| 869 | 977 | } |
| 870 | 978 | |
| 871 | 979 | // MARK: - Audio Recording |
| 872 | 980 | case "startAudioRecording": |
| 873 | 981 | if config.enableAudioRecording { |
| 874 | 982 | startAudioRecording(callbackId: callbackId) |
| 983 | } else { | |
| 984 | rejectCallback(callbackId, error: "Audio recording is disabled", code: "CAPABILITY_DISABLED") | |
| 875 | 985 | } |
| 876 | 986 | case "stopAudioRecording": |
| 877 | 987 | if config.enableAudioRecording { |
| 878 | 988 | stopAudioRecording(callbackId: callbackId) |
| 989 | } else { | |
| 990 | rejectCallback(callbackId, error: "Audio recording is disabled", code: "CAPABILITY_DISABLED") | |
| 879 | 991 | } |
| 880 | 992 | |
| 881 | 993 | // MARK: - Video Recording |
| 882 | 994 | case "startVideoRecording": |
| 883 | 995 | if config.enableVideoRecording { |
| 884 | 996 | startVideoRecording(callbackId: callbackId) |
| 997 | } else { | |
| 998 | rejectCallback(callbackId, error: "Video recording is disabled", code: "CAPABILITY_DISABLED") | |
| 885 | 999 | } |
| 886 | 1000 | |
| 887 | 1001 | // MARK: - Motion Sensors |
| @@ -889,6 +1003,8 @@ struct CraftWebView: UIViewRepresentable { | ||
| 889 | 1003 | if config.enableMotionSensors { |
| 890 | 1004 | let interval = body["interval"] as? Double ?? 100 |
| 891 | 1005 | startMotionUpdates(interval: interval, callbackId: callbackId) |
| 1006 | } else { | |
| 1007 | rejectCallback(callbackId, error: "Motion sensors are disabled", code: "CAPABILITY_DISABLED") | |
| 892 | 1008 | } |
| 893 | 1009 | case "stopMotionUpdates": |
| 894 | 1010 | stopMotionUpdates() |
| @@ -896,22 +1012,33 @@ struct CraftWebView: UIViewRepresentable { | ||
| 896 | 1012 | |
| 897 | 1013 | // MARK: - Local Database |
| 898 | 1014 | 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") | |
| 903 | 1024 | } |
| 904 | 1025 | 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") | |
| 909 | 1035 | } |
| 910 | ||
| 911 | 1036 | // MARK: - Bluetooth |
| 912 | 1037 | case "startBluetoothScan": |
| 913 | 1038 | if config.enableBluetooth { |
| 914 | 1039 | startBluetoothScan(callbackId: callbackId) |
| 1040 | } else { | |
| 1041 | rejectCallback(callbackId, error: "Bluetooth is disabled", code: "CAPABILITY_DISABLED") | |
| 915 | 1042 | } |
| 916 | 1043 | case "stopBluetoothScan": |
| 917 | 1044 | stopBluetoothScan() |
| @@ -921,6 +1048,8 @@ struct CraftWebView: UIViewRepresentable { | ||
| 921 | 1048 | case "scanNFC": |
| 922 | 1049 | if config.enableNFC { |
| 923 | 1050 | scanNFC(callbackId: callbackId) |
| 1051 | } else { | |
| 1052 | rejectCallback(callbackId, error: "NFC is disabled", code: "CAPABILITY_DISABLED") | |
| 924 | 1053 | } |
| 925 | 1054 | |
| 926 | 1055 | // MARK: - Health |
| @@ -928,13 +1057,20 @@ struct CraftWebView: UIViewRepresentable { | ||
| 928 | 1057 | if config.enableHealthKit { |
| 929 | 1058 | let types = body["types"] as? [String] ?? [] |
| 930 | 1059 | requestHealthAuthorization(types: types, callbackId: callbackId) |
| 1060 | } else { | |
| 1061 | rejectCallback(callbackId, error: "HealthKit is disabled", code: "CAPABILITY_DISABLED") | |
| 931 | 1062 | } |
| 932 | 1063 | 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") | |
| 938 | 1074 | } |
| 939 | 1075 | case "saveHealthWorkout": |
| 940 | 1076 | if config.enableHealthKit { |
| @@ -959,38 +1095,62 @@ struct CraftWebView: UIViewRepresentable { | ||
| 959 | 1095 | case "takeScreenshot": |
| 960 | 1096 | if config.enableScreenCapture { |
| 961 | 1097 | takeScreenshot(callbackId: callbackId) |
| 1098 | } else { | |
| 1099 | rejectCallback(callbackId, error: "Screen capture is disabled", code: "CAPABILITY_DISABLED") | |
| 962 | 1100 | } |
| 963 | 1101 | |
| 964 | 1102 | // MARK: - Background Tasks |
| 965 | 1103 | 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") | |
| 969 | 1112 | } |
| 970 | 1113 | 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") | |
| 977 | 1125 | } |
| 978 | 1126 | 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") | |
| 982 | 1135 | } |
| 983 | 1136 | case "cancelAllBackgroundTasks": |
| 984 | 1137 | if config.enableBackgroundTasks { |
| 985 | 1138 | cancelAllBackgroundTasks(callbackId: callbackId) |
| 1139 | } else { | |
| 1140 | rejectCallback(callbackId, error: "Background tasks are disabled", code: "CAPABILITY_DISABLED") | |
| 986 | 1141 | } |
| 987 | 1142 | |
| 988 | 1143 | // MARK: - PDF Viewer |
| 989 | 1144 | 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") | |
| 994 | 1154 | } |
| 995 | 1155 | case "closePDF": |
| 996 | 1156 | closePDF(callbackId: callbackId) |
| @@ -1000,6 +1160,8 @@ struct CraftWebView: UIViewRepresentable { | ||
| 1000 | 1160 | if config.enableContacts { |
| 1001 | 1161 | let multiple = body["multiple"] as? Bool ?? false |
| 1002 | 1162 | pickContact(multiple: multiple, callbackId: callbackId) |
| 1163 | } else { | |
| 1164 | rejectCallback(callbackId, error: "Contacts access is disabled", code: "CAPABILITY_DISABLED") | |
| 1003 | 1165 | } |
| 1004 | 1166 | |
| 1005 | 1167 | // MARK: - App Shortcuts |
| @@ -1044,44 +1206,74 @@ struct CraftWebView: UIViewRepresentable { | ||
| 1044 | 1206 | if config.enableAR { |
| 1045 | 1207 | let options = body["options"] as? [String: Any] ?? [:] |
| 1046 | 1208 | startAR(options: options, callbackId: callbackId) |
| 1209 | } else { | |
| 1210 | rejectCallback(callbackId, error: "AR is disabled", code: "CAPABILITY_DISABLED") | |
| 1047 | 1211 | } |
| 1048 | 1212 | case "stopAR": |
| 1049 | 1213 | if config.enableAR { |
| 1050 | 1214 | stopAR(callbackId: callbackId) |
| 1215 | } else { | |
| 1216 | rejectCallback(callbackId, error: "AR is disabled", code: "CAPABILITY_DISABLED") | |
| 1051 | 1217 | } |
| 1052 | 1218 | 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") | |
| 1057 | 1228 | } |
| 1058 | 1229 | 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") | |
| 1062 | 1238 | } |
| 1063 | 1239 | case "getARPlanes": |
| 1064 | 1240 | if config.enableAR { |
| 1065 | 1241 | getARPlanes(callbackId: callbackId) |
| 1242 | } else { | |
| 1243 | rejectCallback(callbackId, error: "AR is disabled", code: "CAPABILITY_DISABLED") | |
| 1066 | 1244 | } |
| 1067 | 1245 | |
| 1068 | 1246 | // MARK: - ML (Core ML / Vision) |
| 1069 | 1247 | 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") | |
| 1073 | 1256 | } |
| 1074 | 1257 | 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") | |
| 1078 | 1266 | } |
| 1079 | 1267 | 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") | |
| 1083 | 1276 | } |
| 1084 | ||
| 1085 | 1277 | // MARK: - Widget |
| 1086 | 1278 | case "updateWidget": |
| 1087 | 1279 | if let data = body["data"] as? [String: Any] { |