@shortkitsdk/react-native 0.2.6 → 0.2.11

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.
Files changed (75) hide show
  1. package/ShortKitReactNative.podspec +1 -0
  2. package/android/build.gradle.kts +5 -1
  3. package/android/src/main/java/com/shortkit/reactnative/ReactCarouselOverlayHost.kt +319 -0
  4. package/android/src/main/java/com/shortkit/reactnative/ReactLoadingHost.kt +40 -0
  5. package/android/src/main/java/com/shortkit/reactnative/ReactOverlayHost.kt +559 -0
  6. package/android/src/main/java/com/shortkit/reactnative/ShortKitBridge.kt +984 -0
  7. package/android/src/main/java/com/shortkit/reactnative/ShortKitFeedView.kt +88 -220
  8. package/android/src/main/java/com/shortkit/reactnative/ShortKitFeedViewManager.kt +12 -3
  9. package/android/src/main/java/com/shortkit/reactnative/ShortKitModule.kt +123 -741
  10. package/android/src/main/java/com/shortkit/reactnative/ShortKitPlayerNativeView.kt +2 -2
  11. package/android/src/main/java/com/shortkit/reactnative/ShortKitWidgetNativeView.kt +2 -2
  12. package/ios/ReactCarouselOverlayHost.swift +177 -0
  13. package/ios/ReactLoadingHost.swift +38 -0
  14. package/ios/ReactOverlayHost.swift +458 -0
  15. package/ios/SKFabricSurfaceWrapper.h +18 -0
  16. package/ios/SKFabricSurfaceWrapper.mm +57 -0
  17. package/ios/ShortKitBridge.swift +186 -63
  18. package/ios/ShortKitFeedView.swift +62 -229
  19. package/ios/ShortKitFeedViewManager.mm +3 -2
  20. package/ios/ShortKitModule.mm +66 -37
  21. package/ios/ShortKitPlayerNativeView.swift +39 -8
  22. package/ios/ShortKitReactNative-Bridging-Header.h +2 -0
  23. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +1 -1
  24. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.abi.json +2380 -522
  25. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +39 -12
  26. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  27. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftinterface +39 -12
  28. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/ShortKitSDK +0 -0
  29. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +1 -1
  30. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +2380 -522
  31. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +39 -12
  32. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  33. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +39 -12
  34. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/ShortKitSDK +0 -0
  35. package/ios/ShortKitSDK.xcframework.bak/Info.plist +43 -0
  36. package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +418 -0
  37. package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Info.plist +16 -0
  38. package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.abi.json +28917 -0
  39. package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +824 -0
  40. package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  41. package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftinterface +824 -0
  42. package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Modules/module.modulemap +4 -0
  43. package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/ShortKitSDK +0 -0
  44. package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +418 -0
  45. package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Info.plist +16 -0
  46. package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +28917 -0
  47. package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +824 -0
  48. package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  49. package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +824 -0
  50. package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Modules/module.modulemap +4 -0
  51. package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/ShortKitSDK +0 -0
  52. package/ios/ShortKitWidgetNativeView.swift +3 -3
  53. package/package.json +1 -1
  54. package/src/ShortKitCarouselOverlaySurface.tsx +55 -0
  55. package/src/ShortKitCommands.ts +31 -0
  56. package/src/ShortKitContext.ts +6 -25
  57. package/src/ShortKitFeed.tsx +110 -41
  58. package/src/ShortKitLoadingSurface.tsx +24 -0
  59. package/src/ShortKitOverlaySurface.tsx +205 -0
  60. package/src/ShortKitPlayer.tsx +6 -7
  61. package/src/ShortKitProvider.tsx +27 -286
  62. package/src/index.ts +5 -3
  63. package/src/serialization.ts +19 -39
  64. package/src/specs/NativeShortKitModule.ts +58 -46
  65. package/src/specs/ShortKitFeedViewNativeComponent.ts +3 -2
  66. package/src/types.ts +78 -16
  67. package/src/useShortKit.ts +1 -3
  68. package/src/useShortKitPlayer.ts +7 -7
  69. package/android/src/main/java/com/shortkit/reactnative/ShortKitCarouselOverlayBridge.kt +0 -48
  70. package/android/src/main/java/com/shortkit/reactnative/ShortKitOverlayBridge.kt +0 -128
  71. package/ios/ShortKitCarouselOverlayBridge.swift +0 -219
  72. package/ios/ShortKitOverlayBridge.swift +0 -111
  73. package/src/CarouselOverlayManager.tsx +0 -70
  74. package/src/OverlayManager.tsx +0 -87
  75. package/src/useShortKitCarousel.ts +0 -29
