react-native-stallion 2.1.0 → 2.2.0-alpha.2
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/android/src/main/java/com/stallion/events/StallionEventConstants.java +4 -1
- package/android/src/main/java/com/stallion/events/StallionEventManager.java +7 -0
- package/android/src/main/java/com/stallion/networkmanager/StallionDownloadCacheManager.java +48 -0
- package/android/src/main/java/com/stallion/networkmanager/StallionFileDownloader.java +33 -24
- package/android/src/main/java/com/stallion/networkmanager/StallionStageManager.java +12 -4
- package/android/src/main/java/com/stallion/networkmanager/StallionSyncHandler.java +47 -9
- package/android/src/main/java/com/stallion/storage/StallionConfig.java +40 -0
- package/android/src/main/java/com/stallion/storage/StallionConfigConstants.java +3 -0
- package/android/src/main/java/com/stallion/utils/StallionSignatureVerification.java +129 -0
- package/ios/main/Stallion.swift +1 -0
- package/ios/main/StallionConfig.h +4 -1
- package/ios/main/StallionConfig.m +9 -2
- package/ios/main/StallionConfigConstants.h +5 -1
- package/ios/main/StallionConfigConstants.m +5 -1
- package/ios/main/StallionConstants.swift +1 -0
- package/ios/main/StallionEventHandler.h +1 -1
- package/ios/main/StallionEventHandler.m +9 -3
- package/ios/main/StallionExceptionHandler.h +1 -1
- package/ios/main/StallionExceptionHandler.m +7 -2
- package/ios/main/StallionFileManager.h +1 -1
- package/ios/main/StallionFileManager.m +1 -1
- package/ios/main/StallionMeta.h +1 -1
- package/ios/main/StallionMeta.m +1 -1
- package/ios/main/StallionMetaConstants.h +1 -1
- package/ios/main/StallionMetaConstants.m +1 -1
- package/ios/main/StallionSignatureVerification.swift +135 -0
- package/ios/main/StallionSlotManager.h +1 -1
- package/ios/main/StallionSlotManager.m +1 -1
- package/ios/main/StallionStageManager.swift +1 -1
- package/ios/main/StallionStateManager.h +1 -1
- package/ios/main/StallionStateManager.m +1 -1
- package/ios/main/StallionSyncHandler.swift +52 -20
- package/package.json +1 -1
- package/src/main/utils/StallionNativeUtils.js +4 -1
- package/src/main/utils/StallionNativeUtils.js.map +1 -1
- package/types/main/utils/StallionNativeUtils.d.ts +1 -1
- package/types/main/utils/StallionNativeUtils.d.ts.map +1 -1
|
@@ -3,6 +3,7 @@ package com.stallion.events;
|
|
|
3
3
|
public class StallionEventConstants {
|
|
4
4
|
public enum NativeProdEventTypes {
|
|
5
5
|
DOWNLOAD_STARTED_PROD,
|
|
6
|
+
DOWNLOAD_RESUME_PROD,
|
|
6
7
|
DOWNLOAD_ERROR_PROD,
|
|
7
8
|
DOWNLOAD_COMPLETE_PROD,
|
|
8
9
|
SYNC_ERROR_PROD,
|
|
@@ -12,7 +13,8 @@ public class StallionEventConstants {
|
|
|
12
13
|
EXCEPTION_PROD,
|
|
13
14
|
AUTO_ROLLED_BACK_PROD,
|
|
14
15
|
CORRUPTED_FILE_ERROR,
|
|
15
|
-
FILE_MOUNTING_ERROR
|
|
16
|
+
FILE_MOUNTING_ERROR,
|
|
17
|
+
SIGNATURE_VERIFICATION_FAILED
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
public enum NativeStageEventTypes {
|
|
@@ -20,6 +22,7 @@ public class StallionEventConstants {
|
|
|
20
22
|
DOWNLOAD_COMPLETE_STAGE,
|
|
21
23
|
EXCEPTION_STAGE,
|
|
22
24
|
DOWNLOAD_STARTED_STAGE,
|
|
25
|
+
DOWNLOAD_RESUME_STAGE,
|
|
23
26
|
DOWNLOAD_ERROR_STAGE,
|
|
24
27
|
INSTALLED_STAGE,
|
|
25
28
|
}
|
|
@@ -18,6 +18,7 @@ public class StallionEventManager {
|
|
|
18
18
|
public static final String STALLION_NATIVE_EVENT_NAME = "STALLION_NATIVE_EVENT";
|
|
19
19
|
private static final String EVENTS_KEY = "stored_events";
|
|
20
20
|
private static final int MAX_BATCH_COUNT_SIZE = 9;
|
|
21
|
+
private static final int MAX_EVENT_STORAGE_LIMIT = 20;
|
|
21
22
|
|
|
22
23
|
private static StallionEventManager instance;
|
|
23
24
|
private final StallionStateManager stallionStateManager;
|
|
@@ -105,6 +106,12 @@ public class StallionEventManager {
|
|
|
105
106
|
try {
|
|
106
107
|
String eventsString = stallionStateManager.getString(EVENTS_KEY, "{}");
|
|
107
108
|
JSONObject eventsObject = new JSONObject(eventsString);
|
|
109
|
+
|
|
110
|
+
// Flush all if limit reached
|
|
111
|
+
if (eventsObject.length() >= MAX_EVENT_STORAGE_LIMIT) {
|
|
112
|
+
eventsObject = new JSONObject(); // reset
|
|
113
|
+
}
|
|
114
|
+
|
|
108
115
|
eventsObject.put(uniqueId, eventPayload.toString());
|
|
109
116
|
stallionStateManager.setString(EVENTS_KEY, eventsObject.toString());
|
|
110
117
|
} catch (JSONException e) {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
package com.stallion.networkmanager;
|
|
2
|
+
|
|
3
|
+
import com.stallion.storage.StallionConfig;
|
|
4
|
+
import com.stallion.utils.StallionFileManager;
|
|
5
|
+
|
|
6
|
+
import java.io.File;
|
|
7
|
+
import java.io.FileInputStream;
|
|
8
|
+
import java.io.FileOutputStream;
|
|
9
|
+
import java.util.Objects;
|
|
10
|
+
|
|
11
|
+
public class StallionDownloadCacheManager {
|
|
12
|
+
private static final String metaFilePath = "/download-cache.meta";
|
|
13
|
+
|
|
14
|
+
public static long getDownloadCache(StallionConfig config, String downloadUrl, String downloadPath) {
|
|
15
|
+
String lastDownloadingUrl = config.getLastDownloadingUrl();
|
|
16
|
+
long alreadyDownloaded = readMetaFile(downloadPath);
|
|
17
|
+
if(!Objects.equals(lastDownloadingUrl, downloadUrl) || alreadyDownloaded <= 0) {
|
|
18
|
+
config.setLastDownloadingUrl(downloadUrl);
|
|
19
|
+
StallionFileManager.deleteFileOrFolderSilently(new File(downloadPath));
|
|
20
|
+
return 0;
|
|
21
|
+
} else {
|
|
22
|
+
return alreadyDownloaded;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public static void saveDownloadCache(String path, long bytes) {
|
|
27
|
+
try (FileOutputStream fos = new FileOutputStream(path + metaFilePath)) {
|
|
28
|
+
fos.write(Long.toString(bytes).getBytes());
|
|
29
|
+
} catch (Exception ignored) {}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private static long readMetaFile(String path) {
|
|
33
|
+
File meta = new File(path + metaFilePath);
|
|
34
|
+
if (!meta.exists()) return 0;
|
|
35
|
+
try (FileInputStream fis = new FileInputStream(meta)) {
|
|
36
|
+
byte[] data = new byte[(int) meta.length()];
|
|
37
|
+
fis.read(data);
|
|
38
|
+
return Long.parseLong(new String(data));
|
|
39
|
+
} catch (Exception e) {
|
|
40
|
+
return 0;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public static void deleteDownloadCache(String path) {
|
|
45
|
+
File meta = new File(path + metaFilePath);
|
|
46
|
+
if (meta.exists()) meta.delete();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -8,6 +8,7 @@ import java.io.File;
|
|
|
8
8
|
import java.io.FileInputStream;
|
|
9
9
|
import java.io.FileOutputStream;
|
|
10
10
|
import java.io.IOException;
|
|
11
|
+
import java.io.RandomAccessFile;
|
|
11
12
|
import java.net.HttpURLConnection;
|
|
12
13
|
import java.net.URL;
|
|
13
14
|
import java.nio.ByteBuffer;
|
|
@@ -28,6 +29,7 @@ public class StallionFileDownloader {
|
|
|
28
29
|
public static void downloadBundle(
|
|
29
30
|
String downloadUrl,
|
|
30
31
|
String downloadDirectory,
|
|
32
|
+
long alreadyDownloaded,
|
|
31
33
|
StallionDownloadCallback stallionDownloadCallback
|
|
32
34
|
) {
|
|
33
35
|
executor.execute(() -> {
|
|
@@ -61,7 +63,7 @@ public class StallionFileDownloader {
|
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
// Download file
|
|
64
|
-
downloadFile(downloadUrl, downloadedZip, appToken, sdkToken, stallionDownloadCallback);
|
|
66
|
+
downloadFile(downloadUrl, downloadedZip, appToken, sdkToken, stallionDownloadCallback, alreadyDownloaded, downloadDirectory);
|
|
65
67
|
|
|
66
68
|
// Validate and unzip the downloaded file
|
|
67
69
|
validateAndUnzip(downloadedZip, downloadDirectory, stallionDownloadCallback);
|
|
@@ -75,7 +77,7 @@ public class StallionFileDownloader {
|
|
|
75
77
|
private static long getFileSize(String downloadUrl, String appToken, String apiKey) throws IOException {
|
|
76
78
|
HttpURLConnection connection = null;
|
|
77
79
|
try {
|
|
78
|
-
connection = setupConnection(downloadUrl, appToken, apiKey);
|
|
80
|
+
connection = setupConnection(downloadUrl, appToken, apiKey, 0);
|
|
79
81
|
return connection.getContentLength();
|
|
80
82
|
} finally {
|
|
81
83
|
if (connection != null) {
|
|
@@ -96,11 +98,10 @@ public class StallionFileDownloader {
|
|
|
96
98
|
|
|
97
99
|
private static File prepareForDownload(String downloadDirectory) throws IOException {
|
|
98
100
|
File downloadFolder = new File(downloadDirectory);
|
|
99
|
-
if (downloadFolder.exists()) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
throw new IOException("Failed to create download directory: " + downloadDirectory);
|
|
101
|
+
if (!downloadFolder.exists()) {
|
|
102
|
+
if (!downloadFolder.mkdirs()) {
|
|
103
|
+
throw new IOException("Failed to create download directory: " + downloadDirectory);
|
|
104
|
+
}
|
|
104
105
|
}
|
|
105
106
|
return new File(downloadFolder, StallionApiConstants.ZIP_FILE_NAME);
|
|
106
107
|
}
|
|
@@ -110,21 +111,21 @@ public class StallionFileDownloader {
|
|
|
110
111
|
File destinationFile,
|
|
111
112
|
String appToken,
|
|
112
113
|
String sdkToken,
|
|
113
|
-
StallionDownloadCallback callback
|
|
114
|
+
StallionDownloadCallback callback,
|
|
115
|
+
long alreadyDownloaded,
|
|
116
|
+
String downloadDirectory
|
|
114
117
|
) throws IOException {
|
|
115
|
-
HttpURLConnection connection =
|
|
118
|
+
HttpURLConnection connection = setupConnection(downloadUrl, appToken, sdkToken, alreadyDownloaded);
|
|
116
119
|
try (
|
|
117
|
-
BufferedInputStream inputStream = new BufferedInputStream(
|
|
118
|
-
|
|
119
|
-
BufferedOutputStream bout = new BufferedOutputStream(fout, StallionApiConstants.DOWNLOAD_BUFFER_SIZE)
|
|
120
|
+
BufferedInputStream inputStream = new BufferedInputStream(connection.getInputStream());
|
|
121
|
+
RandomAccessFile raf = new RandomAccessFile(destinationFile, "rw")
|
|
120
122
|
) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
+
raf.seek(alreadyDownloaded);
|
|
123
124
|
byte[] buffer = new byte[StallionApiConstants.DOWNLOAD_BUFFER_SIZE];
|
|
124
|
-
long totalBytes = connection.getContentLength();
|
|
125
|
-
long receivedBytes =
|
|
125
|
+
long totalBytes = connection.getContentLength() + alreadyDownloaded;
|
|
126
|
+
long receivedBytes = alreadyDownloaded;
|
|
126
127
|
int bytesRead;
|
|
127
|
-
double lastProgress =
|
|
128
|
+
double lastProgress = (double) receivedBytes / totalBytes;
|
|
128
129
|
|
|
129
130
|
// Ensure totalBytes is valid
|
|
130
131
|
if (totalBytes <= 0) {
|
|
@@ -133,9 +134,11 @@ public class StallionFileDownloader {
|
|
|
133
134
|
}
|
|
134
135
|
|
|
135
136
|
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
|
136
|
-
|
|
137
|
+
raf.write(buffer, 0, bytesRead);
|
|
137
138
|
receivedBytes += bytesRead;
|
|
138
139
|
|
|
140
|
+
StallionDownloadCacheManager.saveDownloadCache(downloadDirectory, receivedBytes);
|
|
141
|
+
|
|
139
142
|
double progress = (double) receivedBytes / totalBytes;
|
|
140
143
|
if (Double.isNaN(progress) || Double.isInfinite(progress)) {
|
|
141
144
|
callback.onReject(StallionApiConstants.DOWNLOAD_ERROR_PREFIX, "Invalid progress calculation");
|
|
@@ -148,8 +151,7 @@ public class StallionFileDownloader {
|
|
|
148
151
|
}
|
|
149
152
|
}
|
|
150
153
|
|
|
151
|
-
|
|
152
|
-
fout.close();
|
|
154
|
+
raf.close();
|
|
153
155
|
inputStream.close();
|
|
154
156
|
|
|
155
157
|
// Check for incomplete download
|
|
@@ -161,18 +163,25 @@ public class StallionFileDownloader {
|
|
|
161
163
|
callback.onReject(StallionApiConstants.DOWNLOAD_ERROR_PREFIX, "IOException occurred: ");
|
|
162
164
|
throw e;
|
|
163
165
|
} finally {
|
|
164
|
-
|
|
165
|
-
connection.disconnect();
|
|
166
|
-
}
|
|
166
|
+
connection.disconnect();
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
|
|
171
|
-
private static HttpURLConnection setupConnection(
|
|
171
|
+
private static HttpURLConnection setupConnection(
|
|
172
|
+
String downloadUrl,
|
|
173
|
+
String appToken,
|
|
174
|
+
String sdkToken,
|
|
175
|
+
long offset
|
|
176
|
+
) throws IOException {
|
|
172
177
|
URL url = new URL(downloadUrl);
|
|
173
178
|
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
|
174
179
|
connection.setRequestMethod("GET");
|
|
175
180
|
|
|
181
|
+
if (offset > 0) {
|
|
182
|
+
connection.setRequestProperty("Range", "bytes=" + offset + "-");
|
|
183
|
+
}
|
|
184
|
+
|
|
176
185
|
if(!appToken.isEmpty()) {
|
|
177
186
|
connection.setRequestProperty(StallionApiConstants.STALLION_APP_TOKEN_KEY, appToken);
|
|
178
187
|
}
|
|
@@ -4,6 +4,7 @@ import com.facebook.react.bridge.Promise;
|
|
|
4
4
|
import com.facebook.react.bridge.ReadableMap;
|
|
5
5
|
import com.stallion.events.StallionEventConstants;
|
|
6
6
|
import com.stallion.events.StallionEventManager;
|
|
7
|
+
import com.stallion.storage.StallionConfig;
|
|
7
8
|
import com.stallion.storage.StallionConfigConstants;
|
|
8
9
|
import com.stallion.storage.StallionMetaConstants;
|
|
9
10
|
import com.stallion.storage.StallionStateManager;
|
|
@@ -21,14 +22,18 @@ public class StallionStageManager {
|
|
|
21
22
|
&& receivedHash != null
|
|
22
23
|
&& !receivedHash.isEmpty()
|
|
23
24
|
) {
|
|
24
|
-
|
|
25
|
+
StallionConfig config = stallionStateManager.getStallionConfig();
|
|
26
|
+
String downloadPath = config.getFilesDirectory()
|
|
25
27
|
+ StallionConfigConstants.STAGE_DIRECTORY
|
|
26
28
|
+ StallionConfigConstants.TEMP_FOLDER_SLOT;
|
|
29
|
+
long alreadyDownloaded = StallionDownloadCacheManager.getDownloadCache(config, receivedDownloadUrl, downloadPath);
|
|
30
|
+
|
|
31
|
+
emitDownloadStartedStage(receivedHash, alreadyDownloaded > 0);
|
|
27
32
|
|
|
28
|
-
emitDownloadStartedStage(receivedHash);
|
|
29
33
|
StallionFileDownloader.downloadBundle(
|
|
30
34
|
receivedDownloadUrl,
|
|
31
35
|
downloadPath,
|
|
36
|
+
alreadyDownloaded,
|
|
32
37
|
new StallionDownloadCallback() {
|
|
33
38
|
@Override
|
|
34
39
|
public void onReject(String prefix, String error) {
|
|
@@ -42,6 +47,7 @@ public class StallionStageManager {
|
|
|
42
47
|
stallionStateManager.stallionMeta.setStageTempHash(receivedHash);
|
|
43
48
|
stallionStateManager.syncStallionMeta();
|
|
44
49
|
emitDownloadSuccessStage(receivedHash);
|
|
50
|
+
StallionDownloadCacheManager.deleteDownloadCache(downloadPath);
|
|
45
51
|
promise.resolve(successPayload);
|
|
46
52
|
}
|
|
47
53
|
|
|
@@ -77,13 +83,15 @@ public class StallionStageManager {
|
|
|
77
83
|
);
|
|
78
84
|
}
|
|
79
85
|
|
|
80
|
-
private static void emitDownloadStartedStage(String releaseHash) {
|
|
86
|
+
private static void emitDownloadStartedStage(String releaseHash, Boolean isResume) {
|
|
81
87
|
JSONObject startedPayload = new JSONObject();
|
|
82
88
|
try {
|
|
83
89
|
startedPayload.put("releaseHash", releaseHash);
|
|
84
90
|
} catch (Exception ignored) { }
|
|
85
91
|
StallionEventManager.getInstance().sendEvent(
|
|
86
|
-
|
|
92
|
+
isResume ?
|
|
93
|
+
StallionEventConstants.NativeStageEventTypes.DOWNLOAD_RESUME_STAGE.toString()
|
|
94
|
+
: StallionEventConstants.NativeStageEventTypes.DOWNLOAD_STARTED_STAGE.toString(),
|
|
87
95
|
startedPayload
|
|
88
96
|
);
|
|
89
97
|
}
|
|
@@ -5,10 +5,14 @@ import com.stallion.storage.StallionConfigConstants;
|
|
|
5
5
|
import com.stallion.storage.StallionMetaConstants;
|
|
6
6
|
import com.stallion.storage.StallionStateManager;
|
|
7
7
|
import com.stallion.storage.StallionConfig;
|
|
8
|
+
import com.stallion.utils.StallionFileManager;
|
|
9
|
+
import com.stallion.utils.StallionSignatureVerification;
|
|
8
10
|
import com.stallion.utils.StallionSlotManager;
|
|
9
11
|
import com.stallion.events.StallionEventConstants.NativeProdEventTypes;
|
|
10
12
|
|
|
11
13
|
import org.json.JSONObject;
|
|
14
|
+
|
|
15
|
+
import java.io.File;
|
|
12
16
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
13
17
|
|
|
14
18
|
public class StallionSyncHandler {
|
|
@@ -85,11 +89,13 @@ public class StallionSyncHandler {
|
|
|
85
89
|
|
|
86
90
|
StallionStateManager stateManager = StallionStateManager.getInstance();
|
|
87
91
|
String lastRolledBackHash = stateManager.stallionMeta.getLastRolledBackHash();
|
|
92
|
+
String lastUnverifiedHash = stateManager.getStallionConfig().getLastUnverifiedHash();
|
|
88
93
|
|
|
89
94
|
if (
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
95
|
+
!newReleaseHash.isEmpty()
|
|
96
|
+
&& !newReleaseUrl.isEmpty()
|
|
97
|
+
&& !newReleaseHash.equals(lastRolledBackHash)
|
|
98
|
+
&& !newReleaseHash.equals(lastUnverifiedHash)
|
|
93
99
|
) {
|
|
94
100
|
if(stateManager.getIsMounted()) {
|
|
95
101
|
downloadNewRelease(newReleaseHash, newReleaseUrl);
|
|
@@ -106,16 +112,22 @@ public class StallionSyncHandler {
|
|
|
106
112
|
}
|
|
107
113
|
try {
|
|
108
114
|
StallionStateManager stateManager = StallionStateManager.getInstance();
|
|
109
|
-
|
|
115
|
+
StallionConfig config = stateManager.getStallionConfig();
|
|
116
|
+
String downloadPath = config.getFilesDirectory()
|
|
110
117
|
+ StallionConfigConstants.PROD_DIRECTORY
|
|
111
118
|
+ StallionConfigConstants.TEMP_FOLDER_SLOT;
|
|
112
|
-
String projectId =
|
|
119
|
+
String projectId = config.getProjectId();
|
|
120
|
+
String downloadUrl = newReleaseUrl + "?projectId=" + projectId;
|
|
121
|
+
String publicSigningKey = config.getPublicSigningKey();
|
|
122
|
+
|
|
123
|
+
long alreadyDownloaded = StallionDownloadCacheManager.getDownloadCache(config, downloadUrl, downloadPath);
|
|
113
124
|
|
|
114
|
-
emitDownloadStarted(newReleaseHash);
|
|
125
|
+
emitDownloadStarted(newReleaseHash, alreadyDownloaded > 0);
|
|
115
126
|
|
|
116
127
|
StallionFileDownloader.downloadBundle(
|
|
117
|
-
|
|
128
|
+
downloadUrl,
|
|
118
129
|
downloadPath,
|
|
130
|
+
alreadyDownloaded,
|
|
119
131
|
new StallionDownloadCallback() {
|
|
120
132
|
@Override
|
|
121
133
|
public void onReject(String prefix, String error) {
|
|
@@ -126,6 +138,22 @@ public class StallionSyncHandler {
|
|
|
126
138
|
@Override
|
|
127
139
|
public void onSuccess(String successPayload) {
|
|
128
140
|
isDownloadInProgress.set(false);
|
|
141
|
+
StallionDownloadCacheManager.deleteDownloadCache(downloadPath);
|
|
142
|
+
|
|
143
|
+
if(publicSigningKey != null && !publicSigningKey.isEmpty()) {
|
|
144
|
+
if(
|
|
145
|
+
!StallionSignatureVerification.verifyReleaseSignature(
|
|
146
|
+
downloadPath + StallionConfigConstants.UNZIP_FOLDER_NAME,
|
|
147
|
+
publicSigningKey)
|
|
148
|
+
) {
|
|
149
|
+
// discard the downloaded release
|
|
150
|
+
config.setLastUnverifiedHash(newReleaseHash);
|
|
151
|
+
emitSignatureVerificationFailed(newReleaseHash);
|
|
152
|
+
StallionFileManager.deleteFileOrFolderSilently(new File(downloadPath));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
129
157
|
stateManager.stallionMeta.setCurrentProdSlot(StallionMetaConstants.SlotStates.NEW_SLOT);
|
|
130
158
|
stateManager.stallionMeta.setProdTempHash(newReleaseHash);
|
|
131
159
|
String currentProdNewHash = stateManager.stallionMeta.getProdNewHash();
|
|
@@ -182,13 +210,23 @@ public class StallionSyncHandler {
|
|
|
182
210
|
);
|
|
183
211
|
}
|
|
184
212
|
|
|
185
|
-
private static void emitDownloadStarted(String releaseHash) {
|
|
213
|
+
private static void emitDownloadStarted(String releaseHash, Boolean isResume) {
|
|
214
|
+
JSONObject successPayload = new JSONObject();
|
|
215
|
+
try {
|
|
216
|
+
successPayload.put("releaseHash", releaseHash);
|
|
217
|
+
} catch (Exception ignored) { }
|
|
218
|
+
StallionEventManager.getInstance().sendEvent(
|
|
219
|
+
isResume ? NativeProdEventTypes.DOWNLOAD_RESUME_PROD.toString(): NativeProdEventTypes.DOWNLOAD_STARTED_PROD.toString(),
|
|
220
|
+
successPayload
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
private static void emitSignatureVerificationFailed(String releaseHash) {
|
|
186
224
|
JSONObject successPayload = new JSONObject();
|
|
187
225
|
try {
|
|
188
226
|
successPayload.put("releaseHash", releaseHash);
|
|
189
227
|
} catch (Exception ignored) { }
|
|
190
228
|
StallionEventManager.getInstance().sendEvent(
|
|
191
|
-
NativeProdEventTypes.
|
|
229
|
+
NativeProdEventTypes.SIGNATURE_VERIFICATION_FAILED.toString(),
|
|
192
230
|
successPayload
|
|
193
231
|
);
|
|
194
232
|
}
|
|
@@ -19,6 +19,10 @@ public class StallionConfig {
|
|
|
19
19
|
private final String appVersion;
|
|
20
20
|
private final SharedPreferences sharedPreferences;
|
|
21
21
|
private final String filesDirectory;
|
|
22
|
+
private String lastDownloadingUrl;
|
|
23
|
+
private String lastUnverifiedHash;
|
|
24
|
+
private final String publicSigningKey;
|
|
25
|
+
|
|
22
26
|
|
|
23
27
|
public StallionConfig(Context context, SharedPreferences sharedPreferences) {
|
|
24
28
|
this.sharedPreferences = sharedPreferences;
|
|
@@ -31,6 +35,7 @@ public class StallionConfig {
|
|
|
31
35
|
parentPackageName
|
|
32
36
|
);
|
|
33
37
|
this.projectId = stallionProjectIdRes != 0 ?context.getString(stallionProjectIdRes) : "";
|
|
38
|
+
|
|
34
39
|
int stallionAppTokenRes = res.getIdentifier(
|
|
35
40
|
StallionConfigConstants.STALLION_APP_TOKEN_IDENTIFIER,
|
|
36
41
|
"string",
|
|
@@ -38,6 +43,13 @@ public class StallionConfig {
|
|
|
38
43
|
);
|
|
39
44
|
this.appToken = stallionAppTokenRes != 0 ? context.getString(stallionAppTokenRes) : "";
|
|
40
45
|
|
|
46
|
+
int stallionPublicKeyRes = res.getIdentifier(
|
|
47
|
+
StallionConfigConstants.STALLION_PUBLIC_SIGNING_KEY_IDENTIFIER,
|
|
48
|
+
"string",
|
|
49
|
+
parentPackageName
|
|
50
|
+
);
|
|
51
|
+
this.publicSigningKey = stallionPublicKeyRes != 0 ? context.getString(stallionPublicKeyRes) : "";
|
|
52
|
+
|
|
41
53
|
// get or generate UID
|
|
42
54
|
String cachedUniqueId = sharedPreferences.getString(
|
|
43
55
|
StallionConfigConstants.UNIQUE_ID_IDENTIFIER,
|
|
@@ -63,6 +75,30 @@ public class StallionConfig {
|
|
|
63
75
|
|
|
64
76
|
this.appVersion = fetchAppVersion(context);
|
|
65
77
|
this.filesDirectory = context.getFilesDir().getAbsolutePath();
|
|
78
|
+
this.lastDownloadingUrl = sharedPreferences.getString(StallionConfigConstants.LAST_DOWNLOADING_URL_IDENTIFIER, "");
|
|
79
|
+
this.lastUnverifiedHash = sharedPreferences.getString(StallionConfigConstants.LAST_UNVERIFIED_HASH, "");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public String getLastDownloadingUrl() {
|
|
83
|
+
return this.lastDownloadingUrl;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
public void setLastDownloadingUrl(String newUrl) {
|
|
87
|
+
this.lastDownloadingUrl = newUrl;
|
|
88
|
+
SharedPreferences.Editor editor = sharedPreferences.edit();
|
|
89
|
+
editor.putString(StallionConfigConstants.LAST_DOWNLOADING_URL_IDENTIFIER, newUrl);
|
|
90
|
+
editor.apply();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public String getLastUnverifiedHash() {
|
|
94
|
+
return this.lastUnverifiedHash;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
public void setLastUnverifiedHash(String newUnverifiedHash) {
|
|
98
|
+
this.lastUnverifiedHash = newUnverifiedHash;
|
|
99
|
+
SharedPreferences.Editor editor = sharedPreferences.edit();
|
|
100
|
+
editor.putString(StallionConfigConstants.LAST_UNVERIFIED_HASH, newUnverifiedHash);
|
|
101
|
+
editor.apply();
|
|
66
102
|
}
|
|
67
103
|
|
|
68
104
|
private String fetchAppVersion(Context context) {
|
|
@@ -105,6 +141,10 @@ public class StallionConfig {
|
|
|
105
141
|
|
|
106
142
|
public String getFilesDirectory() { return this.filesDirectory; }
|
|
107
143
|
|
|
144
|
+
public String getPublicSigningKey() {
|
|
145
|
+
return this.publicSigningKey;
|
|
146
|
+
}
|
|
147
|
+
|
|
108
148
|
public JSONObject toJSON() {
|
|
109
149
|
JSONObject configJson = new JSONObject();
|
|
110
150
|
try {
|
|
@@ -7,8 +7,11 @@ public class StallionConfigConstants {
|
|
|
7
7
|
|
|
8
8
|
public static final String STALLION_PROJECT_ID_IDENTIFIER = "StallionProjectId";
|
|
9
9
|
public static final String STALLION_APP_TOKEN_IDENTIFIER = "StallionAppToken";
|
|
10
|
+
public static final String STALLION_PUBLIC_SIGNING_KEY_IDENTIFIER = "StallionPublicSigningKey";
|
|
10
11
|
public static final String UNIQUE_ID_IDENTIFIER = "stallionDeviceId";
|
|
11
12
|
public static final String API_KEY_IDENTIFIER = "x-sdk-access-token";
|
|
13
|
+
public static final String LAST_DOWNLOADING_URL_IDENTIFIER = "StallionLastDownloadingUrl";
|
|
14
|
+
public static final String LAST_UNVERIFIED_HASH = "LastUnverifiedHash";
|
|
12
15
|
|
|
13
16
|
public static final String PROD_DIRECTORY = "/StallionProd";
|
|
14
17
|
public static final String STAGE_DIRECTORY = "/StallionStage";
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
package com.stallion.utils;
|
|
2
|
+
|
|
3
|
+
import java.io.ByteArrayInputStream;
|
|
4
|
+
import java.io.File;
|
|
5
|
+
import java.io.FileInputStream;
|
|
6
|
+
import java.io.IOException;
|
|
7
|
+
import java.io.InputStream;
|
|
8
|
+
import java.nio.charset.StandardCharsets;
|
|
9
|
+
import java.security.DigestInputStream;
|
|
10
|
+
import java.security.KeyFactory;
|
|
11
|
+
import java.security.MessageDigest;
|
|
12
|
+
import java.security.PublicKey;
|
|
13
|
+
import java.security.Signature;
|
|
14
|
+
import java.security.spec.X509EncodedKeySpec;
|
|
15
|
+
import java.util.ArrayList;
|
|
16
|
+
import java.util.Collections;
|
|
17
|
+
import java.util.Scanner;
|
|
18
|
+
|
|
19
|
+
import org.json.JSONArray;
|
|
20
|
+
import org.json.JSONObject;
|
|
21
|
+
|
|
22
|
+
public class StallionSignatureVerification {
|
|
23
|
+
|
|
24
|
+
public static final String SIGNATURE_FILE_NAME = ".stallionsigned";
|
|
25
|
+
|
|
26
|
+
public static boolean verifyReleaseSignature(String downloadedBundlePath, String publicKeyPem) {
|
|
27
|
+
try {
|
|
28
|
+
File folderPath = new File(downloadedBundlePath);
|
|
29
|
+
File signatureFile = new File(folderPath, SIGNATURE_FILE_NAME);
|
|
30
|
+
if (!signatureFile.exists()) return false;
|
|
31
|
+
|
|
32
|
+
String jwt = readFileLegacy(signatureFile);
|
|
33
|
+
String[] jwtParts = jwt.split("\\.");
|
|
34
|
+
if (jwtParts.length != 3) return false;
|
|
35
|
+
|
|
36
|
+
String header = jwtParts[0];
|
|
37
|
+
String payload = jwtParts[1];
|
|
38
|
+
String signature = jwtParts[2];
|
|
39
|
+
|
|
40
|
+
byte[] signatureBytes = base64UrlDecode(signature);
|
|
41
|
+
byte[] signedContent = (header + "." + payload).getBytes(StandardCharsets.UTF_8);
|
|
42
|
+
|
|
43
|
+
String cleanedPem = publicKeyPem
|
|
44
|
+
.replace("-----BEGIN PUBLIC KEY-----", "")
|
|
45
|
+
.replace("-----END PUBLIC KEY-----", "")
|
|
46
|
+
.replaceAll("\\s", "");
|
|
47
|
+
|
|
48
|
+
byte[] decodedKey = android.util.Base64.decode(cleanedPem, android.util.Base64.DEFAULT);
|
|
49
|
+
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decodedKey);
|
|
50
|
+
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
|
51
|
+
PublicKey pubKey = keyFactory.generatePublic(keySpec);
|
|
52
|
+
|
|
53
|
+
Signature sig = Signature.getInstance("SHA256withRSA");
|
|
54
|
+
sig.initVerify(pubKey);
|
|
55
|
+
sig.update(signedContent);
|
|
56
|
+
if (!sig.verify(signatureBytes)) return false;
|
|
57
|
+
|
|
58
|
+
String jsonPayload = new String(base64UrlDecode(payload), StandardCharsets.UTF_8);
|
|
59
|
+
JSONObject payloadObj = new JSONObject(jsonPayload);
|
|
60
|
+
String expectedHash = payloadObj.optString("packageHash", "");
|
|
61
|
+
|
|
62
|
+
String actualHash = computeFolderHash(folderPath);
|
|
63
|
+
return expectedHash.equals(actualHash);
|
|
64
|
+
|
|
65
|
+
} catch (Exception e) {
|
|
66
|
+
e.printStackTrace();
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private static String computeFolderHash(File folder) throws Exception {
|
|
72
|
+
ArrayList<String> manifest = new ArrayList<>();
|
|
73
|
+
addFolderContentsToManifest(folder, "", manifest);
|
|
74
|
+
Collections.sort(manifest);
|
|
75
|
+
JSONArray jsonArray = new JSONArray();
|
|
76
|
+
for (String entry : manifest) {
|
|
77
|
+
jsonArray.put(entry);
|
|
78
|
+
}
|
|
79
|
+
String jsonString = jsonArray.toString().replace("\\/", "/");
|
|
80
|
+
return computeHash(new ByteArrayInputStream(jsonString.getBytes(StandardCharsets.UTF_8)));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private static void addFolderContentsToManifest(File folder, String pathPrefix, ArrayList<String> manifest) throws Exception {
|
|
84
|
+
File[] files = folder.listFiles();
|
|
85
|
+
for (File file : files) {
|
|
86
|
+
String fileName = file.getName();
|
|
87
|
+
String relativePath = pathPrefix.isEmpty() ? fileName : pathPrefix + "/" + fileName;
|
|
88
|
+
if (isIgnored(relativePath)) continue;
|
|
89
|
+
if (file.isDirectory()) {
|
|
90
|
+
addFolderContentsToManifest(file, relativePath, manifest);
|
|
91
|
+
} else {
|
|
92
|
+
manifest.add(relativePath + ":" + computeHash(new FileInputStream(file)));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private static boolean isIgnored(String path) {
|
|
98
|
+
return path.equals(".DS_Store") || path.equals(SIGNATURE_FILE_NAME) || path.startsWith("__MACOSX/");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private static String computeHash(InputStream stream) throws Exception {
|
|
102
|
+
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
|
103
|
+
DigestInputStream dis = new DigestInputStream(stream, digest);
|
|
104
|
+
byte[] buffer = new byte[8192];
|
|
105
|
+
while (dis.read(buffer) != -1);
|
|
106
|
+
dis.close();
|
|
107
|
+
byte[] hash = digest.digest();
|
|
108
|
+
return String.format("%064x", new java.math.BigInteger(1, hash));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private static String readFileLegacy(File file) throws IOException {
|
|
112
|
+
Scanner scanner = new Scanner(file, "UTF-8");
|
|
113
|
+
StringBuilder sb = new StringBuilder();
|
|
114
|
+
while (scanner.hasNextLine()) {
|
|
115
|
+
sb.append(scanner.nextLine());
|
|
116
|
+
}
|
|
117
|
+
scanner.close();
|
|
118
|
+
return sb.toString();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private static byte[] base64UrlDecode(String input) {
|
|
122
|
+
String converted = input.replace('-', '+').replace('_', '/');
|
|
123
|
+
switch (converted.length() % 4) {
|
|
124
|
+
case 2: converted += "=="; break;
|
|
125
|
+
case 3: converted += "="; break;
|
|
126
|
+
}
|
|
127
|
+
return android.util.Base64.decode(converted, android.util.Base64.NO_WRAP);
|
|
128
|
+
}
|
|
129
|
+
}
|
package/ios/main/Stallion.swift
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// StallionConfig.h
|
|
3
3
|
// DoubleConversion
|
|
4
4
|
//
|
|
5
|
-
// Created by
|
|
5
|
+
// Created by Thor963 on 28/01/25.
|
|
6
6
|
//
|
|
7
7
|
|
|
8
8
|
#import <Foundation/Foundation.h>
|
|
@@ -15,10 +15,13 @@
|
|
|
15
15
|
@property (nonatomic, copy) NSString *sdkToken;
|
|
16
16
|
@property (nonatomic, copy, readonly) NSString *appVersion;
|
|
17
17
|
@property (nonatomic, copy, readonly) NSString *filesDirectory;
|
|
18
|
+
@property (nonatomic, copy, readonly) NSString *publicSigningKey;
|
|
19
|
+
@property (nonatomic, copy) NSString *lastUnverifiedHash;
|
|
18
20
|
|
|
19
21
|
- (instancetype)initWithDefaults:(NSUserDefaults *)defaults;
|
|
20
22
|
|
|
21
23
|
- (void)updateSdkToken:(NSString *)newSdkToken;
|
|
24
|
+
- (void)updateLastUnverifiedHash:(NSString *)newUnverifiedHash;
|
|
22
25
|
- (NSDictionary *)toDictionary;
|
|
23
26
|
|
|
24
27
|
@end
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// StallionConfig.m
|
|
3
3
|
// DoubleConversion
|
|
4
4
|
//
|
|
5
|
-
// Created by
|
|
5
|
+
// Created by Thor963 on 28/01/25.
|
|
6
6
|
//
|
|
7
7
|
|
|
8
8
|
#import "StallionConfig.h"
|
|
@@ -17,8 +17,9 @@
|
|
|
17
17
|
_appToken = [[NSBundle mainBundle] objectForInfoDictionaryKey:STALLION_APP_TOKEN_IDENTIFIER] ?: @"";
|
|
18
18
|
_sdkToken = [defaults stringForKey:API_KEY_IDENTIFIER] ?: @"";
|
|
19
19
|
_appVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:STALLION_APP_VERSION_IDENTIFIER] ?: @"";
|
|
20
|
-
|
|
21
20
|
_filesDirectory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject ?: @"";
|
|
21
|
+
_publicSigningKey = [[NSBundle mainBundle] objectForInfoDictionaryKey:STALLION_PUBLIC_SIGNING_KEY_IDENTIFIER] ?: @"";
|
|
22
|
+
_lastUnverifiedHash = [defaults stringForKey:LAST_UNVERIFIED_KEY_IDENTIFIER] ?: @"";
|
|
22
23
|
|
|
23
24
|
NSString *cachedUid = [defaults stringForKey:UNIQUE_ID_IDENTIFIER];
|
|
24
25
|
if (cachedUid && ![cachedUid isEqualToString:@""]) {
|
|
@@ -44,6 +45,12 @@
|
|
|
44
45
|
[[NSUserDefaults standardUserDefaults] synchronize];
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
- (void)updateLastUnverifiedHash:(NSString *)newUnverifiedHash {
|
|
49
|
+
_lastUnverifiedHash = newUnverifiedHash ?: @"";
|
|
50
|
+
[[NSUserDefaults standardUserDefaults] setObject:_lastUnverifiedHash forKey:LAST_UNVERIFIED_KEY_IDENTIFIER];
|
|
51
|
+
[[NSUserDefaults standardUserDefaults] synchronize];
|
|
52
|
+
}
|
|
53
|
+
|
|
47
54
|
- (NSDictionary *)toDictionary {
|
|
48
55
|
@try {
|
|
49
56
|
return @{
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// StallionConfigConstants.h
|
|
3
3
|
// DoubleConversion
|
|
4
4
|
//
|
|
5
|
-
// Created by
|
|
5
|
+
// Created by Thor963 on 28/01/25.
|
|
6
6
|
//
|
|
7
7
|
|
|
8
8
|
#import <Foundation/Foundation.h>
|
|
@@ -29,4 +29,8 @@ extern NSString *const UNZIP_FOLDER_NAME;
|
|
|
29
29
|
|
|
30
30
|
extern NSString *const STALLION_META_IDENTIFIER;
|
|
31
31
|
|
|
32
|
+
extern NSString *const STALLION_PUBLIC_SIGNING_KEY_IDENTIFIER;
|
|
33
|
+
|
|
34
|
+
extern NSString *const LAST_UNVERIFIED_KEY_IDENTIFIER;
|
|
35
|
+
|
|
32
36
|
@end
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// StallionConfigConstants.m
|
|
3
3
|
// DoubleConversion
|
|
4
4
|
//
|
|
5
|
-
// Created by
|
|
5
|
+
// Created by Thor963 on 28/01/25.
|
|
6
6
|
//
|
|
7
7
|
|
|
8
8
|
#import "StallionConfigConstants.h"
|
|
@@ -29,4 +29,8 @@ NSString *const ANDROID_BUNDLE_FILE_NAME = @"/index.android.bundle";
|
|
|
29
29
|
NSString *const DEFAULT_JS_BUNDLE_LOCATION_BASE = @"assets:/";
|
|
30
30
|
NSString *const UNZIP_FOLDER_NAME = @"/build";
|
|
31
31
|
|
|
32
|
+
NSString *const STALLION_PUBLIC_SIGNING_KEY_IDENTIFIER = @"StallionPublicSigningKey";
|
|
33
|
+
|
|
34
|
+
NSString *const LAST_UNVERIFIED_KEY_IDENTIFIER = @"LastUnverifiedHash";
|
|
35
|
+
|
|
32
36
|
@end
|
|
@@ -78,6 +78,7 @@ class StallionConstants {
|
|
|
78
78
|
static let INSTALLED_PROD = "INSTALLED_PROD"
|
|
79
79
|
static let STABILIZED_PROD = "STABILIZED_PROD"
|
|
80
80
|
static let EXCEPTION_PROD = "EXCEPTION_PROD"
|
|
81
|
+
static let SIGNATURE_VERIFICATION_FAILED = "SIGNATURE_VERIFICATION_FAILED"
|
|
81
82
|
}
|
|
82
83
|
public struct NativeEventTypesStage {
|
|
83
84
|
static let DOWNLOAD_STARTED_STAGE = "DOWNLOAD_STARTED_STAGE"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// StallionEventHandler.m
|
|
3
3
|
// DoubleConversion
|
|
4
4
|
//
|
|
5
|
-
// Created by
|
|
5
|
+
// Created by Thor963 on 28/01/25.
|
|
6
6
|
//
|
|
7
7
|
|
|
8
8
|
#import "StallionEventHandler.h"
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
static NSString *const STALLION_NATIVE_EVENT_NAME = @"STALLION_NATIVE_EVENT";
|
|
16
16
|
static NSString *const EVENTS_KEY = @"stored_events";
|
|
17
17
|
static NSInteger const MAX_BATCH_COUNT_SIZE = 9;
|
|
18
|
+
static NSInteger const MAX_EVENT_STORAGE_LIMIT = 20;
|
|
18
19
|
|
|
19
20
|
@implementation StallionEventHandler
|
|
20
21
|
|
|
@@ -30,11 +31,11 @@ static NSInteger const MAX_BATCH_COUNT_SIZE = 9;
|
|
|
30
31
|
// Emit event to React Native and store locally
|
|
31
32
|
- (void)cacheEvent:(NSString *)eventName eventPayload:(NSDictionary *)eventPayload {
|
|
32
33
|
StallionStateManager *stallionStateManager = [StallionStateManager sharedInstance];
|
|
33
|
-
|
|
34
|
+
|
|
34
35
|
NSString *projectId = stallionStateManager.stallionConfig.projectId ?: @"";
|
|
35
36
|
NSString *appVersion = stallionStateManager.stallionConfig.appVersion ?: @"";
|
|
36
37
|
NSString *uid = stallionStateManager.stallionConfig.uid ?: @"";
|
|
37
|
-
|
|
38
|
+
|
|
38
39
|
NSMutableDictionary *mutablePayload = [eventPayload mutableCopy];
|
|
39
40
|
NSString *uniqueId = [[NSUUID UUID] UUIDString];
|
|
40
41
|
mutablePayload[@"eventId"] = uniqueId;
|
|
@@ -62,6 +63,11 @@ static NSInteger const MAX_BATCH_COUNT_SIZE = 9;
|
|
|
62
63
|
eventsObject = [NSMutableDictionary dictionary];
|
|
63
64
|
}
|
|
64
65
|
|
|
66
|
+
// Enforce max size
|
|
67
|
+
if (eventsObject.count >= MAX_EVENT_STORAGE_LIMIT) {
|
|
68
|
+
eventsObject = [NSMutableDictionary dictionary];
|
|
69
|
+
}
|
|
70
|
+
|
|
65
71
|
eventsObject[uniqueId] = eventPayload;
|
|
66
72
|
|
|
67
73
|
NSData *updatedData = [NSJSONSerialization dataWithJSONObject:eventsObject options:0 error:nil];
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// StallionExceptionHandler.m
|
|
3
3
|
// react-native-stallion
|
|
4
4
|
//
|
|
5
|
-
// Created by
|
|
5
|
+
// Created by Thor963 on 29/01/25.
|
|
6
6
|
//
|
|
7
7
|
|
|
8
8
|
#import "StallionExceptionHandler.h"
|
|
@@ -51,7 +51,10 @@ void handleException(NSException *exception) {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
} else if (meta.switchState == SwitchStateStage) {
|
|
54
|
-
|
|
54
|
+
|
|
55
|
+
if(isAutoRollback) {
|
|
56
|
+
[StallionSlotManager rollbackStage];
|
|
57
|
+
}
|
|
55
58
|
|
|
56
59
|
[[StallionEventHandler sharedInstance] cacheEvent:StallionObjConstants.exception_stage_event
|
|
57
60
|
eventPayload:@{
|
|
@@ -60,6 +63,7 @@ void handleException(NSException *exception) {
|
|
|
60
63
|
StallionObjConstants.is_auto_rollback_key: isAutoRollback ? @"true" : @"false"
|
|
61
64
|
}];
|
|
62
65
|
|
|
66
|
+
if(!exceptionAlertDismissed) {
|
|
63
67
|
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Stallion Exception Handler"
|
|
64
68
|
message:[NSString stringWithFormat:@"%@\n%@",
|
|
65
69
|
@"A crash occurred in the app. Build was rolled back. Check crash report below. Continue crash to invoke other exception handlers. \n \n",
|
|
@@ -82,6 +86,7 @@ void handleException(NSException *exception) {
|
|
|
82
86
|
while (exceptionAlertDismissed == FALSE) {
|
|
83
87
|
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
|
|
84
88
|
}
|
|
89
|
+
}
|
|
85
90
|
}
|
|
86
91
|
|
|
87
92
|
// Call default exception handler if available
|
package/ios/main/StallionMeta.h
CHANGED
package/ios/main/StallionMeta.m
CHANGED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import CommonCrypto
|
|
3
|
+
import Security
|
|
4
|
+
|
|
5
|
+
class StallionSignatureVerification {
|
|
6
|
+
|
|
7
|
+
static let signatureFileName = ".stallionsigned"
|
|
8
|
+
|
|
9
|
+
static func verifyReleaseSignature(downloadedBundlePath: String, publicKeyPem: String) -> Bool {
|
|
10
|
+
do {
|
|
11
|
+
let signatureFilePath = (downloadedBundlePath as NSString).appendingPathComponent(signatureFileName)
|
|
12
|
+
guard FileManager.default.fileExists(atPath: signatureFilePath) else { return false }
|
|
13
|
+
|
|
14
|
+
let jwt = try String(contentsOfFile: signatureFilePath, encoding: .utf8)
|
|
15
|
+
let parts = jwt.components(separatedBy: ".")
|
|
16
|
+
guard parts.count == 3 else { return false }
|
|
17
|
+
let header = parts[0]
|
|
18
|
+
let payload = parts[1]
|
|
19
|
+
let signatureB64 = parts[2]
|
|
20
|
+
|
|
21
|
+
guard let signatureData = base64UrlDecode(signatureB64),
|
|
22
|
+
let signedData = (header + "." + payload).data(using: .utf8) else { return false }
|
|
23
|
+
|
|
24
|
+
guard let pubKey = try? convertPemToPublicKey(pemString: publicKeyPem) else { return false }
|
|
25
|
+
|
|
26
|
+
var error: Unmanaged<CFError>?
|
|
27
|
+
let verified = SecKeyVerifySignature(
|
|
28
|
+
pubKey,
|
|
29
|
+
.rsaSignatureMessagePKCS1v15SHA256,
|
|
30
|
+
signedData as CFData,
|
|
31
|
+
signatureData as CFData,
|
|
32
|
+
&error
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
guard verified else { return false }
|
|
36
|
+
|
|
37
|
+
guard let payloadData = base64UrlDecode(payload),
|
|
38
|
+
let payloadJson = try JSONSerialization.jsonObject(with: payloadData) as? [String: Any],
|
|
39
|
+
let expectedHash = payloadJson["packageHash"] as? String else { return false }
|
|
40
|
+
|
|
41
|
+
let actualHash = try computeCodePushStyleHash(folderPath: downloadedBundlePath)
|
|
42
|
+
return expectedHash == actualHash
|
|
43
|
+
|
|
44
|
+
} catch {
|
|
45
|
+
print("Signature verification error: \(error)")
|
|
46
|
+
return false
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private static func computeCodePushStyleHash(folderPath: String) throws -> String {
|
|
51
|
+
var manifest: [String] = []
|
|
52
|
+
try addContentsOfFolder(to: &manifest, folderPath: folderPath, pathPrefix: "")
|
|
53
|
+
let sortedManifest = manifest.sorted()
|
|
54
|
+
let jsonData = try JSONSerialization.data(withJSONObject: sortedManifest, options: [])
|
|
55
|
+
var jsonString = String(data: jsonData, encoding: .utf8) ?? ""
|
|
56
|
+
jsonString = jsonString.replacingOccurrences(of: "\\/", with: "/")
|
|
57
|
+
return sha256Hex(jsonString)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private static func addContentsOfFolder(to manifest: inout [String], folderPath: String, pathPrefix: String) throws {
|
|
61
|
+
let items = try FileManager.default.contentsOfDirectory(atPath: folderPath)
|
|
62
|
+
for item in items {
|
|
63
|
+
let fullPath = (folderPath as NSString).appendingPathComponent(item)
|
|
64
|
+
let relativePath = pathPrefix.isEmpty ? item : (pathPrefix as NSString).appendingPathComponent(item)
|
|
65
|
+
|
|
66
|
+
if isIgnored(relativePath) { continue }
|
|
67
|
+
|
|
68
|
+
var isDir: ObjCBool = false
|
|
69
|
+
FileManager.default.fileExists(atPath: fullPath, isDirectory: &isDir)
|
|
70
|
+
|
|
71
|
+
if isDir.boolValue {
|
|
72
|
+
try addContentsOfFolder(to: &manifest, folderPath: fullPath, pathPrefix: relativePath)
|
|
73
|
+
} else {
|
|
74
|
+
let data = try Data(contentsOf: URL(fileURLWithPath: fullPath))
|
|
75
|
+
let hash = sha256Hex(data)
|
|
76
|
+
manifest.append("\(relativePath):\(hash)")
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private static func isIgnored(_ path: String) -> Bool {
|
|
82
|
+
return path.hasPrefix("__MACOSX/") ||
|
|
83
|
+
path == ".DS_Store" ||
|
|
84
|
+
path.hasSuffix("/.DS_Store") ||
|
|
85
|
+
path == signatureFileName ||
|
|
86
|
+
path.hasSuffix("/\(signatureFileName)")
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private static func sha256Hex(_ input: String) -> String {
|
|
90
|
+
guard let data = input.data(using: .utf8) else { return "" }
|
|
91
|
+
return sha256Hex(data)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private static func sha256Hex(_ data: Data) -> String {
|
|
95
|
+
var digest = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
|
|
96
|
+
data.withUnsafeBytes {
|
|
97
|
+
_ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &digest)
|
|
98
|
+
}
|
|
99
|
+
return digest.map { String(format: "%02x", $0) }.joined()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private static func base64UrlDecode(_ input: String) -> Data? {
|
|
103
|
+
var base64 = input.replacingOccurrences(of: "-", with: "+")
|
|
104
|
+
.replacingOccurrences(of: "_", with: "/")
|
|
105
|
+
let padding = base64.count % 4
|
|
106
|
+
if padding > 0 {
|
|
107
|
+
base64 += String(repeating: "=", count: 4 - padding)
|
|
108
|
+
}
|
|
109
|
+
return Data(base64Encoded: base64)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private static func convertPemToPublicKey(pemString: String) throws -> SecKey {
|
|
113
|
+
let cleaned = pemString
|
|
114
|
+
.replacingOccurrences(of: "-----BEGIN PUBLIC KEY-----\n", with: "")
|
|
115
|
+
.replacingOccurrences(of: "-----END PUBLIC KEY-----", with: "")
|
|
116
|
+
.replacingOccurrences(of: "\n", with: "")
|
|
117
|
+
.replacingOccurrences(of: "\r", with: "")
|
|
118
|
+
|
|
119
|
+
guard let keyData = Data(base64Encoded: cleaned, options: .ignoreUnknownCharacters) else {
|
|
120
|
+
throw NSError(domain: "Stallion", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid base64 key"])
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let keyDict: [String: Any] = [
|
|
124
|
+
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
|
|
125
|
+
kSecAttrKeyClass as String: kSecAttrKeyClassPublic,
|
|
126
|
+
kSecAttrKeySizeInBits as String: 2048
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
var error: Unmanaged<CFError>?
|
|
130
|
+
guard let secKey = SecKeyCreateWithData(keyData as CFData, keyDict as CFDictionary, &error) else {
|
|
131
|
+
throw error!.takeRetainedValue() as Error
|
|
132
|
+
}
|
|
133
|
+
return secKey
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// StallionSyncHandler.swift
|
|
3
3
|
// react-native-stallion
|
|
4
4
|
//
|
|
5
|
-
// Created by
|
|
5
|
+
// Created by Thor963 on 29/01/25.
|
|
6
6
|
//
|
|
7
7
|
|
|
8
8
|
import Foundation
|
|
@@ -152,8 +152,9 @@ class StallionSyncHandler {
|
|
|
152
152
|
|
|
153
153
|
let stateManager = StallionStateManager.sharedInstance()
|
|
154
154
|
let lastRolledBackHash = stateManager?.stallionMeta?.lastRolledBackHash ?? ""
|
|
155
|
+
let lastUnverifiedHash = stateManager?.stallionConfig?.lastUnverifiedHash ?? ""
|
|
155
156
|
|
|
156
|
-
if newReleaseHash != lastRolledBackHash {
|
|
157
|
+
if newReleaseHash != lastRolledBackHash && newReleaseHash != lastUnverifiedHash {
|
|
157
158
|
if stateManager?.isMounted == true {
|
|
158
159
|
downloadNewRelease(newReleaseHash: newReleaseHash, newReleaseUrl: newReleaseUrl)
|
|
159
160
|
} else {
|
|
@@ -179,29 +180,52 @@ class StallionSyncHandler {
|
|
|
179
180
|
let downloadPath = config.filesDirectory + "/" + StallionConstants.PROD_DIRECTORY + "/" + StallionConstants.TEMP_FOLDER_SLOT
|
|
180
181
|
let projectId = config.projectId ?? ""
|
|
181
182
|
|
|
183
|
+
let publicSigningKey = config.publicSigningKey ?? ""
|
|
184
|
+
|
|
182
185
|
guard let fromUrl = URL(string: newReleaseUrl + "?projectId=" + projectId) else { return }
|
|
183
186
|
|
|
184
187
|
emitDownloadStarted(releaseHash: newReleaseHash)
|
|
185
188
|
|
|
186
|
-
StallionFileDownloader().downloadBundle(
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
189
|
+
StallionFileDownloader().downloadBundle(
|
|
190
|
+
url: fromUrl,
|
|
191
|
+
downloadDirectory: downloadPath,
|
|
192
|
+
onProgress: { progress in
|
|
193
|
+
// Handle progress updates if necessary
|
|
194
|
+
},
|
|
195
|
+
resolve: { _ in
|
|
196
|
+
completeDownload()
|
|
197
|
+
|
|
198
|
+
if(publicSigningKey != nil && !publicSigningKey.isEmpty) {
|
|
199
|
+
if(
|
|
200
|
+
!StallionSignatureVerification.verifyReleaseSignature(
|
|
201
|
+
downloadedBundlePath: downloadPath + "/" + StallionConstants.FilePaths.ZipFolderName,
|
|
202
|
+
publicKeyPem: publicSigningKey
|
|
203
|
+
)
|
|
204
|
+
) {
|
|
205
|
+
// discard downloaded release
|
|
206
|
+
config.updateLastUnverifiedHash(newReleaseHash)
|
|
207
|
+
emitSignatureVerificationFailed(releaseHash: newReleaseHash)
|
|
208
|
+
StallionFileManager.deleteFileOrFolderSilently(downloadPath)
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
stateManager.stallionMeta?.currentProdSlot = SlotStates.newSlot
|
|
213
|
+
stateManager.stallionMeta?.prodTempHash = newReleaseHash
|
|
214
|
+
if let currentProdNewHash = stateManager.stallionMeta?.prodNewHash,
|
|
215
|
+
!currentProdNewHash.isEmpty {
|
|
216
|
+
StallionSlotManager.stabilizeProd()
|
|
217
|
+
}
|
|
218
|
+
stateManager.syncStallionMeta()
|
|
219
|
+
emitDownloadSuccess(releaseHash: newReleaseHash)
|
|
220
|
+
},
|
|
221
|
+
reject: { code, prefix, error in
|
|
222
|
+
completeDownload()
|
|
223
|
+
emitDownloadError(
|
|
224
|
+
releaseHash: newReleaseHash,
|
|
225
|
+
error: "\(String(describing: prefix))\(String(describing: error))"
|
|
226
|
+
)
|
|
227
|
+
}
|
|
203
228
|
)
|
|
204
|
-
})
|
|
205
229
|
}
|
|
206
230
|
|
|
207
231
|
private static func completeDownload() {
|
|
@@ -246,5 +270,13 @@ class StallionSyncHandler {
|
|
|
246
270
|
shouldCache: true
|
|
247
271
|
)
|
|
248
272
|
}
|
|
273
|
+
|
|
274
|
+
private static func emitSignatureVerificationFailed(releaseHash: String) {
|
|
275
|
+
let verificationFailurePayload: NSDictionary = ["releaseHash": releaseHash]
|
|
276
|
+
Stallion.sendEventToRn(eventName: StallionConstants.NativeEventTypesProd.SIGNATURE_VERIFICATION_FAILED,
|
|
277
|
+
eventBody: verificationFailurePayload,
|
|
278
|
+
shouldCache: true
|
|
279
|
+
)
|
|
280
|
+
}
|
|
249
281
|
}
|
|
250
282
|
|
package/package.json
CHANGED
|
@@ -32,5 +32,8 @@ export const onLaunchNative = StallionNativeModule === null || StallionNativeMod
|
|
|
32
32
|
export const sync = StallionNativeModule === null || StallionNativeModule === void 0 ? void 0 : StallionNativeModule.sync;
|
|
33
33
|
export const popEventsNative = StallionNativeModule === null || StallionNativeModule === void 0 ? void 0 : StallionNativeModule.popEvents;
|
|
34
34
|
export const acknowledgeEventsNative = StallionNativeModule === null || StallionNativeModule === void 0 ? void 0 : StallionNativeModule.acknowledgeEvents;
|
|
35
|
-
export const restart =
|
|
35
|
+
export const restart = () => {
|
|
36
|
+
var _StallionNativeModule;
|
|
37
|
+
StallionNativeModule === null || StallionNativeModule === void 0 ? void 0 : (_StallionNativeModule = StallionNativeModule.restart) === null || _StallionNativeModule === void 0 ? void 0 : _StallionNativeModule.call(StallionNativeModule);
|
|
38
|
+
};
|
|
36
39
|
//# sourceMappingURL=StallionNativeUtils.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["StallionNativeModule","setSdkTokenNative","updateSdkToken","getStallionMetaNative","Promise","resolve","reject","getStallionMeta","then","metaString","JSON","parse","_","catch","getStallionConfigNative","getStallionConfig","configString","toggleStallionSwitchNative","toggleStallionSwitch","downloadBundleNative","downloadStageBundle","onLaunchNative","onLaunch","sync","popEventsNative","popEvents","acknowledgeEventsNative","acknowledgeEvents","restart"],"sourceRoot":"../../../../src","sources":["main/utils/StallionNativeUtils.ts"],"mappings":"AAAA,OAAOA,oBAAoB,MAAM,4BAA4B;AAW7D,OAAO,MAAMC,iBAAqC,GAChDD,oBAAoB,aAApBA,oBAAoB,uBAApBA,oBAAoB,CAAEE,cAAc;AAEtC,OAAO,MAAMC,qBAA6C,GAAGA,CAAA,KAAM;EACjE,OAAO,IAAIC,OAAO,CAAC,CAACC,OAAO,EAAEC,MAAM,KAAK;IACtCN,oBAAoB,aAApBA,oBAAoB,uBAApBA,oBAAoB,CAAEO,eAAe,CAAC,CAAC,CACpCC,IAAI,CAAEC,UAAkB,IAAK;MAC5B,IAAI;QACFJ,OAAO,CAACK,IAAI,CAACC,KAAK,CAACF,UAAU,CAAC,CAAC;MACjC,CAAC,CAAC,OAAOG,CAAC,EAAE;QACVN,MAAM,CAAC,qBAAqB,CAAC;MAC/B;IACF,CAAC,CAAC,CACDO,KAAK,CAAC,MAAM;MACXP,MAAM,CAAC,6BAA6B,CAAC;IACvC,CAAC,CAAC;EACN,CAAC,CAAC;AACJ,CAAC;AAED,OAAO,MAAMQ,uBAAiD,GAAGA,CAAA,KAAM;EACrE,OAAO,IAAIV,OAAO,CAAC,CAACC,OAAO,EAAEC,MAAM,KAAK;IACtCN,oBAAoB,aAApBA,oBAAoB,uBAApBA,oBAAoB,CAAEe,iBAAiB,CAAC,CAAC,CACtCP,IAAI,CAAEQ,YAAoB,IAAK;MAC9B,IAAI;QACFX,OAAO,CAACK,IAAI,CAACC,KAAK,CAACK,YAAY,CAAC,CAAC;MACnC,CAAC,CAAC,OAAOJ,CAAC,EAAE;QACVN,MAAM,CAAC,uBAAuB,CAAC;MACjC;IACF,CAAC,CAAC,CACDO,KAAK,CAAC,MAAM;MACXP,MAAM,CAAC,+BAA+B,CAAC;IACzC,CAAC,CAAC;EACN,CAAC,CAAC;AACJ,CAAC;AAED,OAAO,MAAMW,0BAAuD,GAClEjB,oBAAoB,aAApBA,oBAAoB,uBAApBA,oBAAoB,CAAEkB,oBAAoB;AAE5C,OAAO,MAAMC,oBAA2C,GACtDnB,oBAAoB,aAApBA,oBAAoB,uBAApBA,oBAAoB,CAAEoB,mBAAmB;AAE3C,OAAO,MAAMC,cAAqC,GAChDrB,oBAAoB,aAApBA,oBAAoB,uBAApBA,oBAAoB,CAAEsB,QAAQ;AAEhC,OAAO,MAAMC,IAAgB,GAAGvB,oBAAoB,aAApBA,oBAAoB,uBAApBA,oBAAoB,CAAEuB,IAAI;AAE1D,OAAO,MAAMC,eAAsC,GACjDxB,oBAAoB,aAApBA,oBAAoB,uBAApBA,oBAAoB,CAAEyB,SAAS;AAEjC,OAAO,MAAMC,uBAA8D,GACzE1B,oBAAoB,aAApBA,oBAAoB,uBAApBA,oBAAoB,CAAE2B,iBAAiB;AAEzC,OAAO,MAAMC,
|
|
1
|
+
{"version":3,"names":["StallionNativeModule","setSdkTokenNative","updateSdkToken","getStallionMetaNative","Promise","resolve","reject","getStallionMeta","then","metaString","JSON","parse","_","catch","getStallionConfigNative","getStallionConfig","configString","toggleStallionSwitchNative","toggleStallionSwitch","downloadBundleNative","downloadStageBundle","onLaunchNative","onLaunch","sync","popEventsNative","popEvents","acknowledgeEventsNative","acknowledgeEvents","restart","_StallionNativeModule","call"],"sourceRoot":"../../../../src","sources":["main/utils/StallionNativeUtils.ts"],"mappings":"AAAA,OAAOA,oBAAoB,MAAM,4BAA4B;AAW7D,OAAO,MAAMC,iBAAqC,GAChDD,oBAAoB,aAApBA,oBAAoB,uBAApBA,oBAAoB,CAAEE,cAAc;AAEtC,OAAO,MAAMC,qBAA6C,GAAGA,CAAA,KAAM;EACjE,OAAO,IAAIC,OAAO,CAAC,CAACC,OAAO,EAAEC,MAAM,KAAK;IACtCN,oBAAoB,aAApBA,oBAAoB,uBAApBA,oBAAoB,CAAEO,eAAe,CAAC,CAAC,CACpCC,IAAI,CAAEC,UAAkB,IAAK;MAC5B,IAAI;QACFJ,OAAO,CAACK,IAAI,CAACC,KAAK,CAACF,UAAU,CAAC,CAAC;MACjC,CAAC,CAAC,OAAOG,CAAC,EAAE;QACVN,MAAM,CAAC,qBAAqB,CAAC;MAC/B;IACF,CAAC,CAAC,CACDO,KAAK,CAAC,MAAM;MACXP,MAAM,CAAC,6BAA6B,CAAC;IACvC,CAAC,CAAC;EACN,CAAC,CAAC;AACJ,CAAC;AAED,OAAO,MAAMQ,uBAAiD,GAAGA,CAAA,KAAM;EACrE,OAAO,IAAIV,OAAO,CAAC,CAACC,OAAO,EAAEC,MAAM,KAAK;IACtCN,oBAAoB,aAApBA,oBAAoB,uBAApBA,oBAAoB,CAAEe,iBAAiB,CAAC,CAAC,CACtCP,IAAI,CAAEQ,YAAoB,IAAK;MAC9B,IAAI;QACFX,OAAO,CAACK,IAAI,CAACC,KAAK,CAACK,YAAY,CAAC,CAAC;MACnC,CAAC,CAAC,OAAOJ,CAAC,EAAE;QACVN,MAAM,CAAC,uBAAuB,CAAC;MACjC;IACF,CAAC,CAAC,CACDO,KAAK,CAAC,MAAM;MACXP,MAAM,CAAC,+BAA+B,CAAC;IACzC,CAAC,CAAC;EACN,CAAC,CAAC;AACJ,CAAC;AAED,OAAO,MAAMW,0BAAuD,GAClEjB,oBAAoB,aAApBA,oBAAoB,uBAApBA,oBAAoB,CAAEkB,oBAAoB;AAE5C,OAAO,MAAMC,oBAA2C,GACtDnB,oBAAoB,aAApBA,oBAAoB,uBAApBA,oBAAoB,CAAEoB,mBAAmB;AAE3C,OAAO,MAAMC,cAAqC,GAChDrB,oBAAoB,aAApBA,oBAAoB,uBAApBA,oBAAoB,CAAEsB,QAAQ;AAEhC,OAAO,MAAMC,IAAgB,GAAGvB,oBAAoB,aAApBA,oBAAoB,uBAApBA,oBAAoB,CAAEuB,IAAI;AAE1D,OAAO,MAAMC,eAAsC,GACjDxB,oBAAoB,aAApBA,oBAAoB,uBAApBA,oBAAoB,CAAEyB,SAAS;AAEjC,OAAO,MAAMC,uBAA8D,GACzE1B,oBAAoB,aAApBA,oBAAoB,uBAApBA,oBAAoB,CAAE2B,iBAAiB;AAEzC,OAAO,MAAMC,OAAO,GAAGA,CAAA,KAAM;EAAA,IAAAC,qBAAA;EAC3B7B,oBAAoB,aAApBA,oBAAoB,wBAAA6B,qBAAA,GAApB7B,oBAAoB,CAAE4B,OAAO,cAAAC,qBAAA,uBAA7BA,qBAAA,CAAAC,IAAA,CAAA9B,oBAAgC,CAAC;AACnC,CAAC"}
|
|
@@ -8,5 +8,5 @@ export declare const onLaunchNative: TOnLaunchBundleNative;
|
|
|
8
8
|
export declare const sync: () => void;
|
|
9
9
|
export declare const popEventsNative: () => Promise<string>;
|
|
10
10
|
export declare const acknowledgeEventsNative: (eventIds: string) => Promise<string>;
|
|
11
|
-
export declare const restart: () =>
|
|
11
|
+
export declare const restart: () => void;
|
|
12
12
|
//# sourceMappingURL=StallionNativeUtils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StallionNativeUtils.d.ts","sourceRoot":"","sources":["../../../../src/main/utils/StallionNativeUtils.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,qBAAqB,EACrB,kBAAkB,EAClB,sBAAsB,EACtB,2BAA2B,EAC3B,qBAAqB,EACrB,wBAAwB,EACzB,MAAM,uBAAuB,CAAC;AAE/B,eAAO,MAAM,iBAAiB,EAAE,kBACM,CAAC;AAEvC,eAAO,MAAM,qBAAqB,EAAE,sBAcnC,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,wBAcrC,CAAC;AAEF,eAAO,MAAM,0BAA0B,EAAE,2BACG,CAAC;AAE7C,eAAO,MAAM,oBAAoB,EAAE,qBACQ,CAAC;AAE5C,eAAO,MAAM,cAAc,EAAE,qBACG,CAAC;AAEjC,eAAO,MAAM,IAAI,EAAE,MAAM,IAAiC,CAAC;AAE3D,eAAO,MAAM,eAAe,EAAE,MAAM,OAAO,CAAC,MAAM,CACjB,CAAC;AAElC,eAAO,MAAM,uBAAuB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CACjC,CAAC;AAE1C,eAAO,MAAM,OAAO,
|
|
1
|
+
{"version":3,"file":"StallionNativeUtils.d.ts","sourceRoot":"","sources":["../../../../src/main/utils/StallionNativeUtils.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,qBAAqB,EACrB,kBAAkB,EAClB,sBAAsB,EACtB,2BAA2B,EAC3B,qBAAqB,EACrB,wBAAwB,EACzB,MAAM,uBAAuB,CAAC;AAE/B,eAAO,MAAM,iBAAiB,EAAE,kBACM,CAAC;AAEvC,eAAO,MAAM,qBAAqB,EAAE,sBAcnC,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,wBAcrC,CAAC;AAEF,eAAO,MAAM,0BAA0B,EAAE,2BACG,CAAC;AAE7C,eAAO,MAAM,oBAAoB,EAAE,qBACQ,CAAC;AAE5C,eAAO,MAAM,cAAc,EAAE,qBACG,CAAC;AAEjC,eAAO,MAAM,IAAI,EAAE,MAAM,IAAiC,CAAC;AAE3D,eAAO,MAAM,eAAe,EAAE,MAAM,OAAO,CAAC,MAAM,CACjB,CAAC;AAElC,eAAO,MAAM,uBAAuB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CACjC,CAAC;AAE1C,eAAO,MAAM,OAAO,YAEnB,CAAC"}
|