Skip to content

Commit eca8335

Browse files
authored
[Samples][React-Native] add Expo sample for messaging (EventHubs/ServiceBus) (Azure#23610)
1. Run `npx create-expo-app messaging` 2. Remove the generated `.git` folder 3. Add a `tsconfig.json` 4. Run `npx expo start` 5. Add testing code 6. Add node js polyfills 7. Add WebSocket wrapper
1 parent 44fd8c7 commit eca8335

File tree

19 files changed

+686
-0
lines changed

19 files changed

+686
-0
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true,
3+
"40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true
4+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
node_modules/
2+
.expo/
3+
dist/
4+
npm-debug.*
5+
*.jks
6+
*.p8
7+
*.p12
8+
*.key
9+
*.mobileprovision
10+
*.orig.*
11+
web-build/
12+
13+
# macOS
14+
.DS_Store
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { StatusBar } from "expo-status-bar";
2+
import React, { useState } from "react";
3+
import { Button, StyleSheet, Text, View, FlatList, TouchableOpacity } from "react-native";
4+
5+
import "node-libs-react-native/globals";
6+
import "react-native-get-random-values";
7+
8+
import { testSDK } from "./src/testSDK";
9+
10+
const DATA = [
11+
{
12+
id: "eh-send-msgs",
13+
title: "Event Hub: Send Messages",
14+
},
15+
{
16+
id: "eh-receive-msgs",
17+
title: "Event Hub: Receive Messages",
18+
},
19+
{
20+
id: "sb-send-msgs",
21+
title: "Service Bus: Send Messages",
22+
},
23+
{
24+
id: "sb-receive-msgs",
25+
title: "Service Bus: Receive Messages",
26+
},
27+
];
28+
29+
const Item = ({ item, onPress, backgroundColor, textColor }) => (
30+
<TouchableOpacity onPress={onPress} style={[styles.item, backgroundColor]}>
31+
<Text style={[styles.title, textColor]}>{item.title}</Text>
32+
</TouchableOpacity>
33+
);
34+
35+
export default function App() {
36+
const [selectedId, setSelectedId] = useState(null);
37+
38+
const renderItem = ({ item }) => {
39+
const backgroundColor = item.id === selectedId ? "#6e3b6e" : "#f9c2ff";
40+
const color = item.id === selectedId ? "white" : "black";
41+
42+
return (
43+
<Item
44+
item={item}
45+
onPress={() => setSelectedId(item.id)}
46+
backgroundColor={{ backgroundColor }}
47+
textColor={{ color }}
48+
/>
49+
);
50+
};
51+
52+
return (
53+
<View style={styles.container}>
54+
<Text>@azure/service-bus test app</Text>
55+
<Button title="Run tests!" onPress={() => testSDK(selectedId)} />
56+
<FlatList
57+
data={DATA}
58+
renderItem={renderItem}
59+
keyExtractor={(item) => item.id}
60+
extraData={selectedId}
61+
/>
62+
<StatusBar style="auto" />
63+
</View>
64+
);
65+
}
66+
67+
const styles = StyleSheet.create({
68+
container: {
69+
flex: 1,
70+
backgroundColor: "#fff",
71+
alignItems: "center",
72+
justifyContent: "center",
73+
},
74+
});
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
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.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"expo": {
3+
"name": "messaging",
4+
"slug": "messaging",
5+
"version": "1.0.0",
6+
"orientation": "portrait",
7+
"icon": "./assets/icon.png",
8+
"userInterfaceStyle": "light",
9+
"splash": {
10+
"image": "./assets/splash.png",
11+
"resizeMode": "contain",
12+
"backgroundColor": "#ffffff"
13+
},
14+
"updates": {
15+
"fallbackToCacheTimeout": 0
16+
},
17+
"assetBundlePatterns": ["**/*"],
18+
"ios": {
19+
"supportsTablet": true
20+
},
21+
"android": {
22+
"adaptiveIcon": {
23+
"foregroundImage": "./assets/adaptive-icon.png",
24+
"backgroundColor": "#FFFFFF"
25+
}
26+
},
27+
"web": {
28+
"favicon": "./assets/favicon.png"
29+
}
30+
}
31+
}
17.1 KB
Loading
1.43 KB
Loading
21.9 KB
Loading
46.2 KB
Loading
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module.exports = function (api) {
2+
api.cache(true);
3+
return {
4+
presets: ["babel-preset-expo"],
5+
plugins: [
6+
"@babel/plugin-proposal-async-generator-functions",
7+
["inline-dotenv", { unsafe: true }],
8+
],
9+
};
10+
};

0 commit comments

Comments
 (0)