@@ -18,25 +18,78 @@ import ShortKitSDK
18
18
  private var cancellables = Set<AnyCancellable>()
19
19
  private weak var delegate: ShortKitBridgeDelegateProtocol?
20
20
 
21
+ /// Surface presenter for creating RCTFabricSurface instances in overlay hosts.
22
+ /// Set from ShortKitModule.mm via setSurfacePresenter: (called by RCTInstance).
23
+ public var surfacePresenter: AnyObject?
24
+
25
+ /// Called from ShortKitModule when RCTInstance provides the surface presenter.
26
+ @objc public func updateSurfacePresenter(_ presenter: AnyObject?) {
27
+ self.surfacePresenter = presenter
28
+ }
29
+
30
+ /// Preload handles keyed by UUID, consumed by feed views via preloadId prop.
31
+ public var preloadHandles: [String: FeedPreload] = [:]
32
+
33
+ // MARK: - Feed Instance Registry
34
+
35
+ private struct WeakFeedRef {
36
+ weak var vc: ShortKitFeedViewController?
37
+ }
38
+
39
+ private var feedRegistry: [String: WeakFeedRef] = [:]
40
+ private var pendingOps: [String: [(ShortKitFeedViewController) -> Void]] = [:]
41
+
42
+ public func registerFeed(id: String, viewController vc: ShortKitFeedViewController) {
43
+ feedRegistry[id] = WeakFeedRef(vc: vc)
44
+
45
+ // Wire per-feed remaining content count callback
46
+ vc.onRemainingContentCountChange = { [weak self] count in
47
+ self?.emit("onRemainingContentCountChanged", body: [
48
+ "feedId": id,
49
+ "count": count
50
+ ])
51
+ }
52
+
53
+ // Replay buffered operations on the next run-loop tick so the VC's
54
+ // view hierarchy is fully set up after didMoveToWindow returns.
55
+ if let ops = pendingOps.removeValue(forKey: id) {
56
+ DispatchQueue.main.async { [weak vc] in
57
+ guard let vc = vc else { return }
58
+ for op in ops {
59
+ op(vc)
60
+ }
61
+ }
62
+ }
63
+ }
64
+
65
+ public func unregisterFeed(id: String) {
66
+ feedRegistry.removeValue(forKey: id)
67
+ // Note: pendingOps are preserved for this ID to survive detach/reattach cycles
68
+ }
69
+
70
+ private func feedViewController(for id: String) -> ShortKitFeedViewController? {
71
+ return feedRegistry[id]?.vc
72
+ }
73
+
21
74
  // MARK: - Init
22
75
 
