pulse-updates 1.0.7 → 1.0.8
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/app/pulse/updates/PulseController.kt +3 -0
- package/android/src/main/java/app/pulse/updates/launcher/PulseAppLauncher.kt +97 -13
- package/ios/PulseUpdates/AppLauncher/PulseAppLauncher.swift +171 -39
- package/ios/PulseUpdates/PulseController.swift +123 -24
- package/ios/PulseUpdates/PulseUpdates.swift +2 -1
- package/lib/commonjs/assetResolver.js +0 -13
- package/lib/commonjs/assetResolver.js.map +1 -1
- package/lib/module/assetResolver.js +0 -13
- package/lib/module/assetResolver.js.map +1 -1
- package/lib/typescript/assetResolver.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/assetResolver.ts +0 -13
|
@@ -199,6 +199,9 @@ class PulseController private constructor() {
|
|
|
199
199
|
fun initializeWithoutStarting() {
|
|
200
200
|
if (isStarted) return
|
|
201
201
|
|
|
202
|
+
// Mark as started to prevent re-initialization during reload
|
|
203
|
+
isStarted = true
|
|
204
|
+
|
|
202
205
|
createDirectories()
|
|
203
206
|
initializeDatabase()
|
|
204
207
|
loadEmbeddedManifest()
|
|
@@ -114,6 +114,12 @@ class PulseAppLauncher(
|
|
|
114
114
|
try {
|
|
115
115
|
val assets = database.assetsForUpdate(update.updateId)
|
|
116
116
|
|
|
117
|
+
pulseLog(TAG, "ensureAllAssetsExist for updateId=${update.updateId.take(16)}... found ${assets.size} assets")
|
|
118
|
+
|
|
119
|
+
// Initialize asset map with embedded assets (these are referenced directly, not copied)
|
|
120
|
+
val mutableAssetFilesMap = buildEmbeddedAssetsMap().toMutableMap()
|
|
121
|
+
pulseLog(TAG, "Embedded assets map has ${mutableAssetFilesMap.size} entries")
|
|
122
|
+
|
|
117
123
|
if (assets.isEmpty()) {
|
|
118
124
|
// No assets to verify, just find the bundle
|
|
119
125
|
update.bundleHash?.let { hash ->
|
|
@@ -123,16 +129,59 @@ class PulseAppLauncher(
|
|
|
123
129
|
return
|
|
124
130
|
}
|
|
125
131
|
|
|
126
|
-
//
|
|
127
|
-
val
|
|
132
|
+
// Separate assets into those we need to verify vs those already in embedded
|
|
133
|
+
val assetsToVerify = mutableListOf<PulseAsset>()
|
|
134
|
+
|
|
135
|
+
for (asset in assets) {
|
|
136
|
+
if (asset.isLaunchAsset) {
|
|
137
|
+
// Always verify the launch asset (bundle) - it must be downloaded
|
|
138
|
+
assetsToVerify.add(asset)
|
|
139
|
+
} else {
|
|
140
|
+
val hashKey = asset.hash.lowercase()
|
|
141
|
+
// If asset hash is already in embedded map, we can use it directly
|
|
142
|
+
if (mutableAssetFilesMap.containsKey(hashKey)) {
|
|
143
|
+
// Already referenced from embedded, no action needed
|
|
144
|
+
continue
|
|
145
|
+
}
|
|
146
|
+
// Check if downloaded version exists
|
|
147
|
+
val localFile = assetStorePath(asset.hash)
|
|
148
|
+
if (localFile.exists()) {
|
|
149
|
+
// Use downloaded version
|
|
150
|
+
val localUri = localFile.toURI().toString()
|
|
151
|
+
mutableAssetFilesMap[hashKey] = localUri
|
|
152
|
+
asset.key?.let { key ->
|
|
153
|
+
mutableAssetFilesMap[key] = localUri
|
|
154
|
+
}
|
|
155
|
+
} else {
|
|
156
|
+
// Need to verify/download this asset
|
|
157
|
+
assetsToVerify.add(asset)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
pulseLog(TAG, "${assets.size - assetsToVerify.size} assets from embedded, ${assetsToVerify.size} to verify/download")
|
|
128
163
|
|
|
129
|
-
|
|
164
|
+
if (assetsToVerify.isEmpty()) {
|
|
165
|
+
// All assets accounted for, but we need the bundle
|
|
166
|
+
update.bundleHash?.let { hash ->
|
|
167
|
+
launchAssetFile = bundleStorePath(hash)
|
|
168
|
+
if (launchAssetFile?.exists() != true) {
|
|
169
|
+
callback(false, LauncherException("Bundle not found"))
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
assetFilesMap = mutableAssetFilesMap
|
|
174
|
+
callback(launchAssetFile != null, null)
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
val totalAssets = assetsToVerify.size
|
|
130
179
|
val completedAssets = java.util.concurrent.atomic.AtomicInteger(0)
|
|
131
180
|
val launchError = AtomicReference<Exception?>(null)
|
|
132
181
|
val launchAssetFound = AtomicBoolean(false)
|
|
133
182
|
val latch = CountDownLatch(totalAssets)
|
|
134
183
|
|
|
135
|
-
for (asset in
|
|
184
|
+
for (asset in assetsToVerify) {
|
|
136
185
|
val localFile = assetStorePath(asset.hash)
|
|
137
186
|
|
|
138
187
|
Thread {
|
|
@@ -146,9 +195,8 @@ class PulseAppLauncher(
|
|
|
146
195
|
} else {
|
|
147
196
|
val localUri = localFile.toURI().toString()
|
|
148
197
|
synchronized(mutableAssetFilesMap) {
|
|
149
|
-
|
|
150
|
-
mutableAssetFilesMap[
|
|
151
|
-
// Also store by path-based key for Metro compatibility
|
|
198
|
+
val hashKey = asset.hash.lowercase()
|
|
199
|
+
mutableAssetFilesMap[hashKey] = localUri
|
|
152
200
|
asset.key?.let { key ->
|
|
153
201
|
mutableAssetFilesMap[key] = localUri
|
|
154
202
|
}
|
|
@@ -368,11 +416,25 @@ data class EmbeddedManifest(
|
|
|
368
416
|
fun fromJson(json: String): EmbeddedManifest? {
|
|
369
417
|
return try {
|
|
370
418
|
val root = org.json.JSONObject(json)
|
|
371
|
-
|
|
419
|
+
// Support both formats: wrapped in "manifest" key or directly at root
|
|
420
|
+
val manifest = root.optJSONObject("manifest") ?: root
|
|
372
421
|
|
|
373
422
|
val updateId = manifest.getString("updateId")
|
|
374
423
|
val runtimeVersion = manifest.getString("runtimeVersion")
|
|
375
|
-
|
|
424
|
+
|
|
425
|
+
// Support both commitTime (ms) and createdAt (ISO 8601)
|
|
426
|
+
val commitTime = if (manifest.has("commitTime")) {
|
|
427
|
+
manifest.optLong("commitTime", System.currentTimeMillis())
|
|
428
|
+
} else if (manifest.has("createdAt")) {
|
|
429
|
+
try {
|
|
430
|
+
java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", java.util.Locale.US)
|
|
431
|
+
.parse(manifest.getString("createdAt"))?.time ?: System.currentTimeMillis()
|
|
432
|
+
} catch (e: Exception) {
|
|
433
|
+
System.currentTimeMillis()
|
|
434
|
+
}
|
|
435
|
+
} else {
|
|
436
|
+
System.currentTimeMillis()
|
|
437
|
+
}
|
|
376
438
|
|
|
377
439
|
val assets = mutableListOf<EmbeddedAsset>()
|
|
378
440
|
|
|
@@ -383,9 +445,21 @@ data class EmbeddedManifest(
|
|
|
383
445
|
}
|
|
384
446
|
}
|
|
385
447
|
|
|
386
|
-
// Parse launch asset
|
|
448
|
+
// Parse launch asset (either separate or from bundle field)
|
|
387
449
|
manifest.optJSONObject("launchAsset")?.let { launchAssetJson ->
|
|
388
450
|
EmbeddedAsset.fromJson(launchAssetJson, isLaunchAsset = true)?.let { assets.add(it) }
|
|
451
|
+
} ?: manifest.optJSONObject("bundle")?.let { bundleJson ->
|
|
452
|
+
val bundleHash = bundleJson.optString("hash", "")
|
|
453
|
+
if (bundleHash.isNotEmpty()) {
|
|
454
|
+
assets.add(EmbeddedAsset(
|
|
455
|
+
key = "bundle",
|
|
456
|
+
hash = bundleHash,
|
|
457
|
+
type = bundleJson.optString("contentType", null),
|
|
458
|
+
mainBundleDir = null,
|
|
459
|
+
mainBundleFilename = null,
|
|
460
|
+
isLaunchAsset = true
|
|
461
|
+
))
|
|
462
|
+
}
|
|
389
463
|
}
|
|
390
464
|
|
|
391
465
|
EmbeddedManifest(updateId, runtimeVersion, commitTime, assets)
|
|
@@ -440,10 +514,20 @@ data class PulseUpdateInfo(
|
|
|
440
514
|
)
|
|
441
515
|
|
|
442
516
|
class PulseDefaultSelectionPolicy : PulseSelectionPolicy {
|
|
517
|
+
/**
|
|
518
|
+
* Select the best update to launch
|
|
519
|
+
* Priority: 1) Ready updates (newest by commitTime), 2) Embedded updates (fallback)
|
|
520
|
+
*/
|
|
443
521
|
override fun selectUpdateToLaunch(updates: List<PulseUpdate>): PulseUpdate? {
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
522
|
+
// First, try to find the newest ready (downloaded) update
|
|
523
|
+
val readyUpdates = updates.filter { it.status == PulseUpdateStatus.READY }
|
|
524
|
+
val newestReady = readyUpdates.maxByOrNull { it.commitTime.time }
|
|
525
|
+
if (newestReady != null) {
|
|
526
|
+
return newestReady
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Fall back to embedded update
|
|
530
|
+
return updates.find { it.status == PulseUpdateStatus.EMBEDDED }
|
|
447
531
|
}
|
|
448
532
|
|
|
449
533
|
override fun selectUpdatesToDelete(launchedUpdate: PulseUpdateInfo, allUpdates: List<PulseUpdateInfo>): List<PulseUpdateInfo> {
|
|
@@ -126,6 +126,9 @@ final class PulseAppLauncher {
|
|
|
126
126
|
do {
|
|
127
127
|
let assets = try database.assets(forUpdateId: update.updateId)
|
|
128
128
|
|
|
129
|
+
// Initialize asset map with embedded assets (these are referenced directly, not copied)
|
|
130
|
+
assetFilesMap = buildEmbeddedAssetsMap()
|
|
131
|
+
|
|
129
132
|
if assets.isEmpty {
|
|
130
133
|
// No assets to verify, just find the bundle
|
|
131
134
|
if let bundleHash = update.bundleHash {
|
|
@@ -135,15 +138,54 @@ final class PulseAppLauncher {
|
|
|
135
138
|
return
|
|
136
139
|
}
|
|
137
140
|
|
|
138
|
-
//
|
|
139
|
-
|
|
141
|
+
// Separate assets into those we need to verify vs those already in embedded
|
|
142
|
+
var assetsToVerify: [PulseAsset] = []
|
|
143
|
+
|
|
144
|
+
for asset in assets {
|
|
145
|
+
if asset.isLaunchAsset {
|
|
146
|
+
// Always verify the launch asset (bundle) - it must be downloaded
|
|
147
|
+
assetsToVerify.append(asset)
|
|
148
|
+
} else {
|
|
149
|
+
let hashKey = asset.hash.lowercased()
|
|
150
|
+
// If asset hash is already in embedded map, we can use it directly
|
|
151
|
+
if assetFilesMap[hashKey] != nil {
|
|
152
|
+
// Already referenced from embedded, no action needed
|
|
153
|
+
continue
|
|
154
|
+
}
|
|
155
|
+
// Check if downloaded version exists
|
|
156
|
+
let localUrl = assetStorePath(hash: asset.hash)
|
|
157
|
+
if FileManager.default.fileExists(atPath: localUrl.path) {
|
|
158
|
+
// Use downloaded version
|
|
159
|
+
if let key = asset.key {
|
|
160
|
+
assetFilesMap[key] = localUrl.absoluteString
|
|
161
|
+
}
|
|
162
|
+
assetFilesMap[hashKey] = localUrl.absoluteString
|
|
163
|
+
} else {
|
|
164
|
+
// Need to verify/download this asset
|
|
165
|
+
assetsToVerify.append(asset)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if assetsToVerify.isEmpty {
|
|
171
|
+
// All assets accounted for, but we need the bundle
|
|
172
|
+
if let bundleHash = update.bundleHash {
|
|
173
|
+
launchAssetUrl = bundleStorePath(hash: bundleHash)
|
|
174
|
+
if !FileManager.default.fileExists(atPath: launchAssetUrl!.path) {
|
|
175
|
+
completion(false, PulseLauncherError.bundleNotFound)
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
completion(launchAssetUrl != nil, nil)
|
|
180
|
+
return
|
|
181
|
+
}
|
|
140
182
|
|
|
141
183
|
completedAssets = 0
|
|
142
184
|
launchError = nil
|
|
143
185
|
|
|
144
|
-
let totalAssets =
|
|
186
|
+
let totalAssets = assetsToVerify.count
|
|
145
187
|
|
|
146
|
-
for asset in
|
|
188
|
+
for asset in assetsToVerify {
|
|
147
189
|
let localUrl = assetStorePath(hash: asset.hash)
|
|
148
190
|
|
|
149
191
|
ensureAssetExists(asset: asset, localUrl: localUrl) { [weak self] exists in
|
|
@@ -155,8 +197,12 @@ final class PulseAppLauncher {
|
|
|
155
197
|
if exists {
|
|
156
198
|
if asset.isLaunchAsset {
|
|
157
199
|
self.launchAssetUrl = localUrl
|
|
158
|
-
} else
|
|
159
|
-
|
|
200
|
+
} else {
|
|
201
|
+
let hashKey = asset.hash.lowercased()
|
|
202
|
+
self.assetFilesMap[hashKey] = localUrl.absoluteString
|
|
203
|
+
if let key = asset.key {
|
|
204
|
+
self.assetFilesMap[key] = localUrl.absoluteString
|
|
205
|
+
}
|
|
160
206
|
}
|
|
161
207
|
}
|
|
162
208
|
|
|
@@ -229,22 +275,44 @@ final class PulseAppLauncher {
|
|
|
229
275
|
}
|
|
230
276
|
|
|
231
277
|
guard let match = matchingAsset,
|
|
232
|
-
let bundleFilename = match.nsBundleFilename
|
|
278
|
+
let bundleFilename = match.nsBundleFilename,
|
|
279
|
+
let assetType = match.type else {
|
|
233
280
|
completion(false)
|
|
234
281
|
return
|
|
235
282
|
}
|
|
236
283
|
|
|
237
|
-
// Get bundle path
|
|
284
|
+
// Get bundle path using FileManager (handles deep nested paths)
|
|
238
285
|
DispatchQueue.global(qos: .userInitiated).async {
|
|
239
|
-
let bundlePath
|
|
286
|
+
let bundleRoot = Bundle.main.bundlePath
|
|
287
|
+
let fileManager = FileManager.default
|
|
288
|
+
let ext = assetType.components(separatedBy: "/").last ?? "png"
|
|
289
|
+
var sourcePath: String? = nil
|
|
290
|
+
|
|
291
|
+
// 1. Try in assets folder with subdirectory path
|
|
292
|
+
if let dir = match.nsBundleDir, !dir.isEmpty {
|
|
293
|
+
let path = "\(bundleRoot)/assets/\(dir)/\(bundleFilename).\(ext)"
|
|
294
|
+
if fileManager.fileExists(atPath: path) {
|
|
295
|
+
sourcePath = path
|
|
296
|
+
}
|
|
297
|
+
}
|
|
240
298
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
299
|
+
// 2. Try without assets/ prefix
|
|
300
|
+
if sourcePath == nil, let dir = match.nsBundleDir, !dir.isEmpty {
|
|
301
|
+
let path = "\(bundleRoot)/\(dir)/\(bundleFilename).\(ext)"
|
|
302
|
+
if fileManager.fileExists(atPath: path) {
|
|
303
|
+
sourcePath = path
|
|
304
|
+
}
|
|
245
305
|
}
|
|
246
306
|
|
|
247
|
-
|
|
307
|
+
// 3. Try at root
|
|
308
|
+
if sourcePath == nil {
|
|
309
|
+
let path = "\(bundleRoot)/\(bundleFilename).\(ext)"
|
|
310
|
+
if fileManager.fileExists(atPath: path) {
|
|
311
|
+
sourcePath = path
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
guard let finalSourcePath = sourcePath else {
|
|
248
316
|
self.launcherQueue.async {
|
|
249
317
|
completion(false)
|
|
250
318
|
}
|
|
@@ -260,13 +328,13 @@ final class PulseAppLauncher {
|
|
|
260
328
|
)
|
|
261
329
|
|
|
262
330
|
// Copy file
|
|
263
|
-
try FileManager.default.copyItem(atPath:
|
|
331
|
+
try FileManager.default.copyItem(atPath: finalSourcePath, toPath: localUrl.path)
|
|
264
332
|
|
|
265
333
|
self.launcherQueue.async {
|
|
266
334
|
completion(true)
|
|
267
335
|
}
|
|
268
336
|
} catch {
|
|
269
|
-
|
|
337
|
+
pulseLogWarn("Failed to copy asset from bundle: \(error)")
|
|
270
338
|
self.launcherQueue.async {
|
|
271
339
|
completion(false)
|
|
272
340
|
}
|
|
@@ -310,7 +378,7 @@ final class PulseAppLauncher {
|
|
|
310
378
|
completion(true, nil)
|
|
311
379
|
}
|
|
312
380
|
} catch {
|
|
313
|
-
|
|
381
|
+
pulseLogWarn("Failed to download asset: \(error)")
|
|
314
382
|
self.launcherQueue.async {
|
|
315
383
|
completion(false, error)
|
|
316
384
|
}
|
|
@@ -337,29 +405,59 @@ final class PulseAppLauncher {
|
|
|
337
405
|
|
|
338
406
|
// MARK: - Embedded Assets Map
|
|
339
407
|
|
|
340
|
-
/// Build map of embedded assets (key -> file URL)
|
|
408
|
+
/// Build map of embedded assets (hash -> file URL, key -> file URL)
|
|
409
|
+
/// Uses FileManager to find assets with deep nested paths
|
|
341
410
|
private func buildEmbeddedAssetsMap() -> [String: String] {
|
|
342
411
|
guard let embedded = embeddedManifest ?? loadEmbeddedManifest() else {
|
|
343
412
|
return [:]
|
|
344
413
|
}
|
|
345
414
|
|
|
346
415
|
var map: [String: String] = [:]
|
|
416
|
+
let bundleRoot = Bundle.main.bundlePath
|
|
417
|
+
let fileManager = FileManager.default
|
|
347
418
|
|
|
348
419
|
for asset in embedded.assets where !asset.isLaunchAsset {
|
|
349
|
-
guard let
|
|
350
|
-
let
|
|
420
|
+
guard let filename = asset.nsBundleFilename,
|
|
421
|
+
let assetType = asset.type else {
|
|
351
422
|
continue
|
|
352
423
|
}
|
|
353
424
|
|
|
354
|
-
|
|
425
|
+
// Get file extension from type (e.g., "image/png" -> "png")
|
|
426
|
+
let ext = assetType.components(separatedBy: "/").last ?? "png"
|
|
427
|
+
var bundlePath: String? = nil
|
|
428
|
+
|
|
429
|
+
// 1. Try in assets folder with subdirectory path (RN stores assets here)
|
|
355
430
|
if let dir = asset.nsBundleDir, !dir.isEmpty {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
431
|
+
let path = "\(bundleRoot)/assets/\(dir)/\(filename).\(ext)"
|
|
432
|
+
if fileManager.fileExists(atPath: path) {
|
|
433
|
+
bundlePath = path
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// 2. Try without assets/ prefix
|
|
438
|
+
if bundlePath == nil, let dir = asset.nsBundleDir, !dir.isEmpty {
|
|
439
|
+
let path = "\(bundleRoot)/\(dir)/\(filename).\(ext)"
|
|
440
|
+
if fileManager.fileExists(atPath: path) {
|
|
441
|
+
bundlePath = path
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// 3. Try at root
|
|
446
|
+
if bundlePath == nil {
|
|
447
|
+
let path = "\(bundleRoot)/\(filename).\(ext)"
|
|
448
|
+
if fileManager.fileExists(atPath: path) {
|
|
449
|
+
bundlePath = path
|
|
450
|
+
}
|
|
359
451
|
}
|
|
360
452
|
|
|
361
453
|
if let path = bundlePath {
|
|
362
|
-
|
|
454
|
+
let url = URL(fileURLWithPath: path).absoluteString
|
|
455
|
+
// Store by hash (lowercased for consistency)
|
|
456
|
+
map[asset.hash.lowercased()] = url
|
|
457
|
+
// Also store by key for Metro compatibility
|
|
458
|
+
if let key = asset.key {
|
|
459
|
+
map[key] = url
|
|
460
|
+
}
|
|
363
461
|
}
|
|
364
462
|
}
|
|
365
463
|
|
|
@@ -373,13 +471,16 @@ final class PulseAppLauncher {
|
|
|
373
471
|
// MARK: - Embedded Manifest
|
|
374
472
|
|
|
375
473
|
private func loadEmbeddedManifest() -> PulseEmbeddedManifest? {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
474
|
+
// Try pulse/ subdirectory first, then root
|
|
475
|
+
let paths = ["pulse/embedded-manifest.json", "embedded-manifest.json"]
|
|
476
|
+
for path in paths {
|
|
477
|
+
let url = Bundle.main.bundleURL.appendingPathComponent(path)
|
|
478
|
+
if let data = try? Data(contentsOf: url),
|
|
479
|
+
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
|
|
480
|
+
return PulseEmbeddedManifest(json: json)
|
|
481
|
+
}
|
|
380
482
|
}
|
|
381
|
-
|
|
382
|
-
return PulseEmbeddedManifest(json: json)
|
|
483
|
+
return nil
|
|
383
484
|
}
|
|
384
485
|
}
|
|
385
486
|
|
|
@@ -420,8 +521,10 @@ struct PulseEmbeddedManifest {
|
|
|
420
521
|
let assets: [PulseEmbeddedAsset]
|
|
421
522
|
|
|
422
523
|
init?(json: [String: Any]) {
|
|
423
|
-
|
|
424
|
-
|
|
524
|
+
// Support both formats: wrapped in "manifest" key or directly at root
|
|
525
|
+
let manifest = (json["manifest"] as? [String: Any]) ?? json
|
|
526
|
+
|
|
527
|
+
guard let updateId = manifest["updateId"] as? String,
|
|
425
528
|
let runtimeVersion = manifest["runtimeVersion"] as? String else {
|
|
426
529
|
return nil
|
|
427
530
|
}
|
|
@@ -431,6 +534,11 @@ struct PulseEmbeddedManifest {
|
|
|
431
534
|
|
|
432
535
|
if let commitTimeMs = manifest["commitTime"] as? Double {
|
|
433
536
|
self.commitTime = Date(timeIntervalSince1970: commitTimeMs / 1000)
|
|
537
|
+
} else if let createdAt = manifest["createdAt"] as? String {
|
|
538
|
+
// Parse ISO 8601 date
|
|
539
|
+
let formatter = ISO8601DateFormatter()
|
|
540
|
+
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
|
541
|
+
self.commitTime = formatter.date(from: createdAt) ?? Date()
|
|
434
542
|
} else {
|
|
435
543
|
self.commitTime = Date()
|
|
436
544
|
}
|
|
@@ -446,11 +554,22 @@ struct PulseEmbeddedManifest {
|
|
|
446
554
|
}
|
|
447
555
|
}
|
|
448
556
|
|
|
449
|
-
// Parse launch asset
|
|
557
|
+
// Parse launch asset (either separate or from bundle field)
|
|
450
558
|
if let launchAssetJson = manifest["launchAsset"] as? [String: Any],
|
|
451
559
|
var launchAsset = PulseEmbeddedAsset(json: launchAssetJson) {
|
|
452
560
|
launchAsset.isLaunchAsset = true
|
|
453
561
|
parsedAssets.append(launchAsset)
|
|
562
|
+
} else if let bundleJson = manifest["bundle"] as? [String: Any] {
|
|
563
|
+
// Create launch asset from bundle field
|
|
564
|
+
var bundleAsset = PulseEmbeddedAsset(
|
|
565
|
+
key: "bundle",
|
|
566
|
+
hash: (bundleJson["hash"] as? String) ?? "",
|
|
567
|
+
type: bundleJson["contentType"] as? String,
|
|
568
|
+
nsBundleDir: nil,
|
|
569
|
+
nsBundleFilename: nil
|
|
570
|
+
)
|
|
571
|
+
bundleAsset.isLaunchAsset = true
|
|
572
|
+
parsedAssets.append(bundleAsset)
|
|
454
573
|
}
|
|
455
574
|
|
|
456
575
|
self.assets = parsedAssets
|
|
@@ -465,6 +584,14 @@ struct PulseEmbeddedAsset {
|
|
|
465
584
|
let nsBundleFilename: String?
|
|
466
585
|
var isLaunchAsset: Bool = false
|
|
467
586
|
|
|
587
|
+
init(key: String?, hash: String, type: String?, nsBundleDir: String?, nsBundleFilename: String?) {
|
|
588
|
+
self.key = key
|
|
589
|
+
self.hash = hash
|
|
590
|
+
self.type = type
|
|
591
|
+
self.nsBundleDir = nsBundleDir
|
|
592
|
+
self.nsBundleFilename = nsBundleFilename
|
|
593
|
+
}
|
|
594
|
+
|
|
468
595
|
init?(json: [String: Any]) {
|
|
469
596
|
guard let hash = json["hash"] as? String else {
|
|
470
597
|
return nil
|
|
@@ -492,12 +619,17 @@ protocol PulseSelectionPolicy {
|
|
|
492
619
|
|
|
493
620
|
struct PulseDefaultSelectionPolicy: PulseSelectionPolicy {
|
|
494
621
|
|
|
495
|
-
/// Select the
|
|
622
|
+
/// Select the best update to launch
|
|
623
|
+
/// Priority: 1) Ready updates (newest by commitTime), 2) Embedded updates (fallback)
|
|
496
624
|
func selectUpdateToLaunch(from updates: [PulseUpdate]) -> PulseUpdate? {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
625
|
+
// First, try to find the newest ready (downloaded) update
|
|
626
|
+
let readyUpdates = updates.filter { $0.status == .ready }
|
|
627
|
+
if let newest = readyUpdates.sorted(by: { $0.commitTime > $1.commitTime }).first {
|
|
628
|
+
return newest
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// Fall back to embedded update
|
|
632
|
+
return updates.first { $0.status == .embedded }
|
|
501
633
|
}
|
|
502
634
|
|
|
503
635
|
/// Select updates to delete - keeps launched update and one backup
|
|
@@ -70,6 +70,11 @@ public final class PulseController {
|
|
|
70
70
|
// Only valid for the immediate check->fetch sequence, cleared on use or new check
|
|
71
71
|
private var lastCheckManifest: PulseManifestModel?
|
|
72
72
|
|
|
73
|
+
// Flag to prevent concurrent fetch operations (with lock for thread safety)
|
|
74
|
+
private var isFetching = false
|
|
75
|
+
private var pendingFetchCallbacks: [(Result<PulseFetchResult, Error>) -> Void] = []
|
|
76
|
+
private let fetchLock = NSLock()
|
|
77
|
+
|
|
73
78
|
// MARK: - Native Config (from Info.plist, like expo-updates)
|
|
74
79
|
|
|
75
80
|
/// Load config from Info.plist (called before JS starts)
|
|
@@ -146,6 +151,9 @@ public final class PulseController {
|
|
|
146
151
|
public func initializeWithoutStarting() {
|
|
147
152
|
guard !isStarted else { return }
|
|
148
153
|
|
|
154
|
+
// Mark as started to prevent re-initialization during reload
|
|
155
|
+
isStarted = true
|
|
156
|
+
|
|
149
157
|
createDirectories()
|
|
150
158
|
initializeDatabase()
|
|
151
159
|
loadEmbeddedManifest()
|
|
@@ -296,17 +304,40 @@ public final class PulseController {
|
|
|
296
304
|
guard let manifest = embeddedManifest else { return [:] }
|
|
297
305
|
|
|
298
306
|
var assets: [String: String] = [:]
|
|
307
|
+
let bundleRoot = Bundle.main.bundlePath
|
|
308
|
+
let fileManager = FileManager.default
|
|
299
309
|
|
|
300
310
|
for asset in manifest.assets where !asset.isLaunchAsset {
|
|
301
|
-
guard let filename = asset.nsBundleFilename else {
|
|
311
|
+
guard let filename = asset.nsBundleFilename, let assetType = asset.type else {
|
|
302
312
|
continue
|
|
303
313
|
}
|
|
304
314
|
|
|
305
|
-
|
|
315
|
+
// Get file extension from type (e.g., "image/png" -> "png")
|
|
316
|
+
let ext = assetType.components(separatedBy: "/").last ?? "png"
|
|
317
|
+
var bundlePath: String? = nil
|
|
318
|
+
|
|
319
|
+
// 1. Try in assets folder with subdirectory path (RN stores assets here)
|
|
306
320
|
if let dir = asset.nsBundleDir, !dir.isEmpty {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
321
|
+
let path = "\(bundleRoot)/assets/\(dir)/\(filename).\(ext)"
|
|
322
|
+
if fileManager.fileExists(atPath: path) {
|
|
323
|
+
bundlePath = path
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// 2. Try without assets/ prefix
|
|
328
|
+
if bundlePath == nil, let dir = asset.nsBundleDir, !dir.isEmpty {
|
|
329
|
+
let path = "\(bundleRoot)/\(dir)/\(filename).\(ext)"
|
|
330
|
+
if fileManager.fileExists(atPath: path) {
|
|
331
|
+
bundlePath = path
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// 3. Try at root
|
|
336
|
+
if bundlePath == nil {
|
|
337
|
+
let path = "\(bundleRoot)/\(filename).\(ext)"
|
|
338
|
+
if fileManager.fileExists(atPath: path) {
|
|
339
|
+
bundlePath = path
|
|
340
|
+
}
|
|
310
341
|
}
|
|
311
342
|
|
|
312
343
|
if let path = bundlePath {
|
|
@@ -507,6 +538,17 @@ public final class PulseController {
|
|
|
507
538
|
return
|
|
508
539
|
}
|
|
509
540
|
|
|
541
|
+
// Thread-safe check and set of isFetching flag
|
|
542
|
+
fetchLock.lock()
|
|
543
|
+
if isFetching {
|
|
544
|
+
pendingFetchCallbacks.append(completion)
|
|
545
|
+
fetchLock.unlock()
|
|
546
|
+
pulseLog("fetchUpdate: already fetching, queuing callback")
|
|
547
|
+
return
|
|
548
|
+
}
|
|
549
|
+
isFetching = true
|
|
550
|
+
fetchLock.unlock()
|
|
551
|
+
|
|
510
552
|
// Use cached manifest from recent checkForUpdate if available
|
|
511
553
|
var manifestToUse = cachedManifest
|
|
512
554
|
if manifestToUse == nil, let cached = lastCheckManifest {
|
|
@@ -521,9 +563,28 @@ public final class PulseController {
|
|
|
521
563
|
config: config,
|
|
522
564
|
database: database,
|
|
523
565
|
directory: directory,
|
|
524
|
-
cachedManifest: manifestToUse
|
|
525
|
-
|
|
526
|
-
|
|
566
|
+
cachedManifest: manifestToUse
|
|
567
|
+
) { [weak self] result in
|
|
568
|
+
guard let self = self else {
|
|
569
|
+
completion(result)
|
|
570
|
+
return
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Thread-safe reset of isFetching and get pending callbacks
|
|
574
|
+
self.fetchLock.lock()
|
|
575
|
+
self.isFetching = false
|
|
576
|
+
let callbacks = self.pendingFetchCallbacks
|
|
577
|
+
self.pendingFetchCallbacks.removeAll()
|
|
578
|
+
self.fetchLock.unlock()
|
|
579
|
+
|
|
580
|
+
// Call original completion
|
|
581
|
+
completion(result)
|
|
582
|
+
|
|
583
|
+
// Call all queued callbacks with the same result
|
|
584
|
+
for callback in callbacks {
|
|
585
|
+
callback(result)
|
|
586
|
+
}
|
|
587
|
+
}
|
|
527
588
|
}
|
|
528
589
|
|
|
529
590
|
/// Reload the app with the new update
|
|
@@ -586,11 +647,13 @@ public final class PulseController {
|
|
|
586
647
|
|
|
587
648
|
private func loadEmbeddedManifest() {
|
|
588
649
|
// Try pulse/ subdirectory first, then root
|
|
589
|
-
|
|
590
|
-
|
|
650
|
+
let urlInPulse = Bundle.main.url(forResource: "embedded-manifest", withExtension: "json", subdirectory: "pulse")
|
|
651
|
+
let urlInRoot = Bundle.main.url(forResource: "embedded-manifest", withExtension: "json")
|
|
652
|
+
|
|
653
|
+
guard let url = urlInPulse ?? urlInRoot,
|
|
591
654
|
let data = try? Data(contentsOf: url),
|
|
592
655
|
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
|
|
593
|
-
pulseLog("No embedded manifest found")
|
|
656
|
+
pulseLog("No embedded manifest found or failed to parse")
|
|
594
657
|
return
|
|
595
658
|
}
|
|
596
659
|
|
|
@@ -598,17 +661,50 @@ public final class PulseController {
|
|
|
598
661
|
|
|
599
662
|
// Cache embedded asset hashes
|
|
600
663
|
if let manifest = embeddedManifest {
|
|
664
|
+
var foundCount = 0
|
|
665
|
+
var notFoundCount = 0
|
|
666
|
+
let bundleRoot = Bundle.main.bundlePath
|
|
667
|
+
let fileManager = FileManager.default
|
|
668
|
+
|
|
601
669
|
for asset in manifest.assets {
|
|
602
|
-
if let filename = asset.nsBundleFilename {
|
|
603
|
-
|
|
670
|
+
if let filename = asset.nsBundleFilename, let assetType = asset.type {
|
|
671
|
+
// Get file extension from type (e.g., "image/png" -> "png")
|
|
672
|
+
let ext = assetType.components(separatedBy: "/").last ?? "png"
|
|
673
|
+
var bundlePath: String? = nil
|
|
674
|
+
|
|
675
|
+
// 1. Try in assets folder with subdirectory path (RN stores assets here)
|
|
604
676
|
if let dir = asset.nsBundleDir, !dir.isEmpty {
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
677
|
+
let path = "\(bundleRoot)/assets/\(dir)/\(filename).\(ext)"
|
|
678
|
+
if fileManager.fileExists(atPath: path) {
|
|
679
|
+
bundlePath = path
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// 2. Try without assets/ prefix
|
|
684
|
+
if bundlePath == nil, let dir = asset.nsBundleDir, !dir.isEmpty {
|
|
685
|
+
let path = "\(bundleRoot)/\(dir)/\(filename).\(ext)"
|
|
686
|
+
if fileManager.fileExists(atPath: path) {
|
|
687
|
+
bundlePath = path
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// 3. Try at root
|
|
692
|
+
if bundlePath == nil {
|
|
693
|
+
let path = "\(bundleRoot)/\(filename).\(ext)"
|
|
694
|
+
if fileManager.fileExists(atPath: path) {
|
|
695
|
+
bundlePath = path
|
|
696
|
+
}
|
|
608
697
|
}
|
|
609
698
|
|
|
610
699
|
if let path = bundlePath {
|
|
611
700
|
embeddedAssetHashes[asset.hash.lowercased()] = URL(fileURLWithPath: path)
|
|
701
|
+
foundCount += 1
|
|
702
|
+
} else {
|
|
703
|
+
notFoundCount += 1
|
|
704
|
+
}
|
|
705
|
+
} else {
|
|
706
|
+
if !asset.isLaunchAsset {
|
|
707
|
+
notFoundCount += 1
|
|
612
708
|
}
|
|
613
709
|
}
|
|
614
710
|
|
|
@@ -616,6 +712,7 @@ public final class PulseController {
|
|
|
616
712
|
embeddedBundleHash = asset.hash.lowercased()
|
|
617
713
|
}
|
|
618
714
|
}
|
|
715
|
+
pulseLog("Assets found in bundle: \(foundCount), not found: \(notFoundCount)")
|
|
619
716
|
}
|
|
620
717
|
|
|
621
718
|
pulseLog("Loaded embedded manifest: \(embeddedManifest?.updateId ?? "unknown"), embeddedAssetHashes count: \(embeddedAssetHashes.count)")
|
|
@@ -1133,9 +1230,16 @@ final class PulseRemoteLoader {
|
|
|
1133
1230
|
}
|
|
1134
1231
|
|
|
1135
1232
|
// Move to final destination
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1233
|
+
do {
|
|
1234
|
+
try FileManager.default.createDirectory(at: destination.deletingLastPathComponent(), withIntermediateDirectories: true)
|
|
1235
|
+
if FileManager.default.fileExists(atPath: destination.path) {
|
|
1236
|
+
try FileManager.default.removeItem(at: destination)
|
|
1237
|
+
}
|
|
1238
|
+
try FileManager.default.moveItem(at: tempPath, to: destination)
|
|
1239
|
+
} catch {
|
|
1240
|
+
completion(.failure(error))
|
|
1241
|
+
return
|
|
1242
|
+
}
|
|
1139
1243
|
|
|
1140
1244
|
// Store in database as launch asset
|
|
1141
1245
|
storeBundleInDatabase()
|
|
@@ -1161,12 +1265,7 @@ final class PulseRemoteLoader {
|
|
|
1161
1265
|
|
|
1162
1266
|
let group = DispatchGroup()
|
|
1163
1267
|
var downloadError: Error?
|
|
1164
|
-
|
|
1165
1268
|
let embeddedHashes = PulseController.shared.embeddedAssetHashes
|
|
1166
|
-
pulseLog("downloadAssets: embeddedAssetHashes count=\(embeddedHashes.count)")
|
|
1167
|
-
if !embeddedHashes.isEmpty {
|
|
1168
|
-
pulseLog("downloadAssets: first few embedded hashes: \(Array(embeddedHashes.keys.prefix(3)).map { String($0.prefix(16)) })")
|
|
1169
|
-
}
|
|
1170
1269
|
|
|
1171
1270
|
for asset in assets {
|
|
1172
1271
|
group.enter()
|
|
@@ -98,7 +98,8 @@ public class PulseUpdates: RCTEventEmitter {
|
|
|
98
98
|
// Local assets map for expo-asset compatibility
|
|
99
99
|
// Key: asset key from manifest, Value: local file:// URL
|
|
100
100
|
// expo-updates exposes this as localAssets
|
|
101
|
-
|
|
101
|
+
let assets = controller.localAssets
|
|
102
|
+
state["localAssets"] = assets.isEmpty ? [:] : assets
|
|
102
103
|
|
|
103
104
|
return state
|
|
104
105
|
}
|
|
@@ -105,22 +105,9 @@ function resolveLocalAsset(asset) {
|
|
|
105
105
|
const keysToTry = buildAssetKeys(asset);
|
|
106
106
|
for (const key of keysToTry) {
|
|
107
107
|
if (localAssets[key]) {
|
|
108
|
-
// Debug: log successful resolution
|
|
109
|
-
if (__DEV__) {
|
|
110
|
-
console.log(`[PulseAssetResolver] Resolved ${asset.name}.${asset.type} with key=${key.substring(0, 16)}...`);
|
|
111
|
-
}
|
|
112
108
|
return localAssets[key];
|
|
113
109
|
}
|
|
114
110
|
}
|
|
115
|
-
|
|
116
|
-
// Debug: log failed resolution
|
|
117
|
-
if (__DEV__) {
|
|
118
|
-
console.log(`[PulseAssetResolver] MISS for ${asset.name}.${asset.type}`, {
|
|
119
|
-
triedKeys: keysToTry.map(k => k.substring(0, 16) + '...'),
|
|
120
|
-
localAssetsCount: Object.keys(localAssets).length,
|
|
121
|
-
localAssetsSample: Object.keys(localAssets).slice(0, 3).map(k => k.substring(0, 16) + '...')
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
111
|
return null;
|
|
125
112
|
}
|
|
126
113
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["localAssets","isPatched","originalDefaultAsset","applyPatch","AssetSourceResolver","require","default","prototype","defaultAsset","Object","keys","length","localUri","resolveLocalAsset","asset","fromSource","e","call","apply","initializeAssetResolver","assets","updateLocalAssets","keysToTry","buildAssetKeys","key","
|
|
1
|
+
{"version":3,"names":["localAssets","isPatched","originalDefaultAsset","applyPatch","AssetSourceResolver","require","default","prototype","defaultAsset","Object","keys","length","localUri","resolveLocalAsset","asset","fromSource","e","call","apply","initializeAssetResolver","assets","updateLocalAssets","keysToTry","buildAssetKeys","key","name","type","httpServerLocation","hash","fileHashes","push","values","forEach","h","location","startsWith","slice","scales","scale","scaleSuffix","_default","exports"],"sourceRoot":"../../src","sources":["assetResolver.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -97,22 +97,9 @@ function resolveLocalAsset(asset) {
|
|
|
97
97
|
const keysToTry = buildAssetKeys(asset);
|
|
98
98
|
for (const key of keysToTry) {
|
|
99
99
|
if (localAssets[key]) {
|
|
100
|
-
// Debug: log successful resolution
|
|
101
|
-
if (__DEV__) {
|
|
102
|
-
console.log(`[PulseAssetResolver] Resolved ${asset.name}.${asset.type} with key=${key.substring(0, 16)}...`);
|
|
103
|
-
}
|
|
104
100
|
return localAssets[key];
|
|
105
101
|
}
|
|
106
102
|
}
|
|
107
|
-
|
|
108
|
-
// Debug: log failed resolution
|
|
109
|
-
if (__DEV__) {
|
|
110
|
-
console.log(`[PulseAssetResolver] MISS for ${asset.name}.${asset.type}`, {
|
|
111
|
-
triedKeys: keysToTry.map(k => k.substring(0, 16) + '...'),
|
|
112
|
-
localAssetsCount: Object.keys(localAssets).length,
|
|
113
|
-
localAssetsSample: Object.keys(localAssets).slice(0, 3).map(k => k.substring(0, 16) + '...')
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
103
|
return null;
|
|
117
104
|
}
|
|
118
105
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["localAssets","isPatched","originalDefaultAsset","applyPatch","AssetSourceResolver","require","default","prototype","defaultAsset","Object","keys","length","localUri","resolveLocalAsset","asset","fromSource","e","call","apply","initializeAssetResolver","assets","updateLocalAssets","keysToTry","buildAssetKeys","key","
|
|
1
|
+
{"version":3,"names":["localAssets","isPatched","originalDefaultAsset","applyPatch","AssetSourceResolver","require","default","prototype","defaultAsset","Object","keys","length","localUri","resolveLocalAsset","asset","fromSource","e","call","apply","initializeAssetResolver","assets","updateLocalAssets","keysToTry","buildAssetKeys","key","name","type","httpServerLocation","hash","fileHashes","push","values","forEach","h","location","startsWith","slice","scales","scale","scaleSuffix"],"sourceRoot":"../../src","sources":["assetResolver.ts"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,IAAIA,WAA0C,GAAG,IAAI;AACrD,IAAIC,SAAS,GAAG,KAAK;;AAErB;AACA,IAAIC,oBAAwC,GAAG,IAAI;;AAEnD;AACA;AACA;AACA;AACA,SAASC,UAAUA,CAAA,EAAS;EAC1B,IAAIF,SAAS,EAAE;EAEf,IAAI;IACF,MAAMG,mBAAmB,GAAGC,OAAO,CAAC,kDAAkD,CAAC,CAACC,OAAO;IAE/F,IAAI,CAACF,mBAAmB,IAAI,CAACA,mBAAmB,CAACG,SAAS,CAACC,YAAY,EAAE;MACvE;IACF;;IAEA;IACAN,oBAAoB,GAAGE,mBAAmB,CAACG,SAAS,CAACC,YAAY;;IAEjE;IACAJ,mBAAmB,CAACG,SAAS,CAACC,YAAY,GAAG,YAAgB;MAC3D,IAAI;QACF;QACA,IAAIR,WAAW,IAAIS,MAAM,CAACC,IAAI,CAACV,WAAW,CAAC,CAACW,MAAM,GAAG,CAAC,EAAE;UACtD,MAAMC,QAAQ,GAAGC,iBAAiB,CAAC,IAAI,CAACC,KAAK,CAAC;UAC9C,IAAIF,QAAQ,EAAE;YACZ,OAAO,IAAI,CAACG,UAAU,CAACH,QAAQ,CAAC;UAClC;QACF;MACF,CAAC,CAAC,OAAOI,CAAC,EAAE;QACV;MAAA;;MAGF;MACA,IAAI;QACF,OAAOd,oBAAoB,CAAEe,IAAI,CAAC,IAAI,CAAC;MACzC,CAAC,CAAC,OAAOD,CAAC,EAAE;QACV;QACA;QACA,OAAOd,oBAAoB,CAAEgB,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;MAC9C;IACF,CAAC;IAEDjB,SAAS,GAAG,IAAI;EAClB,CAAC,CAAC,OAAOe,CAAC,EAAE;IACV;EAAA;AAEJ;;AAEA;AACAb,UAAU,CAAC,CAAC;;AAEZ;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASgB,uBAAuBA,CAACC,MAAqC,EAAQ;EACnFpB,WAAW,GAAGoB,MAAM;EACpB;AACF;;AAEA;AACA;AACA;AACA,OAAO,SAASC,iBAAiBA,CAACD,MAAqC,EAAQ;EAC7EpB,WAAW,GAAGoB,MAAM;AACtB;;AAEA;AACA;AACA;AACA;AACA,SAASP,iBAAiBA,CAACC,KAAU,EAAiB;EACpD,IAAI,CAACd,WAAW,IAAI,CAACc,KAAK,EAAE;IAC1B,OAAO,IAAI;EACb;;EAEA;EACA,MAAMQ,SAAS,GAAGC,cAAc,CAACT,KAAK,CAAC;EAEvC,KAAK,MAAMU,GAAG,IAAIF,SAAS,EAAE;IAC3B,IAAItB,WAAW,CAACwB,GAAG,CAAC,EAAE;MACpB,OAAOxB,WAAW,CAACwB,GAAG,CAAC;IACzB;EACF;EAEA,OAAO,IAAI;AACb;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASD,cAAcA,CAACT,KAAU,EAAY;EAC5C,MAAMJ,IAAc,GAAG,EAAE;EACzB,MAAM;IAAEe,IAAI;IAAEC,IAAI;IAAEC,kBAAkB;IAAEC,IAAI;IAAEC;EAAW,CAAC,GAAGf,KAAK;;EAElE;EACA;EACA,IAAIc,IAAI,EAAE;IACRlB,IAAI,CAACoB,IAAI,CAACF,IAAI,CAAC;EACjB;;EAEA;EACA,IAAIC,UAAU,EAAE;IACdpB,MAAM,CAACsB,MAAM,CAACF,UAAU,CAAC,CAACG,OAAO,CAAEC,CAAM,IAAK;MAC5C,IAAI,OAAOA,CAAC,KAAK,QAAQ,IAAIA,CAAC,KAAKL,IAAI,EAAE;QACvClB,IAAI,CAACoB,IAAI,CAACG,CAAC,CAAC;MACd;IACF,CAAC,CAAC;EACJ;;EAEA;EACA,IAAIC,QAAQ,GAAGP,kBAAkB,IAAI,EAAE;;EAEvC;EACA,IAAIO,QAAQ,CAACC,UAAU,CAAC,GAAG,CAAC,EAAE;IAC5BD,QAAQ,GAAGA,QAAQ,CAACE,KAAK,CAAC,CAAC,CAAC;EAC9B;;EAEA;EACA,IAAIF,QAAQ,EAAE;IACZxB,IAAI,CAACoB,IAAI,CAAC,GAAGI,QAAQ,IAAIT,IAAI,IAAIC,IAAI,EAAE,CAAC;EAC1C;;EAEA;EACA,IAAIQ,QAAQ,IAAI,CAACA,QAAQ,CAACC,UAAU,CAAC,QAAQ,CAAC,EAAE;IAC9CzB,IAAI,CAACoB,IAAI,CAAC,UAAUI,QAAQ,IAAIT,IAAI,IAAIC,IAAI,EAAE,CAAC;EACjD;;EAEA;EACAhB,IAAI,CAACoB,IAAI,CAAC,GAAGL,IAAI,IAAIC,IAAI,EAAE,CAAC;;EAE5B;EACA,MAAMW,MAAM,GAAGvB,KAAK,CAACuB,MAAM,IAAI,CAAC,CAAC,CAAC;EAClC,KAAK,MAAMC,KAAK,IAAID,MAAM,EAAE;IAC1B,IAAIC,KAAK,KAAK,CAAC,EAAE;MACf,MAAMC,WAAW,GAAG,IAAID,KAAK,GAAG;MAChC,IAAIJ,QAAQ,EAAE;QACZxB,IAAI,CAACoB,IAAI,CAAC,GAAGI,QAAQ,IAAIT,IAAI,GAAGc,WAAW,IAAIb,IAAI,EAAE,CAAC;QACtD,IAAI,CAACQ,QAAQ,CAACC,UAAU,CAAC,QAAQ,CAAC,EAAE;UAClCzB,IAAI,CAACoB,IAAI,CAAC,UAAUI,QAAQ,IAAIT,IAAI,GAAGc,WAAW,IAAIb,IAAI,EAAE,CAAC;QAC/D;MACF;IACF;EACF;EAEA,OAAOhB,IAAI;AACb;AAEA,eAAe;EACbS,uBAAuB;EACvBE;AACF,CAAC","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assetResolver.d.ts","sourceRoot":"","sources":["../../src/assetResolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AA2DH;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,GAAG,IAAI,CAGnF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,GAAG,IAAI,CAE7E;;;;;
|
|
1
|
+
{"version":3,"file":"assetResolver.d.ts","sourceRoot":"","sources":["../../src/assetResolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AA2DH;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,GAAG,IAAI,CAGnF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,GAAG,IAAI,CAE7E;;;;;AAyFD,wBAGE"}
|
package/package.json
CHANGED
package/src/assetResolver.ts
CHANGED
|
@@ -101,23 +101,10 @@ function resolveLocalAsset(asset: any): string | null {
|
|
|
101
101
|
|
|
102
102
|
for (const key of keysToTry) {
|
|
103
103
|
if (localAssets[key]) {
|
|
104
|
-
// Debug: log successful resolution
|
|
105
|
-
if (__DEV__) {
|
|
106
|
-
console.log(`[PulseAssetResolver] Resolved ${asset.name}.${asset.type} with key=${key.substring(0, 16)}...`);
|
|
107
|
-
}
|
|
108
104
|
return localAssets[key];
|
|
109
105
|
}
|
|
110
106
|
}
|
|
111
107
|
|
|
112
|
-
// Debug: log failed resolution
|
|
113
|
-
if (__DEV__) {
|
|
114
|
-
console.log(`[PulseAssetResolver] MISS for ${asset.name}.${asset.type}`, {
|
|
115
|
-
triedKeys: keysToTry.map(k => k.substring(0, 16) + '...'),
|
|
116
|
-
localAssetsCount: Object.keys(localAssets).length,
|
|
117
|
-
localAssetsSample: Object.keys(localAssets).slice(0, 3).map(k => k.substring(0, 16) + '...')
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
|
|
121
108
|
return null;
|
|
122
109
|
}
|
|
123
110
|
|