mbnotify-app 1.0.2 โ 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +291 -0
- package/dist/cjs/index.d.ts +0 -6
- package/dist/cjs/index.js +6 -19
- package/dist/esm/index.d.ts +0 -6
- package/dist/esm/index.js +5 -18
- package/package.json +5 -2
- package/src/index.ts +8 -19
package/README.md
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
|
|
2
|
+
---
|
|
3
|
+
|
|
4
|
+
# ๐ mbnotify Ecosystem
|
|
5
|
+
|
|
6
|
+
A **real-time push notification system** for web & mobile apps powered by MQTT.
|
|
7
|
+
|
|
8
|
+
This ecosystem consists of two packages:
|
|
9
|
+
|
|
10
|
+
* ๐ฆ **mbnotify** โ Send notifications (Backend / Server)
|
|
11
|
+
* ๐ฑ **mbnotify-app** โ Receive notifications (Expo / React Native)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# ๐ฆ 1. mbnotify (Server / Sender)
|
|
16
|
+
|
|
17
|
+
Send push notifications to devices instantly.
|
|
18
|
+
|
|
19
|
+
## ๐ฅ Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install mbnotify
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## โก Usage
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
import { sendNotification } from "mbnotify";
|
|
29
|
+
|
|
30
|
+
await sendNotification({
|
|
31
|
+
appName: "myapp",
|
|
32
|
+
token: "dev_xxxxxxxxxxxxx",
|
|
33
|
+
|
|
34
|
+
title: "Order Shipped ๐",
|
|
35
|
+
body: "Your order has been shipped!",
|
|
36
|
+
|
|
37
|
+
icon: "https://cdn-icons-png.flaticon.com/512/1827/1827392.png",
|
|
38
|
+
image: "https://images.unsplash.com/photo-1586528116311-ad8dd3c8310d?w=1200",
|
|
39
|
+
|
|
40
|
+
url: "https://google.com",
|
|
41
|
+
|
|
42
|
+
data: {
|
|
43
|
+
orderId: "12345"
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
console.log("โ
Notification sent");
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## ๐งพ Payload Options
|
|
53
|
+
|
|
54
|
+
| Field | Type | Description |
|
|
55
|
+
| ------- | ------ | -------------------- |
|
|
56
|
+
| appName | string | App identifier |
|
|
57
|
+
| token | string | Device token |
|
|
58
|
+
| title | string | Notification title |
|
|
59
|
+
| body | string | Notification message |
|
|
60
|
+
| icon | string | Small icon URL |
|
|
61
|
+
| image | string | Large image URL |
|
|
62
|
+
| url | string | Deep link / redirect |
|
|
63
|
+
| data | object | Custom payload |
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## ๐ฏ Features
|
|
68
|
+
|
|
69
|
+
* โก Real-time delivery
|
|
70
|
+
* ๐ Works globally via MQTT
|
|
71
|
+
* ๐ฆ Lightweight API
|
|
72
|
+
* ๐ Supports deep linking
|
|
73
|
+
* ๐ง Custom data payloads
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
# ๐ฑ 2. mbnotify-app (Client / Receiver)
|
|
78
|
+
|
|
79
|
+
Receive notifications inside your **Expo / React Native app**.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## ๐ฅ Installation
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
npm install mbnotify-app
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Also install Expo notifications:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npx expo install expo-notifications
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## โ๏ธ Setup
|
|
98
|
+
|
|
99
|
+
### โ
Required Fix (VERY IMPORTANT)
|
|
100
|
+
|
|
101
|
+
React Native doesn't support some Node modules by default:
|
|
102
|
+
|
|
103
|
+
```js
|
|
104
|
+
import { Buffer } from "buffer";
|
|
105
|
+
import process from "process";
|
|
106
|
+
|
|
107
|
+
global.Buffer = Buffer;
|
|
108
|
+
global.process = process;
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## ๐ Basic Usage
|
|
114
|
+
|
|
115
|
+
```js
|
|
116
|
+
import { useEffect, useState } from "react";
|
|
117
|
+
import { Text, View } from "react-native";
|
|
118
|
+
import * as Notifications from "expo-notifications";
|
|
119
|
+
|
|
120
|
+
import { requestPermission, getToken } from "mbnotify-app";
|
|
121
|
+
|
|
122
|
+
// Show notifications in foreground
|
|
123
|
+
Notifications.setNotificationHandler({
|
|
124
|
+
handleNotification: async () => ({
|
|
125
|
+
shouldShowAlert: true,
|
|
126
|
+
shouldPlaySound: true,
|
|
127
|
+
shouldSetBadge: false,
|
|
128
|
+
}),
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
export default function App() {
|
|
132
|
+
|
|
133
|
+
const [token, setToken] = useState(null);
|
|
134
|
+
const [status, setStatus] = useState("Initializing...");
|
|
135
|
+
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
|
|
138
|
+
async function init() {
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
setStatus("Requesting permission...");
|
|
142
|
+
|
|
143
|
+
const granted = await requestPermission();
|
|
144
|
+
|
|
145
|
+
if (!granted) {
|
|
146
|
+
setStatus("โ Permission denied");
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
setStatus("Connecting...");
|
|
151
|
+
|
|
152
|
+
const deviceToken = await getToken("myapp");
|
|
153
|
+
|
|
154
|
+
setToken(deviceToken);
|
|
155
|
+
setStatus("โ
Connected");
|
|
156
|
+
|
|
157
|
+
console.log("๐ฑ Device token:", deviceToken);
|
|
158
|
+
|
|
159
|
+
} catch (err) {
|
|
160
|
+
console.error(err);
|
|
161
|
+
setStatus("โ Error initializing");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
init();
|
|
167
|
+
|
|
168
|
+
}, []);
|
|
169
|
+
|
|
170
|
+
return (
|
|
171
|
+
<View>
|
|
172
|
+
<Text>{status}</Text>
|
|
173
|
+
{token && <Text>{token}</Text>}
|
|
174
|
+
</View>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## ๐ Functions
|
|
182
|
+
|
|
183
|
+
### `requestPermission()`
|
|
184
|
+
|
|
185
|
+
```js
|
|
186
|
+
const granted = await requestPermission();
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
* Requests notification permission
|
|
190
|
+
* Returns `true` / `false`
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
### `getToken(appName)`
|
|
195
|
+
|
|
196
|
+
```js
|
|
197
|
+
const token = await getToken("myapp");
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
* Connects to MQTT
|
|
201
|
+
* Returns a **unique device token**
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## ๐ฒ Flow Overview
|
|
206
|
+
|
|
207
|
+
```text
|
|
208
|
+
[mbnotify-app] โ Generates Token
|
|
209
|
+
โ
|
|
210
|
+
[Your Server] โ Stores Token
|
|
211
|
+
โ
|
|
212
|
+
[mbnotify] โ Sends Notification
|
|
213
|
+
โ
|
|
214
|
+
[Device] โ Receives Notification ๐
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## ๐ฅ Features
|
|
220
|
+
|
|
221
|
+
* ๐ก Real-time notifications (MQTT)
|
|
222
|
+
* ๐ฑ Expo compatible
|
|
223
|
+
* ๐ Foreground + Background support
|
|
224
|
+
* ๐ Unique device tokens
|
|
225
|
+
* โก Fast & lightweight
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## โ ๏ธ Common Issues
|
|
230
|
+
|
|
231
|
+
### โ MQTT Errors in React Native
|
|
232
|
+
|
|
233
|
+
Fix:
|
|
234
|
+
|
|
235
|
+
```js
|
|
236
|
+
global.Buffer = require("buffer").Buffer;
|
|
237
|
+
global.process = require("process");
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
### โ Notifications not showing
|
|
243
|
+
|
|
244
|
+
Make sure:
|
|
245
|
+
|
|
246
|
+
* Permission is granted
|
|
247
|
+
* `Notifications.setNotificationHandler` is configured
|
|
248
|
+
* App is not restricted by OS battery settings
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## ๐ก Best Practices
|
|
253
|
+
|
|
254
|
+
* Store tokens securely in DB
|
|
255
|
+
* Use meaningful `appName`
|
|
256
|
+
* Avoid sending too many notifications
|
|
257
|
+
* Use `data` for deep linking/navigation
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## ๐งช Example Use Case
|
|
262
|
+
|
|
263
|
+
* ๐ E-commerce โ Order updates
|
|
264
|
+
* ๐ฌ Chat app โ New messages
|
|
265
|
+
* ๐ฆ Delivery โ Status tracking
|
|
266
|
+
* ๐ SaaS โ Alerts & updates
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## ๐จโ๐ป Author
|
|
271
|
+
|
|
272
|
+
**Manoj Gowda B R**
|
|
273
|
+
Full Stack Developer (MERN)
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## ๐ Support
|
|
278
|
+
|
|
279
|
+
If you like this project:
|
|
280
|
+
|
|
281
|
+
โญ Star the repo
|
|
282
|
+
๐ Report issues
|
|
283
|
+
๐ Contribute improvements
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## ๐ License
|
|
288
|
+
|
|
289
|
+
MIT License
|
|
290
|
+
|
|
291
|
+
---
|
package/dist/cjs/index.d.ts
CHANGED
package/dist/cjs/index.js
CHANGED
|
@@ -2,21 +2,15 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.requestPermission = requestPermission;
|
|
4
4
|
exports.getToken = getToken;
|
|
5
|
-
const
|
|
5
|
+
const mqtt_1 = require("mqtt");
|
|
6
6
|
const Notifications = require("expo-notifications");
|
|
7
7
|
const async_storage_1 = require("@react-native-async-storage/async-storage");
|
|
8
8
|
const DEFAULT_BROKER = "wss://broker.hivemq.com:8884/mqtt";
|
|
9
9
|
let client = null;
|
|
10
|
-
/**
|
|
11
|
-
* Request permission
|
|
12
|
-
*/
|
|
13
10
|
async function requestPermission() {
|
|
14
11
|
const { status } = await Notifications.requestPermissionsAsync();
|
|
15
12
|
return status === "granted";
|
|
16
13
|
}
|
|
17
|
-
/**
|
|
18
|
-
* Generate token
|
|
19
|
-
*/
|
|
20
14
|
async function generateToken() {
|
|
21
15
|
let token = await async_storage_1.default.getItem("mbnotify_token");
|
|
22
16
|
if (!token) {
|
|
@@ -28,27 +22,23 @@ async function generateToken() {
|
|
|
28
22
|
}
|
|
29
23
|
return token;
|
|
30
24
|
}
|
|
31
|
-
/**
|
|
32
|
-
* Connect MQTT
|
|
33
|
-
*/
|
|
34
25
|
function connectMQTT(brokerUrl) {
|
|
35
26
|
if (client)
|
|
36
27
|
return client;
|
|
37
|
-
client =
|
|
28
|
+
client = mqtt_1.default.connect(brokerUrl, {
|
|
38
29
|
reconnectPeriod: 5000,
|
|
39
30
|
connectTimeout: 10000
|
|
40
31
|
});
|
|
41
32
|
client.on("connect", () => {
|
|
42
|
-
console.log("โ
|
|
33
|
+
// console.log("โ
mbnotify app connected");
|
|
43
34
|
});
|
|
44
35
|
client.on("error", (err) => {
|
|
45
|
-
console.error("
|
|
36
|
+
console.error("MQTT error:", err);
|
|
46
37
|
});
|
|
47
|
-
// โ
SINGLE
|
|
38
|
+
// โ
SINGLE message listener
|
|
48
39
|
client.on("message", async (_, message) => {
|
|
49
40
|
try {
|
|
50
41
|
const payload = JSON.parse(message.toString());
|
|
51
|
-
console.log("๐ฉ Received:", payload);
|
|
52
42
|
await Notifications.scheduleNotificationAsync({
|
|
53
43
|
content: {
|
|
54
44
|
title: payload.title || "",
|
|
@@ -64,9 +54,6 @@ function connectMQTT(brokerUrl) {
|
|
|
64
54
|
});
|
|
65
55
|
return client;
|
|
66
56
|
}
|
|
67
|
-
/**
|
|
68
|
-
* Get token + subscribe
|
|
69
|
-
*/
|
|
70
57
|
async function getToken(appName, brokerUrl = DEFAULT_BROKER) {
|
|
71
58
|
if (!appName)
|
|
72
59
|
throw new Error("appName required");
|
|
@@ -74,6 +61,6 @@ async function getToken(appName, brokerUrl = DEFAULT_BROKER) {
|
|
|
74
61
|
const topic = `/${appName}/${token}/notification`;
|
|
75
62
|
const mqttClient = connectMQTT(brokerUrl);
|
|
76
63
|
mqttClient.subscribe(topic);
|
|
77
|
-
console.log("๐ก Subscribed:", topic);
|
|
64
|
+
// console.log("๐ก Subscribed:", topic);
|
|
78
65
|
return token;
|
|
79
66
|
}
|
package/dist/esm/index.d.ts
CHANGED
package/dist/esm/index.js
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import mqtt from "mqtt";
|
|
2
2
|
import * as Notifications from "expo-notifications";
|
|
3
3
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
4
4
|
const DEFAULT_BROKER = "wss://broker.hivemq.com:8884/mqtt";
|
|
5
5
|
let client = null;
|
|
6
|
-
/**
|
|
7
|
-
* Request permission
|
|
8
|
-
*/
|
|
9
6
|
export async function requestPermission() {
|
|
10
7
|
const { status } = await Notifications.requestPermissionsAsync();
|
|
11
8
|
return status === "granted";
|
|
12
9
|
}
|
|
13
|
-
/**
|
|
14
|
-
* Generate token
|
|
15
|
-
*/
|
|
16
10
|
async function generateToken() {
|
|
17
11
|
let token = await AsyncStorage.getItem("mbnotify_token");
|
|
18
12
|
if (!token) {
|
|
@@ -24,9 +18,6 @@ async function generateToken() {
|
|
|
24
18
|
}
|
|
25
19
|
return token;
|
|
26
20
|
}
|
|
27
|
-
/**
|
|
28
|
-
* Connect MQTT
|
|
29
|
-
*/
|
|
30
21
|
function connectMQTT(brokerUrl) {
|
|
31
22
|
if (client)
|
|
32
23
|
return client;
|
|
@@ -35,16 +26,15 @@ function connectMQTT(brokerUrl) {
|
|
|
35
26
|
connectTimeout: 10000
|
|
36
27
|
});
|
|
37
28
|
client.on("connect", () => {
|
|
38
|
-
console.log("โ
|
|
29
|
+
// console.log("โ
mbnotify app connected");
|
|
39
30
|
});
|
|
40
31
|
client.on("error", (err) => {
|
|
41
|
-
console.error("
|
|
32
|
+
console.error("MQTT error:", err);
|
|
42
33
|
});
|
|
43
|
-
// โ
SINGLE
|
|
34
|
+
// โ
SINGLE message listener
|
|
44
35
|
client.on("message", async (_, message) => {
|
|
45
36
|
try {
|
|
46
37
|
const payload = JSON.parse(message.toString());
|
|
47
|
-
console.log("๐ฉ Received:", payload);
|
|
48
38
|
await Notifications.scheduleNotificationAsync({
|
|
49
39
|
content: {
|
|
50
40
|
title: payload.title || "",
|
|
@@ -60,9 +50,6 @@ function connectMQTT(brokerUrl) {
|
|
|
60
50
|
});
|
|
61
51
|
return client;
|
|
62
52
|
}
|
|
63
|
-
/**
|
|
64
|
-
* Get token + subscribe
|
|
65
|
-
*/
|
|
66
53
|
export async function getToken(appName, brokerUrl = DEFAULT_BROKER) {
|
|
67
54
|
if (!appName)
|
|
68
55
|
throw new Error("appName required");
|
|
@@ -70,6 +57,6 @@ export async function getToken(appName, brokerUrl = DEFAULT_BROKER) {
|
|
|
70
57
|
const topic = `/${appName}/${token}/notification`;
|
|
71
58
|
const mqttClient = connectMQTT(brokerUrl);
|
|
72
59
|
mqttClient.subscribe(topic);
|
|
73
|
-
console.log("๐ก Subscribed:", topic);
|
|
60
|
+
// console.log("๐ก Subscribed:", topic);
|
|
74
61
|
return token;
|
|
75
62
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mbnotify-app",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "React Native (Expo) client for mbnotify MQTT push notifications",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -31,7 +31,10 @@
|
|
|
31
31
|
"buffer": "^6.0.3",
|
|
32
32
|
"expo-notifications": "~55.0.12",
|
|
33
33
|
"mqtt": "^5.15.0",
|
|
34
|
-
"process": "^0.11.10"
|
|
34
|
+
"process": "^0.11.10",
|
|
35
|
+
"expo": "~55.0.6",
|
|
36
|
+
"react": "19.2.0",
|
|
37
|
+
"react-native": "0.83.2"
|
|
35
38
|
},
|
|
36
39
|
"devDependencies": {
|
|
37
40
|
"typescript": "^5.9.3"
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import mqtt from "mqtt";
|
|
2
2
|
import * as Notifications from "expo-notifications";
|
|
3
3
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
4
4
|
|
|
@@ -6,22 +6,19 @@ const DEFAULT_BROKER = "wss://broker.hivemq.com:8884/mqtt";
|
|
|
6
6
|
|
|
7
7
|
let client: any = null;
|
|
8
8
|
|
|
9
|
-
/**
|
|
10
|
-
* Request permission
|
|
11
|
-
*/
|
|
12
9
|
export async function requestPermission(): Promise<boolean> {
|
|
10
|
+
|
|
13
11
|
const { status } = await Notifications.requestPermissionsAsync();
|
|
12
|
+
|
|
14
13
|
return status === "granted";
|
|
15
14
|
}
|
|
16
15
|
|
|
17
|
-
/**
|
|
18
|
-
* Generate token
|
|
19
|
-
*/
|
|
20
16
|
async function generateToken(): Promise<string> {
|
|
21
17
|
|
|
22
18
|
let token = await AsyncStorage.getItem("mbnotify_token");
|
|
23
19
|
|
|
24
20
|
if (!token) {
|
|
21
|
+
|
|
25
22
|
token =
|
|
26
23
|
"dev_" +
|
|
27
24
|
Math.random().toString(36).substring(2) +
|
|
@@ -33,9 +30,6 @@ async function generateToken(): Promise<string> {
|
|
|
33
30
|
return token;
|
|
34
31
|
}
|
|
35
32
|
|
|
36
|
-
/**
|
|
37
|
-
* Connect MQTT
|
|
38
|
-
*/
|
|
39
33
|
function connectMQTT(brokerUrl: string) {
|
|
40
34
|
|
|
41
35
|
if (client) return client;
|
|
@@ -46,22 +40,20 @@ function connectMQTT(brokerUrl: string) {
|
|
|
46
40
|
});
|
|
47
41
|
|
|
48
42
|
client.on("connect", () => {
|
|
49
|
-
console.log("โ
|
|
43
|
+
// console.log("โ
mbnotify app connected");
|
|
50
44
|
});
|
|
51
45
|
|
|
52
46
|
client.on("error", (err: any) => {
|
|
53
|
-
console.error("
|
|
47
|
+
console.error("MQTT error:", err);
|
|
54
48
|
});
|
|
55
49
|
|
|
56
|
-
// โ
SINGLE
|
|
50
|
+
// โ
SINGLE message listener
|
|
57
51
|
client.on("message", async (_: any, message: any) => {
|
|
58
52
|
|
|
59
53
|
try {
|
|
60
54
|
|
|
61
55
|
const payload = JSON.parse(message.toString());
|
|
62
56
|
|
|
63
|
-
console.log("๐ฉ Received:", payload);
|
|
64
|
-
|
|
65
57
|
await Notifications.scheduleNotificationAsync({
|
|
66
58
|
content: {
|
|
67
59
|
title: payload.title || "",
|
|
@@ -80,9 +72,6 @@ function connectMQTT(brokerUrl: string) {
|
|
|
80
72
|
return client;
|
|
81
73
|
}
|
|
82
74
|
|
|
83
|
-
/**
|
|
84
|
-
* Get token + subscribe
|
|
85
|
-
*/
|
|
86
75
|
export async function getToken(
|
|
87
76
|
appName: string,
|
|
88
77
|
brokerUrl: string = DEFAULT_BROKER
|
|
@@ -98,7 +87,7 @@ export async function getToken(
|
|
|
98
87
|
|
|
99
88
|
mqttClient.subscribe(topic);
|
|
100
89
|
|
|
101
|
-
console.log("๐ก Subscribed:", topic);
|
|
90
|
+
// console.log("๐ก Subscribed:", topic);
|
|
102
91
|
|
|
103
92
|
return token;
|
|
104
93
|
}
|