23
76
  @objc public init(
24
77
  apiKey: String,
25
- config configJSON: String,
78
+ hasLoadingView: Bool,
26
79
  clientAppName: String?,
27
80
  clientAppVersion: String?,
28
81
  customDimensions customDimensionsJSON: String?,
29
- delegate: ShortKitBridgeDelegateProtocol
82
+ delegate: ShortKitBridgeDelegateProtocol,
83
+ surfacePresenter: AnyObject?
30
84
  ) {
31
85
  self.delegate = delegate
86
+ self.surfacePresenter = surfacePresenter
32
87
  super.init()
33
88
 
34
- let feedConfig = Self.parseFeedConfig(configJSON)
35
89
  let dimensions = Self.parseCustomDimensions(customDimensionsJSON)
36
90
 
37
91
  let sdk = ShortKit(
38
92
  apiKey: apiKey,
39
- config: feedConfig,
40
93
  clientAppName: clientAppName,
41
94
  clientAppVersion: clientAppVersion,
42
95
  customDimensions: dimensions
@@ -45,6 +98,14 @@ import ShortKitSDK
45
98
 
46
99
  ShortKitBridge.shared = self
47
100
 
101
+ if hasLoadingView {
102
+ sdk.loadingViewProvider = { [weak self] in
103
+ let host = ReactLoadingHost()
104
+ host.surfacePresenter = self?.surfacePresenter
105
+ return host
106
+ }
107
+ }
108
+
48
109
  subscribeToPublishers(sdk.player)
49
110
  sdk.delegate = self
50
111
  }
@@ -53,6 +114,9 @@ import ShortKitSDK
53
114
 
54
115
  @objc public func teardown() {
55
116
  cancellables.removeAll()
117
+ preloadHandles.removeAll()
118
+ feedRegistry.removeAll()
119
+ pendingOps.removeAll()
56
120
  shortKit = nil
57
121
  if ShortKitBridge.shared === self {
58
122
  ShortKitBridge.shared = nil
@@ -143,17 +207,31 @@ import ShortKitSDK
143
207
 
144
208
  // MARK: - Custom Feed
145
209
 
146
- @objc public func setFeedItems(_ json: String) {
210
+ @objc public func setFeedItems(_ feedId: String, items json: String) {
147
211
  guard let items = Self.parseFeedInputs(json) else { return }
148
- Task { @MainActor in
149
- self.shortKit?.setFeedItems(items)
212
+ DispatchQueue.main.async { [weak self] in
213
+ guard let self else { return }
214
+ if let vc = self.feedViewController(for: feedId) {
215
+ vc.setFeedItems(items)
216
+ } else {
217
+ self.pendingOps[feedId, default: []].append { vc in
218
+ vc.setFeedItems(items)
219
+ }
220
+ }
150
221
  }
151
222
  }
152
223
 
153
- @objc public func appendFeedItems(_ json: String) {
224
+ @objc public func appendFeedItems(_ feedId: String, items json: String) {
154
225
  guard let items = Self.parseFeedInputs(json) else { return }
155
- Task { @MainActor in
156
- self.shortKit?.appendFeedItems(items)
226
+ DispatchQueue.main.async { [weak self] in
227
+ guard let self else { return }
228
+ if let vc = self.feedViewController(for: feedId) {
229
+ vc.appendFeedItems(items)
230
+ } else {
231
+ self.pendingOps[feedId, default: []].append { vc in
232
+ vc.appendFeedItems(items)
233
+ }
234
+ }
157
235
  }
158
236
  }
159
237
 
@@ -209,14 +287,25 @@ import ShortKitSDK
209
287
  }
210
288
  }
211
289
 
212
- @objc public func notifyOverlayReady() {
213
- NotificationCenter.default.post(name: .shortKitOverlayReady, object: nil)
290
+ @objc public func applyFilter(_ feedId: String, filterJSON: String?) {
291
+ let filter = filterJSON.flatMap { Self.parseFeedFilter($0) }
292
+ DispatchQueue.main.async { [weak self] in
293
+ guard let self else { return }
294
+ if let vc = self.feedViewController(for: feedId) {
295
+ vc.applyFilter(filter)
296
+ } else {
297
+ self.pendingOps[feedId, default: []].append { vc in
298
+ vc.applyFilter(filter)
299
+ }
300
+ }
301
+ }
214
302
  }
215
303
 
216
- @objc public func fetchContent(_ limit: Int, completion: @escaping (String) -> Void) {
304
+ @objc public func fetchContent(_ limit: Int, filterJSON: String?, completion: @escaping (String) -> Void) {
305
+ let filter = filterJSON.flatMap { Self.parseFeedFilter($0) }
217
306
  Task {
218
307
  do {
219
- let items = try await self.shortKit?.fetchContent(limit: limit) ?? []
308
+ let items = try await self.shortKit?.fetchContent(limit: limit, filter: filter) ?? []
220
309
  let data = try JSONEncoder().encode(items)
221
310
  let json = String(data: data, encoding: .utf8) ?? "[]"
222
311
  completion(json)
@@ -226,6 +315,22 @@ import ShortKitSDK
226
315
  }
227
316
  }
228
317
 
318
+ @objc public func preloadFeed(_ configJSON: String, completion: @escaping (String) -> Void) {
319
+ let config = Self.parseFeedConfig(configJSON)
320
+ guard let preload = shortKit?.preloadFeed(filter: config.filter) else {
321
+ completion("")
322
+ return
323
+ }
324
+ let uuid = UUID().uuidString
325
+ preloadHandles[uuid] = preload
326
+ completion(uuid)
327
+ }
328
+
329
+ /// Consume a preload handle by ID. Returns and removes the handle.
330
+ public func consumePreload(id: String) -> FeedPreload? {
331
+ return preloadHandles.removeValue(forKey: id)
332
+ }
333
+
229
334
  // MARK: - Combine Subscriptions
230
335
 
231
336
  private func subscribeToPublishers(_ player: ShortKitPlayer) {
@@ -382,22 +487,16 @@ import ShortKitSDK
382
487
  }
383
488
  .store(in: &cancellables)
384
489
 
385
- // Remaining content count
386
- player.remainingContentCount
387
- .receive(on: DispatchQueue.main)
388
- .sink { [weak self] count in
389
- self?.emit("onRemainingContentCountChanged", body: ["count": count])
390
- }
391
- .store(in: &cancellables)
490
+ // Remaining content count — now handled per-feed via onRemainingContentCountChange callback
392
491
  }
393
492
 
394
493
  // MARK: - Event Emission Helpers
395
494
 
396
- private func emit(_ name: String, body: [String: Any]) {
495
+ func emit(_ name: String, body: [String: Any]) {
397
496
  delegate?.emitEvent(name, body: body)
398
497
  }
399
498
 
400
- private func emitOnMain(_ name: String, body: [String: Any]) {
499
+ func emitOnMain(_ name: String, body: [String: Any]) {
401
500
  if Thread.isMainThread {
402
501
  emit(name, body: body)
403
502
  } else {
@@ -407,32 +506,6 @@ import ShortKitSDK
407
506
  }
408
507
  }
409
508
 
410
- // MARK: - Overlay Lifecycle Events (called by Fabric view in Task 13)
411
-
412
- /// Emit overlay lifecycle events from the Fabric view's overlay container.
413
- public func emitOverlayEvent(_ name: String, item: ContentItem) {
414
- emitOnMain(name, body: ["item": serializeContentItemToJSON(item)])
415
- }
416
-
417
- /// Emit a raw overlay event with an arbitrary body.
418
- public func emitOverlayEvent(_ name: String, body: [String: Any]) {
419
- emitOnMain(name, body: body)
420
- }
421
-
422
- // MARK: - Carousel Overlay Lifecycle Events
423
-
424
- /// Emit carousel overlay lifecycle events with an ImageCarouselItem.
425
- public func emitCarouselOverlayEvent(_ name: String, item: ImageCarouselItem) {
426
- guard let data = try? JSONEncoder().encode(item),
427
- let json = String(data: data, encoding: .utf8) else { return }
428
- emitOnMain(name, body: ["item": json])
429
- }
430
-
431
- /// Emit a raw carousel overlay event with an arbitrary body.
432
- public func emitCarouselOverlayEvent(_ name: String, body: [String: Any]) {
433
- emitOnMain(name, body: body)
434
- }
435
-
436
509
  // MARK: - Content Item Serialization
437
510
 
438
511
  /// Serialize a ContentItem to a JSON string for bridge transport.
@@ -487,6 +560,9 @@ import ShortKitSDK
487
560
  if let commentCount = item.commentCount {
488
561
  dict["commentCount"] = commentCount
489
562
  }
563
+ if let fallbackUrl = item.fallbackUrl {
564
+ dict["fallbackUrl"] = fallbackUrl
565
+ }
490
566
 
491
567
  return dict
492
568
  }
@@ -530,7 +606,7 @@ import ShortKitSDK
530
606
  /// {"feedHeight":"{\"type\":\"fullscreen\"}","overlay":"\"none\"",
531
607
  /// "carouselMode":"\"none\"","surveyMode":"\"none\"","muteOnStart":true}
532
608
  /// ```
533
- private static func parseFeedConfig(_ json: String) -> FeedConfig {
609
+ public static func parseFeedConfig(_ json: String) -> FeedConfig {
534
610
  guard let data = json.data(using: .utf8),
535
611
  let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
536
612
  return FeedConfig()
@@ -538,6 +614,7 @@ import ShortKitSDK
538
614
 
539
615
  let feedHeight = parseFeedHeight(obj["feedHeight"] as? String)
540
616
  let muteOnStart = obj["muteOnStart"] as? Bool ?? true
617
+ let autoplay = obj["autoplay"] as? Bool ?? true
541
618
  let videoOverlay = parseVideoOverlay(obj["overlay"] as? String)
542
619
 
543
620
  let feedSourceStr = obj["feedSource"] as? String ?? "algorithmic"
@@ -545,6 +622,8 @@ import ShortKitSDK
545
622
 
546
623
  let carouselOverlay = parseCarouselOverlay(obj["carouselOverlay"] as? String)
547
624
 
625
+ let filter = parseFeedFilter(obj["filter"] as? String)
626
+
548
627
  return FeedConfig(
549
628
  feedHeight: feedHeight,
550
629
  videoOverlay: videoOverlay,
@@ -552,7 +631,9 @@ import ShortKitSDK
552
631
  surveyOverlay: .none,
553
632
  adOverlay: .none,
554
633
  muteOnStart: muteOnStart,
555
- feedSource: feedSource
634
+ autoplay: autoplay,
635
+ feedSource: feedSource,
636
+ filter: filter
556
637
  )
557
638
  }
558
639
 
@@ -560,7 +641,7 @@ import ShortKitSDK
560
641
  ///
561
642
  /// Examples:
562
643
  /// - `"\"none\""` → `.none`
563
- /// - `"{\"type\":\"custom\"}"` → `.custom { ShortKitOverlayBridge() }`
644
+ /// - `"{\"type\":\"custom\"}"` → `.custom { ReactOverlayHost() }`
564
645
  private static func parseVideoOverlay(_ json: String?) -> VideoOverlayMode {
565
646
  guard let json,
566
647
  let data = json.data(using: .utf8),
@@ -577,10 +658,13 @@ import ShortKitSDK
577
658
  if let obj = parsed as? [String: Any],
578
659
  let type = obj["type"] as? String,
579
660
  type == "custom" {
661
+ let name = obj["name"] as? String ?? "Default"
580
662
  return .custom { @Sendable in
581
- let overlay = ShortKitOverlayBridge()
582
- overlay.bridge = ShortKitBridge.shared
583
- return overlay
663
+ let host = ReactOverlayHost()
664
+ host.surfacePresenter = ShortKitBridge.shared?.surfacePresenter
665
+ host.overlayModuleName = "ShortKitOverlay_\(name)"
666
+ host.bridge = ShortKitBridge.shared
667
+ return host
584
668
  }
585
669
  }
586
670
 
@@ -591,7 +675,7 @@ import ShortKitSDK
591
675
  ///
592
676
  /// Examples:
593
677
  /// - `"\"none\""` → `.none`
594
- /// - `"{\"type\":\"custom\"}"` → `.custom { ShortKitCarouselOverlayBridge() }`
678
+ /// - `"{\"type\":\"custom\",\"name\":\"news\"}"` → `.custom { ReactCarouselOverlayHost() }`
595
679
  private static func parseCarouselOverlay(_ json: String?) -> CarouselOverlayMode {
596
680
  guard let json,
597
681
  let data = json.data(using: .utf8),
@@ -606,10 +690,16 @@ import ShortKitSDK
606
690
  if let obj = parsed as? [String: Any],
607
691
  let type = obj["type"] as? String,
608
692
  type == "custom" {
693
+ let name = obj["name"] as? String ?? "Default"
609
694
  return .custom { @Sendable in
610
- let overlay = ShortKitCarouselOverlayBridge()
611
- overlay.bridge = ShortKitBridge.shared
612
- return overlay
695
+ let host = ReactCarouselOverlayHost()
696
+ host.surfacePresenter = ShortKitBridge.shared?.surfacePresenter
697
+ host.bridge = ShortKitBridge.shared
698
+ host.carouselOverlayModuleName = "ShortKitCarouselOverlay_\(name)"
699
+ // Eagerly create the RN surface so it's mounted and ready before
700
+ // the cell scrolls into view, matching video overlay behaviour.
701
+ host.prepareSurface()
702
+ return host
613
703
  }
614
704
  }
615
705
 
@@ -637,6 +727,23 @@ import ShortKitSDK
637
727
  }
638
728
  }
639
729
 
730
+ /// Parse a JSON string of FeedFilter from the JS bridge.
731
+ private static func parseFeedFilter(_ json: String?) -> FeedFilter? {
732
+ guard let json,
733
+ let data = json.data(using: .utf8),
734
+ let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
735
+ return nil
736
+ }
737
+
738
+ return FeedFilter(
739
+ tags: obj["tags"] as? [String],
740
+ section: obj["section"] as? String,
741
+ author: obj["author"] as? String,
742
+ contentType: obj["contentType"] as? String,
743
+ metadata: obj["metadata"] as? [String: String]
744
+ )
745
+ }
746
+
640
747
  /// Parse a JSON string of FeedInput[] from the JS bridge.
641
748
  private static func parseFeedInputs(_ json: String) -> [FeedInput]? {
642
749
  guard let data = json.data(using: .utf8),
@@ -650,7 +757,8 @@ import ShortKitSDK
650
757
  switch type {
651
758
  case "video":
652
759
  guard let playbackId = obj["playbackId"] as? String else { continue }
653
- result.append(.video(playbackId: playbackId))
760
+ let fallbackUrl = obj["fallbackUrl"] as? String
761
+ result.append(.video(playbackId: playbackId, fallbackUrl: fallbackUrl))
654
762
  case "imageCarousel":
655
763
  guard let itemData = obj["item"],
656
764
  let itemJSON = try? JSONSerialization.data(withJSONObject: itemData),
@@ -685,10 +793,25 @@ extension ShortKitBridge: ShortKitDelegate {
685
793
  "index": index
686
794
  ])
687
795
  }
796
+
797
+ public func shortKitDidRequestRefresh(_ shortKit: ShortKit) {
798
+ emitOnMain("onRefreshRequested", body: [:])
799
+ }
800
+
801
+ public func shortKit(_ shortKit: ShortKit, didFetchContentItems items: [ContentItem]) {
802
+ Task {
803
+ let data = try? JSONEncoder().encode(items)
804
+ let json = data.flatMap { String(data: $0, encoding: .utf8) } ?? "[]"
805
+ self.emitOnMain("onDidFetchContentItems", body: ["items": json])
806
+ }
807
+ }
688
808
  }
689
809
 
690
- // MARK: - Notification Names
810
+ // MARK: - Dismiss Emission
691
811
 
692
- extension Notification.Name {
693
- static let shortKitOverlayReady = Notification.Name("ShortKitOverlayReady")
812
+ extension ShortKitBridge {
813
+ @objc public func emitDismiss() {
814
+ emitOnMain("onDismiss", body: [:])
815
+ }
694
816
  }
817
+