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
JavaScript
;
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;