native-update 1.3.6 → 1.4.0
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 +39 -0
- package/dist/esm/config/support.d.ts +9 -0
- package/dist/esm/config/support.js +10 -0
- package/dist/esm/config/support.js.map +1 -0
- package/docs/README.md +19 -10
- package/docs/guides/admin-panel.md +268 -0
- package/docs/guides/channel-management.md +343 -0
- package/docs/guides/dashboard-guide.md +365 -0
- package/docs/guides/end-to-end-workflow.md +491 -0
- package/docs/guides/troubleshooting.md +482 -0
- package/example-apps/react-capacitor/android/app/build/.npmkeep +0 -0
- package/package.json +1 -1
- package/cli/node_modules/.yarn-integrity +0 -16
- package/cli/node_modules/commander/LICENSE +0 -22
- package/cli/node_modules/commander/Readme.md +0 -1148
- package/cli/node_modules/commander/esm.mjs +0 -16
- package/cli/node_modules/commander/index.js +0 -26
- package/cli/node_modules/commander/lib/argument.js +0 -145
- package/cli/node_modules/commander/lib/command.js +0 -2179
- package/cli/node_modules/commander/lib/error.js +0 -43
- package/cli/node_modules/commander/lib/help.js +0 -462
- package/cli/node_modules/commander/lib/option.js +0 -329
- package/cli/node_modules/commander/lib/suggestSimilar.js +0 -100
- package/cli/node_modules/commander/package-support.json +0 -16
- package/cli/node_modules/commander/package.json +0 -80
- package/cli/node_modules/commander/typings/esm.d.mts +0 -3
- package/cli/node_modules/commander/typings/index.d.ts +0 -884
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
# End-to-End Workflow Guide
|
|
2
|
+
|
|
3
|
+
This guide walks you through the complete workflow from setting up Native Update to deploying your first OTA update to production users.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
9
|
+
│ Native Update Workflow │
|
|
10
|
+
├─────────────────────────────────────────────────────────────────────┤
|
|
11
|
+
│ │
|
|
12
|
+
│ 1. SETUP 2. INTEGRATE 3. BUILD & UPLOAD │
|
|
13
|
+
│ ┌─────────┐ ┌─────────────┐ ┌──────────────────┐ │
|
|
14
|
+
│ │Dashboard│ → │ App Code │ → │ Dashboard Upload │ │
|
|
15
|
+
│ │ Account │ │ Integration │ │ or CLI │ │
|
|
16
|
+
│ └─────────┘ └─────────────┘ └──────────────────┘ │
|
|
17
|
+
│ │
|
|
18
|
+
│ 4. TEST 5. ROLLOUT 6. MONITOR │
|
|
19
|
+
│ ┌─────────┐ ┌─────────────┐ ┌──────────────────┐ │
|
|
20
|
+
│ │Dev/Stage│ → │ Production │ → │ Analytics & │ │
|
|
21
|
+
│ │ Testing │ │ Deployment │ │ Rollback │ │
|
|
22
|
+
│ └─────────┘ └─────────────┘ └──────────────────┘ │
|
|
23
|
+
│ │
|
|
24
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Prerequisites
|
|
28
|
+
|
|
29
|
+
Before starting, ensure you have:
|
|
30
|
+
|
|
31
|
+
- [ ] A Capacitor app (React, Vue, Angular, or vanilla JS)
|
|
32
|
+
- [ ] Node.js 18+ installed
|
|
33
|
+
- [ ] yarn package manager
|
|
34
|
+
- [ ] Google account (for dashboard login)
|
|
35
|
+
|
|
36
|
+
## Phase 1: Dashboard Setup (10 minutes)
|
|
37
|
+
|
|
38
|
+
### Step 1.1: Create Dashboard Account
|
|
39
|
+
|
|
40
|
+
1. Visit [nativeupdate.aoneahsan.com](https://nativeupdate.aoneahsan.com)
|
|
41
|
+
2. Click **Get Started**
|
|
42
|
+
3. Sign in with Google
|
|
43
|
+
4. Complete onboarding
|
|
44
|
+
|
|
45
|
+
### Step 1.2: Connect Google Drive
|
|
46
|
+
|
|
47
|
+
1. Go to **Dashboard > Google Drive**
|
|
48
|
+
2. Click **Connect Google Drive**
|
|
49
|
+
3. Grant permissions
|
|
50
|
+
4. Verify connection is active (green status)
|
|
51
|
+
|
|
52
|
+
### Step 1.3: Create Your App
|
|
53
|
+
|
|
54
|
+
1. Go to **Dashboard > Apps**
|
|
55
|
+
2. Click **Create App**
|
|
56
|
+
3. Enter details:
|
|
57
|
+
```
|
|
58
|
+
App Name: My Awesome App
|
|
59
|
+
Package ID: com.example.myawesomeapp
|
|
60
|
+
Description: A sample app with OTA updates
|
|
61
|
+
Platforms: [x] iOS [x] Android [x] Web
|
|
62
|
+
```
|
|
63
|
+
4. Click **Create App**
|
|
64
|
+
|
|
65
|
+
### Step 1.4: Note Your Configuration
|
|
66
|
+
|
|
67
|
+
1. Go to **Dashboard > Configuration**
|
|
68
|
+
2. Select your app
|
|
69
|
+
3. Copy the Capacitor configuration (you'll need this later)
|
|
70
|
+
|
|
71
|
+
## Phase 2: App Integration (15 minutes)
|
|
72
|
+
|
|
73
|
+
### Step 2.1: Install the Plugin
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# Navigate to your Capacitor project
|
|
77
|
+
cd my-capacitor-app
|
|
78
|
+
|
|
79
|
+
# Install the plugin
|
|
80
|
+
yarn add native-update
|
|
81
|
+
|
|
82
|
+
# Sync with native platforms
|
|
83
|
+
npx cap sync
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Step 2.2: Configure Capacitor
|
|
87
|
+
|
|
88
|
+
Update `capacitor.config.ts`:
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { CapacitorConfig } from '@capacitor/cli';
|
|
92
|
+
|
|
93
|
+
const config: CapacitorConfig = {
|
|
94
|
+
appId: 'com.example.myawesomeapp',
|
|
95
|
+
appName: 'My Awesome App',
|
|
96
|
+
webDir: 'dist',
|
|
97
|
+
plugins: {
|
|
98
|
+
NativeUpdate: {
|
|
99
|
+
appId: 'com.example.myawesomeapp',
|
|
100
|
+
updateUrl: 'https://nativeupdate.aoneahsan.com/api/updates',
|
|
101
|
+
channel: 'production',
|
|
102
|
+
autoCheck: true,
|
|
103
|
+
checkInterval: 3600,
|
|
104
|
+
updateStrategy: 'background'
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export default config;
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Step 2.3: Initialize in Your App
|
|
113
|
+
|
|
114
|
+
Create `src/services/update-service.ts`:
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
import { NativeUpdate } from 'native-update';
|
|
118
|
+
|
|
119
|
+
export const updateService = {
|
|
120
|
+
async initialize() {
|
|
121
|
+
// Configure the plugin
|
|
122
|
+
await NativeUpdate.configure({
|
|
123
|
+
updateUrl: 'https://nativeupdate.aoneahsan.com/api/updates',
|
|
124
|
+
autoCheck: true,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Set up event listeners
|
|
128
|
+
NativeUpdate.addListener('updateAvailable', (info) => {
|
|
129
|
+
console.log('Update available:', info.version);
|
|
130
|
+
this.promptUpdate(info);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
NativeUpdate.addListener('downloadProgress', (progress) => {
|
|
134
|
+
console.log(`Download: ${progress.percent}%`);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
NativeUpdate.addListener('updateReady', () => {
|
|
138
|
+
console.log('Update ready to install');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Check for updates on startup
|
|
142
|
+
await this.checkForUpdates();
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
async checkForUpdates() {
|
|
146
|
+
try {
|
|
147
|
+
const result = await NativeUpdate.sync();
|
|
148
|
+
|
|
149
|
+
if (result.status === 'UPDATE_AVAILABLE') {
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return null;
|
|
154
|
+
} catch (error) {
|
|
155
|
+
console.error('Update check failed:', error);
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
async promptUpdate(updateInfo: any) {
|
|
161
|
+
// Show your custom UI or use native dialog
|
|
162
|
+
const shouldUpdate = confirm(
|
|
163
|
+
`Version ${updateInfo.version} is available. Update now?`
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
if (shouldUpdate) {
|
|
167
|
+
await this.downloadAndInstall();
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
async downloadAndInstall() {
|
|
172
|
+
try {
|
|
173
|
+
// Download is handled by sync, just reload
|
|
174
|
+
await NativeUpdate.reload();
|
|
175
|
+
} catch (error) {
|
|
176
|
+
console.error('Update failed:', error);
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Step 2.4: Initialize on App Start
|
|
183
|
+
|
|
184
|
+
In your main app file (e.g., `App.tsx` or `main.ts`):
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
import { updateService } from './services/update-service';
|
|
188
|
+
|
|
189
|
+
// Initialize updates when app starts
|
|
190
|
+
async function initApp() {
|
|
191
|
+
await updateService.initialize();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
initApp();
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Step 2.5: Build Your App
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
# Build web assets
|
|
201
|
+
yarn build
|
|
202
|
+
|
|
203
|
+
# Sync to native platforms
|
|
204
|
+
npx cap sync
|
|
205
|
+
|
|
206
|
+
# Run on device/simulator to test
|
|
207
|
+
npx cap run ios
|
|
208
|
+
# or
|
|
209
|
+
npx cap run android
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Phase 3: First Upload (5 minutes)
|
|
213
|
+
|
|
214
|
+
### Step 3.1: Build for Upload
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
# Create production build
|
|
218
|
+
yarn build
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Step 3.2: Upload via Dashboard
|
|
222
|
+
|
|
223
|
+
1. Go to **Dashboard > Upload**
|
|
224
|
+
2. Select your app
|
|
225
|
+
3. Select **development** channel (for initial testing)
|
|
226
|
+
4. Select your platform
|
|
227
|
+
5. Upload your `dist` folder as a ZIP:
|
|
228
|
+
```bash
|
|
229
|
+
# Create ZIP of your build
|
|
230
|
+
cd dist && zip -r ../build-v1.0.0.zip . && cd ..
|
|
231
|
+
```
|
|
232
|
+
6. Enter version details:
|
|
233
|
+
```
|
|
234
|
+
Version: 1.0.0
|
|
235
|
+
Build Number: 1
|
|
236
|
+
Release Notes: Initial release with OTA support
|
|
237
|
+
Release Type: major
|
|
238
|
+
```
|
|
239
|
+
7. Click **Upload Build**
|
|
240
|
+
|
|
241
|
+
### Step 3.3: Verify Upload
|
|
242
|
+
|
|
243
|
+
1. Go to **Dashboard > Builds**
|
|
244
|
+
2. Confirm your build appears with status "active"
|
|
245
|
+
|
|
246
|
+
## Phase 4: Testing (10 minutes)
|
|
247
|
+
|
|
248
|
+
### Step 4.1: Test on Development Channel
|
|
249
|
+
|
|
250
|
+
1. Ensure your app is configured for `channel: 'development'`
|
|
251
|
+
2. Run the app on a device/simulator
|
|
252
|
+
3. The app should detect no updates (you're on latest)
|
|
253
|
+
|
|
254
|
+
### Step 4.2: Make a Change
|
|
255
|
+
|
|
256
|
+
1. Edit something visible in your app (e.g., change a title)
|
|
257
|
+
2. Rebuild: `yarn build`
|
|
258
|
+
3. Upload as version 1.0.1 to development channel
|
|
259
|
+
|
|
260
|
+
### Step 4.3: Verify Update Works
|
|
261
|
+
|
|
262
|
+
1. Reopen your app (or wait for auto-check)
|
|
263
|
+
2. App should detect update available
|
|
264
|
+
3. Accept the update
|
|
265
|
+
4. App reloads with new changes
|
|
266
|
+
5. Verify your change is visible
|
|
267
|
+
|
|
268
|
+
### Step 4.4: Test Rollback (Optional)
|
|
269
|
+
|
|
270
|
+
1. Make a breaking change that crashes the app
|
|
271
|
+
2. Upload as version 1.0.2
|
|
272
|
+
3. The plugin should automatically rollback to 1.0.1
|
|
273
|
+
|
|
274
|
+
## Phase 5: Staging Deployment
|
|
275
|
+
|
|
276
|
+
### Step 5.1: Upload to Staging
|
|
277
|
+
|
|
278
|
+
1. Once development testing passes
|
|
279
|
+
2. Go to **Dashboard > Upload**
|
|
280
|
+
3. Upload the same build to **staging** channel
|
|
281
|
+
4. Version: Keep same version (1.0.1)
|
|
282
|
+
|
|
283
|
+
### Step 5.2: Configure Staging Testers
|
|
284
|
+
|
|
285
|
+
1. Build app with `channel: 'staging'`
|
|
286
|
+
2. Distribute to QA team via TestFlight/Firebase Distribution
|
|
287
|
+
3. They will receive staging updates
|
|
288
|
+
|
|
289
|
+
### Step 5.3: QA Validation
|
|
290
|
+
|
|
291
|
+
Have testers verify:
|
|
292
|
+
- [ ] Update is detected
|
|
293
|
+
- [ ] Download completes successfully
|
|
294
|
+
- [ ] App reloads correctly
|
|
295
|
+
- [ ] All features work as expected
|
|
296
|
+
- [ ] No crashes or errors
|
|
297
|
+
|
|
298
|
+
## Phase 6: Production Deployment
|
|
299
|
+
|
|
300
|
+
### Step 6.1: Upload to Production
|
|
301
|
+
|
|
302
|
+
1. Go to **Dashboard > Upload**
|
|
303
|
+
2. Upload to **production** channel
|
|
304
|
+
3. Double-check version and release notes
|
|
305
|
+
|
|
306
|
+
### Step 6.2: Create Gradual Rollout
|
|
307
|
+
|
|
308
|
+
1. Go to **Dashboard > Rollouts**
|
|
309
|
+
2. Click **Create Rollout**
|
|
310
|
+
3. Select your app and production channel
|
|
311
|
+
4. Set initial percentage: **10%**
|
|
312
|
+
5. Click **Start Rollout**
|
|
313
|
+
|
|
314
|
+
### Step 6.3: Monitor Initial Rollout
|
|
315
|
+
|
|
316
|
+
Wait 24-48 hours and check:
|
|
317
|
+
- **Analytics**: View download and install rates
|
|
318
|
+
- **Errors**: Check for any reported errors
|
|
319
|
+
- **Rollbacks**: Monitor rollback rate (should be <1%)
|
|
320
|
+
|
|
321
|
+
### Step 6.4: Expand Rollout
|
|
322
|
+
|
|
323
|
+
If metrics are healthy:
|
|
324
|
+
1. Go to **Dashboard > Rollouts**
|
|
325
|
+
2. Increase to **50%**
|
|
326
|
+
3. Monitor for another 24 hours
|
|
327
|
+
4. Increase to **100%** to complete
|
|
328
|
+
|
|
329
|
+
## Phase 7: Ongoing Maintenance
|
|
330
|
+
|
|
331
|
+
### Regular Update Cycle
|
|
332
|
+
|
|
333
|
+
```
|
|
334
|
+
1. Develop features/fixes
|
|
335
|
+
↓
|
|
336
|
+
2. Build and test locally
|
|
337
|
+
↓
|
|
338
|
+
3. Upload to development
|
|
339
|
+
↓
|
|
340
|
+
4. Internal testing
|
|
341
|
+
↓
|
|
342
|
+
5. Upload to staging
|
|
343
|
+
↓
|
|
344
|
+
6. QA testing
|
|
345
|
+
↓
|
|
346
|
+
7. Upload to production
|
|
347
|
+
↓
|
|
348
|
+
8. Gradual rollout (10% → 50% → 100%)
|
|
349
|
+
↓
|
|
350
|
+
9. Monitor analytics
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Monitoring Checklist
|
|
354
|
+
|
|
355
|
+
Daily:
|
|
356
|
+
- [ ] Check error rates in Analytics
|
|
357
|
+
- [ ] Review any reported issues
|
|
358
|
+
- [ ] Monitor rollback rates
|
|
359
|
+
|
|
360
|
+
Weekly:
|
|
361
|
+
- [ ] Review overall success rates
|
|
362
|
+
- [ ] Plan upcoming updates
|
|
363
|
+
- [ ] Archive old builds
|
|
364
|
+
|
|
365
|
+
Monthly:
|
|
366
|
+
- [ ] Audit channel configurations
|
|
367
|
+
- [ ] Review security settings
|
|
368
|
+
- [ ] Check storage usage
|
|
369
|
+
|
|
370
|
+
## Complete Code Example
|
|
371
|
+
|
|
372
|
+
Here's a complete React example:
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
// src/App.tsx
|
|
376
|
+
import { useEffect, useState } from 'react';
|
|
377
|
+
import { NativeUpdate } from 'native-update';
|
|
378
|
+
|
|
379
|
+
function App() {
|
|
380
|
+
const [updateStatus, setUpdateStatus] = useState<string>('Checking...');
|
|
381
|
+
const [version, setVersion] = useState<string>('1.0.0');
|
|
382
|
+
|
|
383
|
+
useEffect(() => {
|
|
384
|
+
initializeUpdates();
|
|
385
|
+
}, []);
|
|
386
|
+
|
|
387
|
+
async function initializeUpdates() {
|
|
388
|
+
try {
|
|
389
|
+
// Configure
|
|
390
|
+
await NativeUpdate.configure({
|
|
391
|
+
updateUrl: 'https://nativeupdate.aoneahsan.com/api/updates',
|
|
392
|
+
autoCheck: true,
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
// Get current version
|
|
396
|
+
const info = await NativeUpdate.getCurrentBundle();
|
|
397
|
+
setVersion(info.version || '1.0.0');
|
|
398
|
+
|
|
399
|
+
// Set up listeners
|
|
400
|
+
NativeUpdate.addListener('updateAvailable', (info) => {
|
|
401
|
+
setUpdateStatus(`Update ${info.version} available!`);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
NativeUpdate.addListener('downloadProgress', (progress) => {
|
|
405
|
+
setUpdateStatus(`Downloading: ${progress.percent}%`);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
// Check for updates
|
|
409
|
+
const result = await NativeUpdate.sync();
|
|
410
|
+
|
|
411
|
+
if (result.status === 'UPDATE_AVAILABLE') {
|
|
412
|
+
setUpdateStatus('Update available');
|
|
413
|
+
} else if (result.status === 'UP_TO_DATE') {
|
|
414
|
+
setUpdateStatus('Up to date');
|
|
415
|
+
}
|
|
416
|
+
} catch (error) {
|
|
417
|
+
setUpdateStatus('Error checking updates');
|
|
418
|
+
console.error(error);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
async function handleUpdate() {
|
|
423
|
+
try {
|
|
424
|
+
setUpdateStatus('Installing...');
|
|
425
|
+
await NativeUpdate.reload();
|
|
426
|
+
} catch (error) {
|
|
427
|
+
setUpdateStatus('Update failed');
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return (
|
|
432
|
+
<div className="app">
|
|
433
|
+
<h1>My App v{version}</h1>
|
|
434
|
+
<p>Status: {updateStatus}</p>
|
|
435
|
+
|
|
436
|
+
{updateStatus.includes('available') && (
|
|
437
|
+
<button onClick={handleUpdate}>
|
|
438
|
+
Install Update
|
|
439
|
+
</button>
|
|
440
|
+
)}
|
|
441
|
+
|
|
442
|
+
{/* Change this text to test OTA updates! */}
|
|
443
|
+
<p>Welcome to my awesome app!</p>
|
|
444
|
+
</div>
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
export default App;
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
## Troubleshooting
|
|
452
|
+
|
|
453
|
+
### Update Not Detected
|
|
454
|
+
|
|
455
|
+
1. Check network connectivity
|
|
456
|
+
2. Verify channel configuration matches
|
|
457
|
+
3. Check build is uploaded to correct channel
|
|
458
|
+
4. Try manual sync: `await NativeUpdate.sync()`
|
|
459
|
+
|
|
460
|
+
### Download Fails
|
|
461
|
+
|
|
462
|
+
1. Check internet connection
|
|
463
|
+
2. Verify Google Drive is connected
|
|
464
|
+
3. Check file isn't corrupted
|
|
465
|
+
4. Try re-uploading the build
|
|
466
|
+
|
|
467
|
+
### App Crashes After Update
|
|
468
|
+
|
|
469
|
+
1. Plugin should auto-rollback
|
|
470
|
+
2. Check error logs
|
|
471
|
+
3. Verify build was tested properly
|
|
472
|
+
4. Upload a fixed version
|
|
473
|
+
|
|
474
|
+
### Rollout Not Progressing
|
|
475
|
+
|
|
476
|
+
1. Check rollout status in dashboard
|
|
477
|
+
2. Verify percentage is set correctly
|
|
478
|
+
3. Ensure enough users to hit percentage
|
|
479
|
+
|
|
480
|
+
## Next Steps
|
|
481
|
+
|
|
482
|
+
- [Channel Management](./channel-management.md) - Advanced channel configuration
|
|
483
|
+
- [Dashboard Guide](./dashboard-guide.md) - Full dashboard documentation
|
|
484
|
+
- [Security Best Practices](./security-best-practices.md) - Secure your updates
|
|
485
|
+
- [API Reference](../api/API.md) - Complete API documentation
|
|
486
|
+
|
|
487
|
+
## Support
|
|
488
|
+
|
|
489
|
+
Need help? Contact us:
|
|
490
|
+
- Email: aoneahsan@gmail.com
|
|
491
|
+
- Website: [nativeupdate.aoneahsan.com](https://nativeupdate.aoneahsan.com)
|