UNPKG

react-native-google-nearby-messages

Version:

An async Google Nearby Messages API Wrapper for React Native (Android & iOS)

345 lines (344 loc) 14.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.useNearbyErrorCallback = exports.useNearbySearch = exports.useNearbySubscription = exports.useNearbyPublication = exports.addOnErrorListener = exports.checkBluetoothAvailability = exports.checkBluetoothPermission = exports.unpublish = exports.publish = exports.unsubscribe = exports.subscribe = exports.disconnect = exports.connect = void 0; const react_native_1 = require("react-native"); const react_1 = require("react"); const { GoogleNearbyMessages } = react_native_1.NativeModules; const nearbyEventEmitter = new react_native_1.NativeEventEmitter(GoogleNearbyMessages); /** * Initialize and connect the Google Nearby Messages API * @param config The Nearby API configuration object to use. Default: `{ discoveryModes: ['broadcast', 'scan'], discoveryMediums: ['ble'] }` * @returns An unsubscriber function to disconnect the Google Nearby Messages API * @example * const disconnect = await connect(API_KEY, ['broadcast', 'scan'], ['ble', 'audio']); * // ... * disconnect(); */ async function connect(config) { let { apiKey, discoveryMediums, discoveryModes } = config; if (discoveryModes == null || discoveryModes.length === 0) { discoveryModes = ['broadcast', 'scan']; } if (discoveryMediums == null || discoveryMediums.length === 0) { discoveryMediums = ['ble']; } if (react_native_1.Platform.OS === 'ios') { if (apiKey == null) throw new Error('API Key is required on iOS!'); await GoogleNearbyMessages.connect(apiKey, discoveryModes, discoveryMediums); } else { await GoogleNearbyMessages.connect(discoveryModes, discoveryMediums); } return () => GoogleNearbyMessages.disconnect(); } exports.connect = connect; /** * Disconnect the Google Nearby Messages API. Also removes any existing subscriptions or publications. */ function disconnect() { GoogleNearbyMessages.disconnect(); } exports.disconnect = disconnect; /** * Subscribe to nearby message events. Use onMessageFound and onMessageLost to receive callbacks for found and lost messages. Always call unsubscribe() to stop publishing. * @param onMessageFound (Optional) A function to call when a new message has been found * @param onMessageLost (Optional) A function to call when an existing message has been lost * @returns A function to unsubscribe the subscription and remove the event emitters if supplied. * @example * const unsubscribe = await subscribe( * (m) => console.log(`found: ${m}`), * (m) => console.log(`lost: ${m}`) * ); * // ... * unsubscribe(); */ async function subscribe(onMessageFound, onMessageLost) { await GoogleNearbyMessages.subscribe(); const onMessageFoundUnsubscribe = onMessageFound ? onEvent('MESSAGE_FOUND', onMessageFound) : undefined; const onMessageLostUnsubscribe = onMessageLost ? onEvent('MESSAGE_LOST', onMessageLost) : undefined; return () => { if (onMessageFoundUnsubscribe) onMessageFoundUnsubscribe(); if (onMessageLostUnsubscribe) onMessageLostUnsubscribe(); unsubscribe(); }; } exports.subscribe = subscribe; /** * Unsubscribe the current subscription. Also removes all event listeners for `MESSAGE_FOUND` and `MESSAGE_LOST`. */ function unsubscribe() { GoogleNearbyMessages.unsubscribe(); removeAllListeners('MESSAGE_FOUND'); removeAllListeners('MESSAGE_LOST'); } exports.unsubscribe = unsubscribe; /** * Publish/Broadcast a new message. Always call unpublish() to stop publishing. * @param message The message to broadcast. * @returns An unsubscriber function to unpublish the currently published message. * @example * const unpublish = await publish('test'); * // ... * await unpublish(); */ async function publish(message) { await GoogleNearbyMessages.publish(message); return () => unpublish(); } exports.publish = publish; /** * Stop publishing the last message. Can only call after @see publish has been called. */ function unpublish() { return GoogleNearbyMessages.unpublish(); } exports.unpublish = unpublish; /** * Checks if the app is allowed to use the Bluetooth API. * * **On Android**, this function checks if both `BLUETOOTH` and `BLUETOOTH_ADMIN` permissions are granted in the ContextCompat. * * **On iOS**, this function checks if the User has given Bluetooth Permission using the CoreBluetooth API (`CBManager.authorization`). If not yet asked, a "grant permission?" dialog will pop up. */ function checkBluetoothPermission() { return GoogleNearbyMessages.checkBluetoothPermission(); } exports.checkBluetoothPermission = checkBluetoothPermission; /** * Checks if the device supports the Bluetooth operations required by Google Nearby Messages (BLE Publishing and Subscribing) * * **On Android**, this function checks if a `BluetoothAdapter` can be found, and if the Google Play Services are available (required for Google Nearby API). * * **On iOS**, this function powers on the `CBCentralManager` and returns `true` if it was successfully turned on. If no callback was sent within `10` seconds, a timeout error will be thrown. */ function checkBluetoothAvailability() { return GoogleNearbyMessages.checkBluetoothAvailability(); } exports.checkBluetoothAvailability = checkBluetoothAvailability; /** * Subscribe to any errors. * @param callback The function to call when an error occurs. `kind` is the Error Type. e.g.: User turns Bluetooth off, callback gets called with ('BLUETOOTH_ERROR', "Bluetooth is powered off/unavailable!"). */ function addOnErrorListener(callback) { const listeners = [ onErrorEvent('BLUETOOTH_ERROR', (m) => callback('BLUETOOTH_ERROR', m)), onErrorEvent('PERMISSION_ERROR', (m) => callback('PERMISSION_ERROR', m)), onErrorEvent('MESSAGE_NO_DATA_ERROR', (m) => callback('MESSAGE_NO_DATA_ERROR', m)), ]; return () => { listeners.map(l => l()); }; } exports.addOnErrorListener = addOnErrorListener; function onEvent(event, callback) { const subscription = nearbyEventEmitter.addListener(event, (data) => callback(data.message)); return () => subscription.remove(); } function onErrorEvent(event, callback) { const subscription = nearbyEventEmitter.addListener(event, (data) => callback(data.message)); return () => subscription.remove(); } function removeAllListeners(event) { nearbyEventEmitter.removeAllListeners(event); } /** * Publish a simple message and return the current status of the nearby API. * * Also calls `checkBluetoothAvailability()` and `checkBluetoothPermission()`. * @param config The Nearby API configuration object to use. **Warning: Use `useMemo(..)` for the Object, otherwise you get an infinite loop of re-renders!** * @param message The message to publish * @returns The current status of the Nearby API * @example * export default function App() { * const nearbyConfig = useMemo<NearbyConfig>(() => ({ apiKey: GOOGLE_API_KEY }), []); * const nearbyStatus = useNearbyPublication(nearbyConfig, 'Hello from Nearby!'); * // ... * } */ function useNearbyPublication(config, message) { const [nearbyStatus, setNearbyStatus] = react_1.useState('connecting'); react_1.useEffect(() => { const start = async () => { try { const available = await checkBluetoothAvailability(); if (!available) { setNearbyStatus('unavailable'); return; } const permission = await checkBluetoothPermission(); if (!permission) { setNearbyStatus('denied'); return; } await connect(config); await publish(message); setNearbyStatus('published'); } catch (e) { setNearbyStatus('error'); } }; start(); const removeListener = addOnErrorListener((kind, message) => { console.log(`[NEARBY] Error: ${kind}: ${message}`); setNearbyStatus('error'); }); return () => { removeListener(); unpublish(); disconnect(); setNearbyStatus('disconnected'); }; }, [setNearbyStatus, config, message, setNearbyStatus]); return nearbyStatus; } exports.useNearbyPublication = useNearbyPublication; function reducer(messages, payload) { const removeIndices = payload.removeMessages?.map((removeMessage) => messages.findIndex((message) => removeMessage === message)); removeIndices?.forEach((i) => { if (i > -1) messages.splice(i, 1); }); payload.addMessages?.forEach((u) => { if (messages.indexOf(u) === -1) messages.push(u); }); return [...messages]; } /** * Subscribe to nearby messages and return an instance of the `SubscriptionState` object. * * Also calls `checkBluetoothAvailability()` and `checkBluetoothPermission()`. * @param config The Nearby API configuration object to use. **Warning: Use `useMemo(..)` for the Object, otherwise you get an infinite loop of re-renders!** * @returns A state of all nearby messages * @example * export default function App() { * const nearbyConfig = useMemo<NearbyConfig>(() => ({ apiKey: GOOGLE_API_KEY }), []); * const { nearbyMessages, nearbyStatus } = useNearbySubscription(nearbyConfig); * return ( * <FlatList * data={nearbyMessages} * renderItem={({ item }) => <Text>{item}</Text>} * /> * ); * } */ function useNearbySubscription(config) { const [nearbyMessages, dispatch] = react_1.useReducer(reducer, []); const [nearbyStatus, setNearbyStatus] = react_1.useState('connecting'); const messageFound = react_1.useCallback((message) => { dispatch({ addMessages: [message] }); }, [dispatch]); const messageLost = react_1.useCallback((message) => { dispatch({ removeMessages: [message] }); }, [dispatch]); react_1.useEffect(() => { const start = async () => { try { const available = await checkBluetoothAvailability(); if (!available) { setNearbyStatus('unavailable'); return; } const permission = await checkBluetoothPermission(); if (!permission) { setNearbyStatus('denied'); return; } await connect(config); await subscribe(messageFound, messageLost); setNearbyStatus('subscribed'); } catch (e) { setNearbyStatus('error'); } }; start(); const removeListener = addOnErrorListener((kind, message) => { console.log(`[NEARBY] Error: ${kind}: ${message}`); setNearbyStatus('error'); }); return () => { removeListener(); unpublish(); disconnect(); setNearbyStatus('disconnected'); }; }, [config, messageFound, messageLost, setNearbyStatus]); return { nearbyMessages, nearbyStatus }; } exports.useNearbySubscription = useNearbySubscription; /** * Search for a specific message using the nearby messages API. Returns an instance of the `SearchState` interface. * * Also calls `checkBluetoothAvailability()` and `checkBluetoothPermission()`. * @param config The Nearby API configuration object to use. **Warning: Use `useMemo(..)` for the Object, otherwise you get an infinite loop of re-renders!** * @param searchFor The string to perform the nearby search for * @returns A state whether the message has been found or not. * @example * export default function App() { * const nearbyConfig = useMemo<NearbyConfig>(() => ({ apiKey: GOOGLE_API_KEY }), []); * const { isNearby, nearbyStatus } = useNearbySearch(nearbyConfig, 'iPhone 11'); * return ( * <Text>{isNearby ? 'iPhone 11 is nearby!' : 'iPhone 11 is far, far away.'}</Text> * ); * } */ function useNearbySearch(config, searchFor) { const [isNearby, setIsNearby] = react_1.useState(false); const [nearbyStatus, setNearbyStatus] = react_1.useState('connecting'); const messageFound = react_1.useCallback((message) => { if (message === searchFor) setIsNearby(true); }, [searchFor, setIsNearby]); const messageLost = react_1.useCallback((message) => { if (message === searchFor) setIsNearby(false); }, [searchFor, setIsNearby]); react_1.useEffect(() => { const start = async () => { try { const available = await checkBluetoothAvailability(); if (!available) { setNearbyStatus('unavailable'); return; } const permission = await checkBluetoothPermission(); if (!permission) { setNearbyStatus('denied'); return; } await connect(config); await subscribe(messageFound, messageLost); setNearbyStatus('subscribed'); } catch (e) { setNearbyStatus('error'); } }; start(); const removeListener = addOnErrorListener((kind, message) => { console.log(`[NEARBY] Error: ${kind}: ${message}`); setNearbyStatus('error'); }); return () => { removeListener(); unsubscribe(); disconnect(); setNearbyStatus('disconnected'); }; }, [config, messageFound, messageLost, setNearbyStatus]); return { isNearby, nearbyStatus }; } exports.useNearbySearch = useNearbySearch; /** * Add an error listener which automatically disposes when the component unmounts. * @param callback The function to call when an error occurs. */ function useNearbyErrorCallback(callback) { react_1.useEffect(() => addOnErrorListener(callback), [callback]); } exports.useNearbyErrorCallback = useNearbyErrorCallback;