|
| 1 | +# Azure SDK samples for React Native using Expo (TypeScript) |
| 2 | + |
| 3 | +This sample application shows how to use the TypeScript client libraries for Azure in some common scenarios. |
| 4 | + |
| 5 | +In this sample, we build a simple application in React Native using Expo and integrating with Azure Event Hubs and Service Bus. |
| 6 | + |
| 7 | +## Prerequisites |
| 8 | + |
| 9 | +The samples are compatible with LTS versions of Node.js. |
| 10 | + |
| 11 | +The sample is using [Expo](https://expo.dev/), a framework and platform for universal React applications. |
| 12 | + |
| 13 | +You need [an Azure subscription][freesub] and the following resources created to run this sample: |
| 14 | + |
| 15 | +## Step by step |
| 16 | + |
| 17 | +### Create the project |
| 18 | + |
| 19 | +1. Create the project using Expo |
| 20 | + |
| 21 | +```bash |
| 22 | +npx create-expo-app messaging |
| 23 | +``` |
| 24 | + |
| 25 | +2. Add TypeScript support by adding a `tsconfig.json` file with following content |
| 26 | + |
| 27 | +```js |
| 28 | +{ |
| 29 | + "extends": "expo/tsconfig.base", |
| 30 | + "compilerOptions": { |
| 31 | + "strict": true |
| 32 | + } |
| 33 | +} |
| 34 | +``` |
| 35 | + |
| 36 | +3. Run the following Expo command to start the app. Expo will prompt to install TypeScript and types packages for React/React-Native. |
| 37 | + |
| 38 | +```bash |
| 39 | +npx expo start |
| 40 | +``` |
| 41 | + |
| 42 | +Now the application should be bundled and ready to run on device, emulators, or web browsers. |
| 43 | + |
| 44 | +4. Ctrl+C to exit the npm script. |
| 45 | + |
| 46 | +### Add a button and a list box to the app |
| 47 | + |
| 48 | +They will be used to choose a testing scenario and run it. Open App.tsx and replace the content with following code |
| 49 | + |
| 50 | +```ts |
| 51 | +import { StatusBar } from "expo-status-bar"; |
| 52 | +import React, { useState } from "react"; |
| 53 | +import { Button, StyleSheet, Text, View, FlatList, TouchableOpacity } from "react-native"; |
| 54 | + |
| 55 | +import { testSDK } from "./src/testSDK"; |
| 56 | + |
| 57 | +const DATA = [ |
| 58 | + { |
| 59 | + id: "eh-send-msgs", |
| 60 | + title: "Event Hub: Send Messages", |
| 61 | + }, |
| 62 | + { |
| 63 | + id: "eh-receive-msgs", |
| 64 | + title: "Event Hub: Receive Messages", |
| 65 | + }, |
| 66 | + { |
| 67 | + id: "sb-send-msgs", |
| 68 | + title: "Service Bus: Send Messages", |
| 69 | + }, |
| 70 | + { |
| 71 | + id: "sb-receive-msgs", |
| 72 | + title: "Service Bus: Receive Messages", |
| 73 | + }, |
| 74 | +]; |
| 75 | + |
| 76 | +const Item = ({ item, onPress, backgroundColor, textColor }) => ( |
| 77 | + <TouchableOpacity onPress={onPress} style={[styles.item, backgroundColor]}> |
| 78 | + <Text style={[styles.title, textColor]}>{item.title}</Text> |
| 79 | + </TouchableOpacity> |
| 80 | +); |
| 81 | + |
| 82 | +export default function App() { |
| 83 | + const [selectedId, setSelectedId] = useState(null); |
| 84 | + |
| 85 | + const renderItem = ({ item }) => { |
| 86 | + const backgroundColor = item.id === selectedId ? "#6e3b6e" : "#f9c2ff"; |
| 87 | + const color = item.id === selectedId ? "white" : "black"; |
| 88 | + |
| 89 | + return ( |
| 90 | + <Item |
| 91 | + item={item} |
| 92 | + onPress={() => setSelectedId(item.id)} |
| 93 | + backgroundColor={{ backgroundColor }} |
| 94 | + textColor={{ color }} |
| 95 | + /> |
| 96 | + ); |
| 97 | + }; |
| 98 | + |
| 99 | + return ( |
| 100 | + <View style={styles.container}> |
| 101 | + <Text>@azure/service-bus test app</Text> |
| 102 | + <Button title="Run tests!" onPress={() => testSDK(selectedId)} /> |
| 103 | + <FlatList |
| 104 | + data={DATA} |
| 105 | + renderItem={renderItem} |
| 106 | + keyExtractor={(item) => item.id} |
| 107 | + extraData={selectedId} |
| 108 | + /> |
| 109 | + <StatusBar style="auto" /> |
| 110 | + </View> |
| 111 | + ); |
| 112 | +} |
| 113 | + |
| 114 | +const styles = StyleSheet.create({ |
| 115 | + container: { |
| 116 | + flex: 1, |
| 117 | + backgroundColor: "#fff", |
| 118 | + alignItems: "center", |
| 119 | + justifyContent: "center", |
| 120 | + }, |
| 121 | +}); |
| 122 | +``` |
| 123 | +
|
| 124 | +### Add testing code |
| 125 | +
|
| 126 | +Add a `src` directory at the root of the project and add the following files |
| 127 | +
|
| 128 | +- [testSDK.ts](./src/testSDK.ts) - contains the code to run the scenarios. |
| 129 | +- [ehSendEvents.ts](./src/ehSendEvents.ts) - contains the code to send messages to Event Hub. |
| 130 | +- [ehReceiveEvents.ts](./src/ehReceiveEvents.ts) - contains the code to receive messages from Event Hub. |
| 131 | +- [sbSendMessages.ts](./src/sbSendMessages.ts) - contains the code to send messages to Service Bus. |
| 132 | +- [sbReceiveMessages.ts](./src/sbReceiveMessages.ts) - contains the code to receive messages from Service Bus. |
| 133 | +- [wsWrapper.ts](./src/wsWrapper.ts) - contains the code to wrap `WebSocket` implementation in React Native to set default value of its `binaryType` property to `"blob"`. |
| 134 | +
|
| 135 | +### Add dependencies |
| 136 | +
|
| 137 | +We need to add JavaScript Client SDK libraries for Event Hubs and Service Bus as dependencies. |
| 138 | +We use `@babel/plugin-proposal-async-generator-functions` to help transform async iterator usage. |
| 139 | +We also use `babel-plugin-inline-dotenv` to help load secrets from our `.env` file while developing. |
| 140 | +
|
| 141 | +```shell |
| 142 | +yarn add @azure/event-hubs @azure/service-bus |
| 143 | +yarn add --dev babel-plugin-inline-dotenv @babel/plugin-proposal-async-generator-functions |
| 144 | +``` |
| 145 | +
|
| 146 | +Then add the following into `babel.config.js` to enable the plugins |
| 147 | +
|
| 148 | +```diff |
| 149 | + presets: ["babel-preset-expo"], |
| 150 | ++ plugins: [ |
| 151 | ++ "@babel/plugin-proposal-async-generator-functions", |
| 152 | ++ ["inline-dotenv", { unsafe: true }], |
| 153 | ++ ], |
| 154 | +``` |
| 155 | +
|
| 156 | +### Add connection strings to .env file |
| 157 | +
|
| 158 | +Create a `.env` file in the project directory. Retrieve your connection strings from Azure portal and add them to the `.env` file. Since this file contains secrets you need to add it to the ignore list if your code is committed to a repository. We also add variables for messaging entity names. |
| 159 | +
|
| 160 | +**Note** We use connection string directly here for testing purpose. You should consider the trade-off between security and convenience and better use a backend to dynamically provide secrets to only authenticated users. |
| 161 | +
|
| 162 | +``` |
| 163 | +# Service Bus |
| 164 | +SERVICEBUS_CONNECTION_STRING= |
| 165 | +QUEUE_NAME= |
| 166 | + |
| 167 | +# Event Hub |
| 168 | +EVENTHUB_CONNECTION_STRING= |
| 169 | +EVENTHUB_NAME= |
| 170 | +CONSUMER_GROUP_NAME= |
| 171 | +``` |
| 172 | +
|
| 173 | +**Note**: Whenever you update the .env file again, you need to clear expo cache and rebuild. It can be done by passing `-c` to the start command, for example, in `package.json` |
| 174 | +
|
| 175 | +```diff |
| 176 | +- "android": "expo start --android", |
| 177 | ++ "android": "expo start --android -c", |
| 178 | +``` |
| 179 | +
|
| 180 | +### Add polyfills for NodeJS modules |
| 181 | +
|
| 182 | +Our dependency `rhea` depends a few NodeJS modules. We need to provide polyfills for them. In this sample, We will be using `node-libs-react-native` and `react-native-get-random-values`. **Note** that it may be possible to slim the polyfills further as the SDK really only needs `process`, and `Buffer` at runtime. |
| 183 | +
|
| 184 | +```bash |
| 185 | +yarn add node-libs-react-native react-native-get-random-values |
| 186 | +``` |
| 187 | +
|
| 188 | +Add a `metro.config.js` to the root of the project with content of |
| 189 | +
|
| 190 | +```js |
| 191 | +module.exports = { |
| 192 | + resolver: { |
| 193 | + extraNodeModules: require("node-libs-react-native"), |
| 194 | + }, |
| 195 | +}; |
| 196 | +``` |
| 197 | +
|
| 198 | +In `App.tsx` we need to import the polyfills at the top, before importing any Azure SDK module. |
| 199 | +
|
| 200 | +```diff |
| 201 | ++import "node-libs-react-native/globals"; |
| 202 | ++import "react-native-get-random-values"; |
| 203 | + |
| 204 | + import { testSDK } from "./src/testSDK"; |
| 205 | +``` |
| 206 | +
|
| 207 | +### Add a WebSocket wrapper |
| 208 | +
|
| 209 | +The implementation of `WebSocket` on React-Native doesn't follow the standard, which is causing problem for `rhea` when receiving binary data. We need to add a wrapper around `WebSocket` to fix it. |
| 210 | +
|
| 211 | +Add the following to `src/wsWrapper.ts` |
| 212 | +
|
| 213 | +```ts |
| 214 | +export class WebSocketWrapper { |
| 215 | + constructor(...args) { |
| 216 | + const instance = new globalThis.WebSocket(...args); |
| 217 | + instance.binaryType = "blob"; |
| 218 | + return instance; |
| 219 | + } |
| 220 | +} |
| 221 | +``` |
| 222 | +
|
| 223 | +Then update our testing code to pass the `webSocketOptions` via the client options when creating clients: |
| 224 | +
|
| 225 | +```diff |
| 226 | +-import { ServiceBusClient, ServiceBusMessage, ServiceBusMessageBatch } from "@azure/service-bus"; |
| 227 | ++import { ServiceBusClient, ServiceBusMessage, ServiceBusMessageBatch } from "@azure/service-bus"; |
| 228 | ++import { WebSocketWrapper } from "./wsWrapper"; |
| 229 | + |
| 230 | +// ... |
| 231 | + |
| 232 | + export async function main() { |
| 233 | +- const sbClient = new ServiceBusClient(connectionString); |
| 234 | ++ const sbClient = new ServiceBusClient(connectionString, { |
| 235 | ++ webSocketOptions: { |
| 236 | ++ webSocket: WebSocketWrapper, |
| 237 | ++ }, |
| 238 | ++ }); |
| 239 | +``` |
| 240 | +
|
| 241 | +Now the application should work as expected, sending and receiving Event Hub Events/Service Bus messages. |
0 commit comments