|
867 | 867 | };
|
868 | 868 | }
|
869 | 869 |
|
| 870 | + // ../messaging/lib/android.js |
| 871 | + var AndroidMessagingTransport = class { |
| 872 | + /** |
| 873 | + * @param {AndroidMessagingConfig} config |
| 874 | + * @param {import('../index.js').MessagingContext} messagingContext |
| 875 | + * @internal |
| 876 | + */ |
| 877 | + constructor(config, messagingContext) { |
| 878 | + this.messagingContext = messagingContext; |
| 879 | + this.config = config; |
| 880 | + } |
| 881 | + /** |
| 882 | + * @param {import('../index.js').NotificationMessage} msg |
| 883 | + */ |
| 884 | + notify(msg) { |
| 885 | + try { |
| 886 | + this.config.sendMessageThrows?.(JSON.stringify(msg)); |
| 887 | + } catch (e) { |
| 888 | + console.error(".notify failed", e); |
| 889 | + } |
| 890 | + } |
| 891 | + /** |
| 892 | + * @param {import('../index.js').RequestMessage} msg |
| 893 | + * @return {Promise<any>} |
| 894 | + */ |
| 895 | + request(msg) { |
| 896 | + return new Promise((resolve, reject) => { |
| 897 | + const unsub = this.config.subscribe(msg.id, handler); |
| 898 | + try { |
| 899 | + this.config.sendMessageThrows?.(JSON.stringify(msg)); |
| 900 | + } catch (e) { |
| 901 | + unsub(); |
| 902 | + reject(new Error("request failed to send: " + e.message || "unknown error")); |
| 903 | + } |
| 904 | + function handler(data) { |
| 905 | + if (isResponseFor(msg, data)) { |
| 906 | + if (data.result) { |
| 907 | + resolve(data.result || {}); |
| 908 | + return unsub(); |
| 909 | + } |
| 910 | + if (data.error) { |
| 911 | + reject(new Error(data.error.message)); |
| 912 | + return unsub(); |
| 913 | + } |
| 914 | + unsub(); |
| 915 | + throw new Error("unreachable: must have `result` or `error` key by this point"); |
| 916 | + } |
| 917 | + } |
| 918 | + }); |
| 919 | + } |
| 920 | + /** |
| 921 | + * @param {import('../index.js').Subscription} msg |
| 922 | + * @param {(value: unknown | undefined) => void} callback |
| 923 | + */ |
| 924 | + subscribe(msg, callback) { |
| 925 | + const unsub = this.config.subscribe(msg.subscriptionName, (data) => { |
| 926 | + if (isSubscriptionEventFor(msg, data)) { |
| 927 | + callback(data.params || {}); |
| 928 | + } |
| 929 | + }); |
| 930 | + return () => { |
| 931 | + unsub(); |
| 932 | + }; |
| 933 | + } |
| 934 | + }; |
| 935 | + var AndroidMessagingConfig = class { |
| 936 | + /** @type {(json: string, secret: string) => void} */ |
| 937 | + _capturedHandler; |
| 938 | + /** |
| 939 | + * @param {object} params |
| 940 | + * @param {Record<string, any>} params.target |
| 941 | + * @param {boolean} params.debug |
| 942 | + * @param {string} params.secret - a secret to ensure that messages are only |
| 943 | + * processed by the correct handler |
| 944 | + * @param {string} params.javascriptInterface - the name of the javascript interface |
| 945 | + * registered on the native side |
| 946 | + * @param {string} params.messageCallback - the name of the callback that the native |
| 947 | + * side will use to send messages back to the javascript side |
| 948 | + */ |
| 949 | + constructor(params) { |
| 950 | + this.target = params.target; |
| 951 | + this.debug = params.debug; |
| 952 | + this.javascriptInterface = params.javascriptInterface; |
| 953 | + this.secret = params.secret; |
| 954 | + this.messageCallback = params.messageCallback; |
| 955 | + this.listeners = new globalThis.Map(); |
| 956 | + this._captureGlobalHandler(); |
| 957 | + this._assignHandlerMethod(); |
| 958 | + } |
| 959 | + /** |
| 960 | + * The transport can call this to transmit a JSON payload along with a secret |
| 961 | + * to the native Android handler. |
| 962 | + * |
| 963 | + * Note: This can throw - it's up to the transport to handle the error. |
| 964 | + * |
| 965 | + * @type {(json: string) => void} |
| 966 | + * @throws |
| 967 | + * @internal |
| 968 | + */ |
| 969 | + sendMessageThrows(json) { |
| 970 | + this._capturedHandler(json, this.secret); |
| 971 | + } |
| 972 | + /** |
| 973 | + * A subscription on Android is just a named listener. All messages from |
| 974 | + * android -> are delivered through a single function, and this mapping is used |
| 975 | + * to route the messages to the correct listener. |
| 976 | + * |
| 977 | + * Note: Use this to implement request->response by unsubscribing after the first |
| 978 | + * response. |
| 979 | + * |
| 980 | + * @param {string} id |
| 981 | + * @param {(msg: MessageResponse | SubscriptionEvent) => void} callback |
| 982 | + * @returns {() => void} |
| 983 | + * @internal |
| 984 | + */ |
| 985 | + subscribe(id, callback) { |
| 986 | + this.listeners.set(id, callback); |
| 987 | + return () => { |
| 988 | + this.listeners.delete(id); |
| 989 | + }; |
| 990 | + } |
| 991 | + /** |
| 992 | + * Accept incoming messages and try to deliver it to a registered listener. |
| 993 | + * |
| 994 | + * This code is defensive to prevent any single handler from affecting another if |
| 995 | + * it throws (producer interference). |
| 996 | + * |
| 997 | + * @param {MessageResponse | SubscriptionEvent} payload |
| 998 | + * @internal |
| 999 | + */ |
| 1000 | + _dispatch(payload) { |
| 1001 | + if (!payload) |
| 1002 | + return this._log("no response"); |
| 1003 | + if ("id" in payload) { |
| 1004 | + if (this.listeners.has(payload.id)) { |
| 1005 | + this._tryCatch(() => this.listeners.get(payload.id)?.(payload)); |
| 1006 | + } else { |
| 1007 | + this._log("no listeners for ", payload); |
| 1008 | + } |
| 1009 | + } |
| 1010 | + if ("subscriptionName" in payload) { |
| 1011 | + if (this.listeners.has(payload.subscriptionName)) { |
| 1012 | + this._tryCatch(() => this.listeners.get(payload.subscriptionName)?.(payload)); |
| 1013 | + } else { |
| 1014 | + this._log("no subscription listeners for ", payload); |
| 1015 | + } |
| 1016 | + } |
| 1017 | + } |
| 1018 | + /** |
| 1019 | + * |
| 1020 | + * @param {(...args: any[]) => any} fn |
| 1021 | + * @param {string} [context] |
| 1022 | + */ |
| 1023 | + _tryCatch(fn, context = "none") { |
| 1024 | + try { |
| 1025 | + return fn(); |
| 1026 | + } catch (e) { |
| 1027 | + if (this.debug) { |
| 1028 | + console.error("AndroidMessagingConfig error:", context); |
| 1029 | + console.error(e); |
| 1030 | + } |
| 1031 | + } |
| 1032 | + } |
| 1033 | + /** |
| 1034 | + * @param {...any} args |
| 1035 | + */ |
| 1036 | + _log(...args) { |
| 1037 | + if (this.debug) { |
| 1038 | + console.log("AndroidMessagingConfig", ...args); |
| 1039 | + } |
| 1040 | + } |
| 1041 | + /** |
| 1042 | + * Capture the global handler and remove it from the global object. |
| 1043 | + */ |
| 1044 | + _captureGlobalHandler() { |
| 1045 | + const { target, javascriptInterface } = this; |
| 1046 | + if (Object.prototype.hasOwnProperty.call(target, javascriptInterface)) { |
| 1047 | + this._capturedHandler = target[javascriptInterface].process.bind(target[javascriptInterface]); |
| 1048 | + delete target[javascriptInterface]; |
| 1049 | + } else { |
| 1050 | + this._capturedHandler = () => { |
| 1051 | + this._log("Android messaging interface not available", javascriptInterface); |
| 1052 | + }; |
| 1053 | + } |
| 1054 | + } |
| 1055 | + /** |
| 1056 | + * Assign the incoming handler method to the global object. |
| 1057 | + * This is the method that Android will call to deliver messages. |
| 1058 | + */ |
| 1059 | + _assignHandlerMethod() { |
| 1060 | + const responseHandler = (providedSecret, response) => { |
| 1061 | + if (providedSecret === this.secret) { |
| 1062 | + this._dispatch(response); |
| 1063 | + } |
| 1064 | + }; |
| 1065 | + Object.defineProperty(this.target, this.messageCallback, { |
| 1066 | + value: responseHandler |
| 1067 | + }); |
| 1068 | + } |
| 1069 | + }; |
| 1070 | + |
870 | 1071 | // ../messaging/index.js
|
871 | 1072 | var MessagingContext = class {
|
872 | 1073 | /**
|
|
885 | 1086 | var Messaging = class {
|
886 | 1087 | /**
|
887 | 1088 | * @param {MessagingContext} messagingContext
|
888 |
| - * @param {WebkitMessagingConfig | WindowsMessagingConfig | TestTransportConfig} config |
| 1089 | + * @param {WebkitMessagingConfig | WindowsMessagingConfig | AndroidMessagingConfig | TestTransportConfig} config |
889 | 1090 | */
|
890 | 1091 | constructor(messagingContext, config) {
|
891 | 1092 | this.messagingContext = messagingContext;
|
|
986 | 1187 | if (config instanceof WindowsMessagingConfig) {
|
987 | 1188 | return new WindowsMessagingTransport(config, messagingContext);
|
988 | 1189 | }
|
| 1190 | + if (config instanceof AndroidMessagingConfig) { |
| 1191 | + return new AndroidMessagingTransport(config, messagingContext); |
| 1192 | + } |
989 | 1193 | if (config instanceof TestTransportConfig) {
|
990 | 1194 | return new TestTransport(config, messagingContext);
|
991 | 1195 | }
|
|
0 commit comments