react-native-codepush-sdk 1.0.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.
@@ -0,0 +1,181 @@
1
+ import { CodePushUpdate, CodePushDeployment, CodePushSyncStatus, UpdateHistory } from '../types/codepush';
2
+
3
+ // Mock service for demonstration - replace with actual CodePush SDK calls
4
+ class CodePushService {
5
+ private mockDeployments: CodePushDeployment[] = [
6
+ {
7
+ name: 'Production',
8
+ key: 'prod-key-12345678',
9
+ package: {
10
+ id: '1',
11
+ label: 'v1.2.3',
12
+ description: 'Bug fixes and performance improvements',
13
+ packageHash: 'abc123def456',
14
+ blobUrl: 'https://codepush.blob.core.windows.net/package1',
15
+ downloadUrl: 'https://codepush.blob.core.windows.net/package1',
16
+ packageSize: 2048576,
17
+ deploymentKey: 'prod-key-12345678',
18
+ isFirstRun: false,
19
+ failedInstall: false,
20
+ isMandatory: false,
21
+ timestamp: Date.now() - 86400000,
22
+ version: '1.2.3'
23
+ }
24
+ },
25
+ {
26
+ name: 'Staging',
27
+ key: 'staging-key-87654321',
28
+ package: {
29
+ id: '2',
30
+ label: 'v1.2.4-beta',
31
+ description: 'New feature preview and experimental changes',
32
+ packageHash: 'def456ghi789',
33
+ blobUrl: 'https://codepush.blob.core.windows.net/package2',
34
+ downloadUrl: 'https://codepush.blob.core.windows.net/package2',
35
+ packageSize: 3145728,
36
+ deploymentKey: 'staging-key-87654321',
37
+ isFirstRun: false,
38
+ failedInstall: false,
39
+ isMandatory: true,
40
+ timestamp: Date.now() - 3600000,
41
+ version: '1.2.4'
42
+ }
43
+ },
44
+ {
45
+ name: 'Development',
46
+ key: 'dev-key-11223344',
47
+ }
48
+ ];
49
+
50
+ private mockHistory: UpdateHistory[] = [
51
+ {
52
+ id: '1',
53
+ version: '1.2.3',
54
+ timestamp: Date.now() - 86400000,
55
+ status: 'SUCCESS',
56
+ description: 'Bug fixes and performance improvements',
57
+ downloadSize: 2048576,
58
+ },
59
+ {
60
+ id: '2',
61
+ version: '1.2.2',
62
+ timestamp: Date.now() - 172800000,
63
+ status: 'SUCCESS',
64
+ description: 'UI improvements and new dashboard features',
65
+ downloadSize: 1536000,
66
+ },
67
+ {
68
+ id: '3',
69
+ version: '1.2.1',
70
+ timestamp: Date.now() - 259200000,
71
+ status: 'ROLLBACK',
72
+ description: 'Critical bug fix rollback',
73
+ downloadSize: 1024000,
74
+ },
75
+ {
76
+ id: '4',
77
+ version: '1.2.0',
78
+ timestamp: Date.now() - 345600000,
79
+ status: 'FAILED',
80
+ description: 'Major feature update with new authentication',
81
+ downloadSize: 4096000,
82
+ }
83
+ ];
84
+
85
+ async checkForUpdate(): Promise<CodePushUpdate | null> {
86
+ // Simulate API call delay
87
+ await new Promise(resolve => setTimeout(resolve, 1500));
88
+
89
+ // 70% chance of no update, 30% chance of update available
90
+ if (Math.random() > 0.3) {
91
+ return null;
92
+ }
93
+
94
+ return {
95
+ id: 'update-' + Date.now(),
96
+ label: 'v1.2.5',
97
+ description: 'Security updates and bug fixes',
98
+ packageHash: 'new-hash-123',
99
+ blobUrl: 'https://codepush.blob.core.windows.net/new-package',
100
+ downloadUrl: 'https://codepush.blob.core.windows.net/new-package',
101
+ packageSize: 1843200,
102
+ deploymentKey: 'prod-key-12345678',
103
+ isFirstRun: false,
104
+ failedInstall: false,
105
+ isMandatory: false,
106
+ timestamp: Date.now(),
107
+ version: '1.2.5'
108
+ };
109
+ }
110
+
111
+ async downloadUpdate(
112
+ update: CodePushUpdate,
113
+ onProgress: (progress: number) => void
114
+ ): Promise<boolean> {
115
+ // Simulate download progress
116
+ for (let i = 0; i <= 100; i += 5) {
117
+ await new Promise(resolve => setTimeout(resolve, 100));
118
+ onProgress(i);
119
+ }
120
+ return true;
121
+ }
122
+
123
+ async installUpdate(): Promise<boolean> {
124
+ // Simulate installation
125
+ await new Promise(resolve => setTimeout(resolve, 2000));
126
+ return true;
127
+ }
128
+
129
+ async sync(onStatusChange: (status: CodePushSyncStatus) => void): Promise<boolean> {
130
+ onStatusChange({ status: 'CHECKING_FOR_UPDATE' });
131
+ await new Promise(resolve => setTimeout(resolve, 1000));
132
+
133
+ const update = await this.checkForUpdate();
134
+
135
+ if (!update) {
136
+ onStatusChange({ status: 'UP_TO_DATE' });
137
+ return false;
138
+ }
139
+
140
+ onStatusChange({ status: 'DOWNLOADING_PACKAGE', progress: 0 });
141
+
142
+ const downloadSuccess = await this.downloadUpdate(update, (progress) => {
143
+ onStatusChange({ status: 'DOWNLOADING_PACKAGE', progress });
144
+ });
145
+
146
+ if (!downloadSuccess) {
147
+ onStatusChange({ status: 'UNKNOWN_ERROR' });
148
+ return false;
149
+ }
150
+
151
+ onStatusChange({ status: 'INSTALLING_UPDATE' });
152
+ const installSuccess = await this.installUpdate();
153
+
154
+ if (installSuccess) {
155
+ onStatusChange({ status: 'UPDATE_INSTALLED' });
156
+ return true;
157
+ } else {
158
+ onStatusChange({ status: 'UNKNOWN_ERROR' });
159
+ return false;
160
+ }
161
+ }
162
+
163
+ async getDeployments(): Promise<CodePushDeployment[]> {
164
+ // Simulate API call
165
+ await new Promise(resolve => setTimeout(resolve, 500));
166
+ return this.mockDeployments;
167
+ }
168
+
169
+ async getUpdateHistory(): Promise<UpdateHistory[]> {
170
+ // Simulate API call
171
+ await new Promise(resolve => setTimeout(resolve, 300));
172
+ return this.mockHistory;
173
+ }
174
+
175
+ async getCurrentPackageInfo(): Promise<CodePushUpdate | null> {
176
+ // Return current package info
177
+ return this.mockDeployments[0].package || null;
178
+ }
179
+ }
180
+
181
+ export const codePushService = new CodePushService();
@@ -0,0 +1,303 @@
1
+ import React from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ StyleSheet,
6
+ TouchableOpacity,
7
+ ActivityIndicator,
8
+ Alert,
9
+ } from 'react-native';
10
+ import { useCodePush } from '../sdk/CodePushProvider';
11
+
12
+ const UpdateChecker: React.FC = () => {
13
+ const {
14
+ currentUpdate,
15
+ availableUpdate,
16
+ syncStatus,
17
+ isChecking,
18
+ isDownloading,
19
+ isInstalling,
20
+ checkForUpdate,
21
+ syncUpdate,
22
+ rollback,
23
+ clearUpdates,
24
+ } = useCodePush();
25
+
26
+ const getStatusMessage = () => {
27
+ if (isChecking) return 'Checking for updates...';
28
+ if (isDownloading) return 'Downloading update...';
29
+ if (isInstalling) return 'Installing update...';
30
+ if (availableUpdate) return 'Update available!';
31
+ return 'App is up to date';
32
+ };
33
+
34
+ const getStatusColor = () => {
35
+ if (isChecking || isDownloading || isInstalling) return '#007bff';
36
+ if (availableUpdate) return '#ffc107';
37
+ return '#28a745';
38
+ };
39
+
40
+ const handleRollback = () => {
41
+ Alert.alert(
42
+ 'Rollback Update',
43
+ 'Are you sure you want to rollback to the previous version? This will restart the app.',
44
+ [
45
+ { text: 'Cancel', style: 'cancel' },
46
+ { text: 'Rollback', style: 'destructive', onPress: rollback },
47
+ ]
48
+ );
49
+ };
50
+
51
+ const handleClearUpdates = () => {
52
+ Alert.alert(
53
+ 'Clear Updates',
54
+ 'This will remove all downloaded updates and reset to the original app version.',
55
+ [
56
+ { text: 'Cancel', style: 'cancel' },
57
+ { text: 'Clear', style: 'destructive', onPress: clearUpdates },
58
+ ]
59
+ );
60
+ };
61
+
62
+ const formatBytes = (bytes: number) => {
63
+ if (bytes === 0) return '0 Bytes';
64
+ const k = 1024;
65
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
66
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
67
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
68
+ };
69
+
70
+ return (
71
+ <View style={styles.container}>
72
+ <View style={styles.header}>
73
+ <Text style={styles.title}>Custom CodePush</Text>
74
+ <Text style={styles.subtitle}>Over-the-Air Updates</Text>
75
+ </View>
76
+
77
+ {/* Status Card */}
78
+ <View style={[styles.statusCard, { borderLeftColor: getStatusColor() }]}>
79
+ <View style={styles.statusHeader}>
80
+ <Text style={[styles.statusText, { color: getStatusColor() }]}>
81
+ {getStatusMessage()}
82
+ </Text>
83
+ {(isChecking || isDownloading || isInstalling) && (
84
+ <ActivityIndicator size="small" color={getStatusColor()} />
85
+ )}
86
+ </View>
87
+
88
+ {syncStatus?.progress !== undefined && (
89
+ <View style={styles.progressContainer}>
90
+ <View style={styles.progressBar}>
91
+ <View
92
+ style={[
93
+ styles.progressFill,
94
+ {
95
+ width: `${syncStatus.progress}%`,
96
+ backgroundColor: getStatusColor(),
97
+ },
98
+ ]}
99
+ />
100
+ </View>
101
+ <Text style={styles.progressText}>{Math.round(syncStatus.progress)}%</Text>
102
+ </View>
103
+ )}
104
+ </View>
105
+
106
+ {/* Current Version Info */}
107
+ {currentUpdate && (
108
+ <View style={styles.infoCard}>
109
+ <Text style={styles.cardTitle}>Current Version</Text>
110
+ <Text style={styles.infoText}>Label: {currentUpdate.label}</Text>
111
+ <Text style={styles.infoText}>Version: {currentUpdate.appVersion}</Text>
112
+ <Text style={styles.infoText}>Hash: {currentUpdate.packageHash.substring(0, 8)}...</Text>
113
+ <Text style={styles.infoText}>Size: {formatBytes(currentUpdate.packageSize)}</Text>
114
+ {currentUpdate.description && (
115
+ <Text style={styles.infoDescription}>{currentUpdate.description}</Text>
116
+ )}
117
+ </View>
118
+ )}
119
+
120
+ {/* Available Update Info */}
121
+ {availableUpdate && (
122
+ <View style={styles.infoCard}>
123
+ <Text style={styles.cardTitle}>Available Update</Text>
124
+ <Text style={styles.infoText}>Label: {availableUpdate.label}</Text>
125
+ <Text style={styles.infoText}>Version: {availableUpdate.appVersion}</Text>
126
+ <Text style={styles.infoText}>Size: {formatBytes(availableUpdate.packageSize)}</Text>
127
+ <Text style={[
128
+ styles.infoText,
129
+ { color: availableUpdate.isMandatory ? '#dc3545' : '#28a745' }
130
+ ]}>
131
+ {availableUpdate.isMandatory ? 'Mandatory Update' : 'Optional Update'}
132
+ </Text>
133
+ {availableUpdate.description && (
134
+ <Text style={styles.infoDescription}>{availableUpdate.description}</Text>
135
+ )}
136
+ </View>
137
+ )}
138
+
139
+ {/* Action Buttons */}
140
+ <View style={styles.buttonContainer}>
141
+ <TouchableOpacity
142
+ style={[styles.button, styles.primaryButton]}
143
+ onPress={checkForUpdate}
144
+ disabled={isChecking}
145
+ >
146
+ <Text style={styles.buttonText}>Check for Updates</Text>
147
+ </TouchableOpacity>
148
+
149
+ {availableUpdate && (
150
+ <TouchableOpacity
151
+ style={[styles.button, styles.successButton]}
152
+ onPress={syncUpdate}
153
+ disabled={isDownloading || isInstalling}
154
+ >
155
+ <Text style={styles.buttonText}>
156
+ {availableUpdate.isMandatory ? 'Install Now (Required)' : 'Install Update'}
157
+ </Text>
158
+ </TouchableOpacity>
159
+ )}
160
+
161
+ {currentUpdate && (
162
+ <TouchableOpacity
163
+ style={[styles.button, styles.warningButton]}
164
+ onPress={handleRollback}
165
+ disabled={isDownloading || isInstalling}
166
+ >
167
+ <Text style={styles.buttonText}>Rollback</Text>
168
+ </TouchableOpacity>
169
+ )}
170
+
171
+ <TouchableOpacity
172
+ style={[styles.button, styles.dangerButton]}
173
+ onPress={handleClearUpdates}
174
+ disabled={isDownloading || isInstalling}
175
+ >
176
+ <Text style={styles.buttonText}>Clear All Updates</Text>
177
+ </TouchableOpacity>
178
+ </View>
179
+ </View>
180
+ );
181
+ };
182
+
183
+ const styles = StyleSheet.create({
184
+ container: {
185
+ flex: 1,
186
+ padding: 20,
187
+ backgroundColor: '#f8f9fa',
188
+ },
189
+ header: {
190
+ alignItems: 'center',
191
+ marginBottom: 30,
192
+ },
193
+ title: {
194
+ fontSize: 28,
195
+ fontWeight: 'bold',
196
+ color: '#212529',
197
+ marginBottom: 8,
198
+ },
199
+ subtitle: {
200
+ fontSize: 16,
201
+ color: '#6c757d',
202
+ },
203
+ statusCard: {
204
+ backgroundColor: '#ffffff',
205
+ borderRadius: 8,
206
+ padding: 20,
207
+ marginBottom: 20,
208
+ borderLeftWidth: 4,
209
+ shadowColor: '#000',
210
+ shadowOffset: { width: 0, height: 2 },
211
+ shadowOpacity: 0.1,
212
+ shadowRadius: 4,
213
+ elevation: 3,
214
+ },
215
+ statusHeader: {
216
+ flexDirection: 'row',
217
+ justifyContent: 'space-between',
218
+ alignItems: 'center',
219
+ },
220
+ statusText: {
221
+ fontSize: 18,
222
+ fontWeight: '600',
223
+ },
224
+ progressContainer: {
225
+ marginTop: 15,
226
+ flexDirection: 'row',
227
+ alignItems: 'center',
228
+ },
229
+ progressBar: {
230
+ flex: 1,
231
+ height: 8,
232
+ backgroundColor: '#e9ecef',
233
+ borderRadius: 4,
234
+ marginRight: 10,
235
+ },
236
+ progressFill: {
237
+ height: '100%',
238
+ borderRadius: 4,
239
+ },
240
+ progressText: {
241
+ fontSize: 14,
242
+ fontWeight: '500',
243
+ color: '#495057',
244
+ minWidth: 40,
245
+ },
246
+ infoCard: {
247
+ backgroundColor: '#ffffff',
248
+ borderRadius: 8,
249
+ padding: 16,
250
+ marginBottom: 16,
251
+ shadowColor: '#000',
252
+ shadowOffset: { width: 0, height: 1 },
253
+ shadowOpacity: 0.1,
254
+ shadowRadius: 2,
255
+ elevation: 2,
256
+ },
257
+ cardTitle: {
258
+ fontSize: 18,
259
+ fontWeight: '600',
260
+ color: '#212529',
261
+ marginBottom: 12,
262
+ },
263
+ infoText: {
264
+ fontSize: 14,
265
+ color: '#495057',
266
+ marginBottom: 4,
267
+ },
268
+ infoDescription: {
269
+ fontSize: 14,
270
+ color: '#6c757d',
271
+ marginTop: 8,
272
+ fontStyle: 'italic',
273
+ },
274
+ buttonContainer: {
275
+ marginTop: 20,
276
+ },
277
+ button: {
278
+ paddingVertical: 12,
279
+ paddingHorizontal: 20,
280
+ borderRadius: 8,
281
+ marginBottom: 12,
282
+ alignItems: 'center',
283
+ },
284
+ primaryButton: {
285
+ backgroundColor: '#007bff',
286
+ },
287
+ successButton: {
288
+ backgroundColor: '#28a745',
289
+ },
290
+ warningButton: {
291
+ backgroundColor: '#ffc107',
292
+ },
293
+ dangerButton: {
294
+ backgroundColor: '#dc3545',
295
+ },
296
+ buttonText: {
297
+ color: '#ffffff',
298
+ fontSize: 16,
299
+ fontWeight: '600',
300
+ },
301
+ });
302
+
303
+ export default UpdateChecker;
@@ -0,0 +1,184 @@
1
+ import React, { createContext, useContext, useEffect, useState, ReactNode } from 'react';
2
+ import { AppState, AppStateStatus } from 'react-native';
3
+ import CustomCodePush, { CodePushConfiguration, UpdatePackage, SyncStatus } from './CustomCodePush';
4
+
5
+ interface CodePushContextType {
6
+ codePush: CustomCodePush | null;
7
+ currentUpdate: UpdatePackage | null;
8
+ availableUpdate: UpdatePackage | null;
9
+ syncStatus: SyncStatus | null;
10
+ isChecking: boolean;
11
+ isDownloading: boolean;
12
+ isInstalling: boolean;
13
+ checkForUpdate: () => Promise<void>;
14
+ syncUpdate: () => Promise<void>;
15
+ rollback: () => Promise<void>;
16
+ clearUpdates: () => Promise<void>;
17
+ getBundleUrl: () => string | null;
18
+ }
19
+
20
+ const CodePushContext = createContext<CodePushContextType | undefined>(undefined);
21
+
22
+ interface CodePushProviderProps {
23
+ children: ReactNode;
24
+ config: CodePushConfiguration;
25
+ autoCheck?: boolean;
26
+ checkOnResume?: boolean;
27
+ }
28
+
29
+ export const CodePushProvider: React.FC<CodePushProviderProps> = ({
30
+ children,
31
+ config,
32
+ autoCheck = true,
33
+ checkOnResume = true,
34
+ }) => {
35
+ const [codePush] = useState(() => new CustomCodePush(config));
36
+ const [currentUpdate, setCurrentUpdate] = useState<UpdatePackage | null>(null);
37
+ const [availableUpdate, setAvailableUpdate] = useState<UpdatePackage | null>(null);
38
+ const [syncStatus, setSyncStatus] = useState<SyncStatus | null>(null);
39
+ const [isChecking, setIsChecking] = useState(false);
40
+ const [isDownloading, setIsDownloading] = useState(false);
41
+ const [isInstalling, setIsInstalling] = useState(false);
42
+
43
+ useEffect(() => {
44
+ // Wait for SDK to initialize (load stored package) before updating state
45
+ codePush.initialize()
46
+ .then(async () => {
47
+ await loadCurrentUpdate();
48
+ if (autoCheck) {
49
+ await checkForUpdate();
50
+ }
51
+ })
52
+ .catch(error => {
53
+ console.error('Error initializing CodePush SDK:', error);
54
+ });
55
+
56
+ // Set up app state listener for resume checks
57
+ if (checkOnResume) {
58
+ const handleAppStateChange = (nextAppState: AppStateStatus) => {
59
+ if (nextAppState === 'active') {
60
+ checkForUpdate();
61
+ }
62
+ };
63
+
64
+ const subscription = AppState.addEventListener('change', handleAppStateChange);
65
+ return () => subscription?.remove();
66
+ }
67
+ }, []);
68
+
69
+ const loadCurrentUpdate = async () => {
70
+ try {
71
+ const current = await codePush.getCurrentPackage();
72
+ setCurrentUpdate(current);
73
+ } catch (error) {
74
+ console.error('Failed to load current update:', error);
75
+ }
76
+ };
77
+
78
+ const checkForUpdate = async () => {
79
+ if (isChecking) return;
80
+
81
+ setIsChecking(true);
82
+ try {
83
+ const update = await codePush.checkForUpdate();
84
+ setAvailableUpdate(update);
85
+ } catch (error) {
86
+ console.error('Failed to check for update:', error);
87
+ } finally {
88
+ setIsChecking(false);
89
+ }
90
+ };
91
+
92
+ const syncUpdate = async () => {
93
+ if (!availableUpdate || isDownloading || isInstalling) return;
94
+
95
+ try {
96
+ const success = await codePush.sync(
97
+ {
98
+ installMode: 'ON_NEXT_RESTART',
99
+ mandatoryInstallMode: 'ON_NEXT_RESTART',
100
+ },
101
+ (status) => {
102
+ setSyncStatus(status);
103
+
104
+ switch (status.status) {
105
+ case 'DOWNLOADING_PACKAGE':
106
+ setIsDownloading(true);
107
+ setIsInstalling(false);
108
+ break;
109
+ case 'INSTALLING_UPDATE':
110
+ setIsDownloading(false);
111
+ setIsInstalling(true);
112
+ break;
113
+ case 'UPDATE_INSTALLED':
114
+ case 'UP_TO_DATE':
115
+ case 'UNKNOWN_ERROR':
116
+ setIsDownloading(false);
117
+ setIsInstalling(false);
118
+ break;
119
+ }
120
+ },
121
+ (progress) => {
122
+ // Handle download progress if needed
123
+ }
124
+ );
125
+
126
+ if (success) {
127
+ setAvailableUpdate(null);
128
+ await loadCurrentUpdate();
129
+ }
130
+ } catch (error) {
131
+ console.error('Failed to sync update:', error);
132
+ setIsDownloading(false);
133
+ setIsInstalling(false);
134
+ }
135
+ };
136
+
137
+ const rollback = async () => {
138
+ try {
139
+ await codePush.rollback();
140
+ setCurrentUpdate(null);
141
+ } catch (error) {
142
+ console.error('Failed to rollback:', error);
143
+ }
144
+ };
145
+
146
+ const clearUpdates = async () => {
147
+ try {
148
+ await codePush.clearUpdates();
149
+ setCurrentUpdate(null);
150
+ setAvailableUpdate(null);
151
+ } catch (error) {
152
+ console.error('Failed to clear updates:', error);
153
+ }
154
+ };
155
+
156
+ const contextValue: CodePushContextType = {
157
+ codePush,
158
+ currentUpdate,
159
+ availableUpdate,
160
+ syncStatus,
161
+ isChecking,
162
+ isDownloading,
163
+ isInstalling,
164
+ checkForUpdate,
165
+ syncUpdate,
166
+ rollback,
167
+ clearUpdates,
168
+ getBundleUrl: () => codePush.getBundleUrl(),
169
+ };
170
+
171
+ return (
172
+ <CodePushContext.Provider value={contextValue}>
173
+ {children}
174
+ </CodePushContext.Provider>
175
+ );
176
+ };
177
+
178
+ export const useCodePush = (): CodePushContextType => {
179
+ const context = useContext(CodePushContext);
180
+ if (context === undefined) {
181
+ throw new Error('useCodePush must be used within a CodePushProvider');
182
+ }
183
+ return context;
184
+ };