@shortkitsdk/react-native 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/ShortKitReactNative.podspec +19 -0
  2. package/android/build.gradle.kts +34 -0
  3. package/android/src/main/java/com/shortkit/reactnative/ShortKitFeedView.kt +249 -0
  4. package/android/src/main/java/com/shortkit/reactnative/ShortKitFeedViewManager.kt +32 -0
  5. package/android/src/main/java/com/shortkit/reactnative/ShortKitModule.kt +769 -0
  6. package/android/src/main/java/com/shortkit/reactnative/ShortKitOverlayBridge.kt +101 -0
  7. package/android/src/main/java/com/shortkit/reactnative/ShortKitPackage.kt +40 -0
  8. package/app.plugin.js +1 -0
  9. package/ios/ShortKitBridge.swift +537 -0
  10. package/ios/ShortKitFeedView.swift +207 -0
  11. package/ios/ShortKitFeedViewManager.mm +29 -0
  12. package/ios/ShortKitModule.h +25 -0
  13. package/ios/ShortKitModule.mm +204 -0
  14. package/ios/ShortKitOverlayBridge.swift +91 -0
  15. package/ios/ShortKitReactNative-Bridging-Header.h +3 -0
  16. package/ios/ShortKitReactNative.podspec +19 -0
  17. package/package.json +50 -0
  18. package/plugin/build/index.d.ts +3 -0
  19. package/plugin/build/index.js +13 -0
  20. package/plugin/build/withShortKitAndroid.d.ts +8 -0
  21. package/plugin/build/withShortKitAndroid.js +32 -0
  22. package/plugin/build/withShortKitIOS.d.ts +8 -0
  23. package/plugin/build/withShortKitIOS.js +29 -0
  24. package/react-native.config.js +8 -0
  25. package/src/OverlayManager.tsx +87 -0
  26. package/src/ShortKitContext.ts +51 -0
  27. package/src/ShortKitFeed.tsx +203 -0
  28. package/src/ShortKitProvider.tsx +526 -0
  29. package/src/index.ts +26 -0
  30. package/src/serialization.ts +95 -0
  31. package/src/specs/NativeShortKitModule.ts +201 -0
  32. package/src/specs/ShortKitFeedViewNativeComponent.ts +13 -0
  33. package/src/types.ts +167 -0
  34. package/src/useShortKit.ts +20 -0
  35. package/src/useShortKitPlayer.ts +29 -0
@@ -0,0 +1,207 @@
1
+ import UIKit
2
+ import ShortKit
3
+
4
+ /// Fabric native view that embeds `ShortKitFeedViewController` using
5
+ /// UIViewController containment. Props are set by the view manager via
6
+ /// `@objc` setters.
7
+ ///
8
+ /// Also tracks the feed's scroll offset via KVO and applies a native
9
+ /// transform to the sibling RN overlay view so it moves with the active
10
+ /// cell during swipe transitions.
11
+ @objc public class ShortKitFeedView: UIView {
12
+
13
+ // MARK: - Props (set by RCTViewManager)
14
+
15
+ @objc public var config: String? {
16
+ didSet { /* reserved for future use */ }
17
+ }
18
+
19
+ @objc public var overlayType: String? {
20
+ didSet { /* overlay mode is determined at ShortKit init time */ }
21
+ }
22
+
23
+ // MARK: - Child VC
24
+
25
+ private var feedViewController: ShortKitFeedViewController?
26
+
27
+ // MARK: - Scroll Tracking
28
+
29
+ private var scrollObservation: NSKeyValueObservation?
30
+ /// Overlay for the currently active cell (nativeID="overlay-current").
31
+ private weak var currentOverlayView: UIView?
32
+ /// Overlay for the upcoming cell (nativeID="overlay-next").
33
+ private weak var nextOverlayView: UIView?
34
+ /// The page index from which the current scroll gesture started.
35
+ private var currentPage: Int = 0
36
+
37
+ // MARK: - Lifecycle
38
+
39
+ public override func didMoveToWindow() {
40
+ super.didMoveToWindow()
41
+
42
+ if window != nil {
43
+ embedFeedViewControllerIfNeeded()
44
+ }
45
+ }
46
+
47
+ public override func willMove(toWindow newWindow: UIWindow?) {
48
+ super.willMove(toWindow: newWindow)
49
+
50
+ if newWindow == nil {
51
+ removeFeedViewController()
52
+ }
53
+ }
54
+
55
+ public override func layoutSubviews() {
56
+ super.layoutSubviews()
57
+ feedViewController?.view.frame = bounds
58
+ }
59
+
60
+ // MARK: - VC Containment
61
+
62
+ private func embedFeedViewControllerIfNeeded() {
63
+ // Already embedded
64
+ guard feedViewController == nil else { return }
65
+
66
+ guard let sdk = ShortKitBridge.shared?.sdk else {
67
+ NSLog("[ShortKitFeedView] ShortKit SDK not initialized. Call ShortKitModule.initialize() first.")
68
+ return
69
+ }
70
+
71
+ guard let parentVC = findParentViewController() else {
72
+ NSLog("[ShortKitFeedView] Could not find parent UIViewController.")
73
+ return
74
+ }
75
+
76
+ let feedVC = ShortKitFeedViewController(shortKit: sdk)
77
+ self.feedViewController = feedVC
78
+
79
+ parentVC.addChild(feedVC)
80
+ feedVC.view.frame = bounds
81
+ feedVC.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
82
+ addSubview(feedVC.view)
83
+ feedVC.didMove(toParent: parentVC)
84
+
85
+ setupScrollTracking(feedVC)
86
+ }
87
+
88
+ private func removeFeedViewController() {
89
+ scrollObservation?.invalidate()
90
+ scrollObservation = nil
91
+ currentOverlayView?.transform = .identity
92
+ nextOverlayView?.transform = .identity
93
+ currentOverlayView = nil
94
+ nextOverlayView = nil
95
+
96
+ guard let feedVC = feedViewController else { return }
97
+
98
+ feedVC.willMove(toParent: nil)
99
+ feedVC.view.removeFromSuperview()
100
+ feedVC.removeFromParent()
101
+
102
+ self.feedViewController = nil
103
+ }
104
+
105
+ // MARK: - Scroll Tracking
106
+
107
+ private func setupScrollTracking(_ feedVC: ShortKitFeedViewController) {
108
+ guard let scrollView = findScrollView(in: feedVC.view) else {
109
+ NSLog("[ShortKitFeedView] Could not find scroll view in feed VC hierarchy.")
110
+ return
111
+ }
112
+
113
+ scrollObservation = scrollView.observe(\.contentOffset, options: [.new]) { [weak self] sv, _ in
114
+ self?.handleScrollOffset(sv)
115
+ }
116
+ }
117
+
118
+ private func handleScrollOffset(_ scrollView: UIScrollView) {
119
+ let cellHeight = scrollView.bounds.height
120
+ guard cellHeight > 0 else { return }
121
+
122
+ let offset = scrollView.contentOffset.y
123
+ let delta = offset - CGFloat(currentPage) * cellHeight
124
+
125
+ // Update currentPage when the scroll settles near a page boundary
126
+ let nearestPage = Int(round(offset / cellHeight))
127
+ if abs(offset - CGFloat(nearestPage) * cellHeight) < 1.0 {
128
+ currentPage = nearestPage
129
+ }
130
+
131
+ // Find the overlay views if not cached
132
+ if currentOverlayView == nil || nextOverlayView == nil {
133
+ findOverlayViews()
134
+ }
135
+
136
+ // Current overlay follows the active cell
137
+ currentOverlayView?.transform = CGAffineTransform(translationX: 0, y: -delta)
138
+
139
+ // Next overlay: positioned one page ahead in the scroll direction
140
+ if delta >= 0 {
141
+ // Forward scroll (or idle): next cell is below
142
+ nextOverlayView?.transform = CGAffineTransform(translationX: 0, y: cellHeight - delta)
143
+ } else {
144
+ // Backward scroll: next cell is above
145
+ nextOverlayView?.transform = CGAffineTransform(translationX: 0, y: -cellHeight - delta)
146
+ }
147
+ }
148
+
149
+ /// Find the sibling RN overlay views by nativeID (mapped to
150
+ /// accessibilityIdentifier on iOS). In the Fabric hierarchy, the
151
+ /// ShortKitFeedView and the overlay containers are children of the
152
+ /// same parent (wrapped by ShortKitFeed.tsx with overflow: hidden).
153
+ private func findOverlayViews() {
154
+ guard let parent = superview else { return }
155
+
156
+ // The nativeID views may be nested inside intermediate Fabric
157
+ // wrapper views, so we search recursively within siblings.
158
+ for sibling in parent.subviews where sibling !== self {
159
+ if let found = findView(withNativeID: "overlay-current", in: sibling) {
160
+ currentOverlayView = found
161
+ }
162
+ if let found = findView(withNativeID: "overlay-next", in: sibling) {
163
+ nextOverlayView = found
164
+ }
165
+ }
166
+ }
167
+
168
+ /// Recursively find a view by its accessibilityIdentifier (nativeID).
169
+ private func findView(withNativeID nativeID: String, in view: UIView) -> UIView? {
170
+ if view.accessibilityIdentifier == nativeID {
171
+ return view
172
+ }
173
+ for subview in view.subviews {
174
+ if let found = findView(withNativeID: nativeID, in: subview) {
175
+ return found
176
+ }
177
+ }
178
+ return nil
179
+ }
180
+
181
+ /// Recursively find the first UIScrollView in a view hierarchy.
182
+ private func findScrollView(in view: UIView) -> UIScrollView? {
183
+ if let sv = view as? UIScrollView {
184
+ return sv
185
+ }
186
+ for subview in view.subviews {
187
+ if let sv = findScrollView(in: subview) {
188
+ return sv
189
+ }
190
+ }
191
+ return nil
192
+ }
193
+
194
+ // MARK: - Helpers
195
+
196
+ /// Walk the responder chain to find the nearest UIViewController.
197
+ private func findParentViewController() -> UIViewController? {
198
+ var responder: UIResponder? = self
199
+ while let next = responder?.next {
200
+ if let vc = next as? UIViewController {
201
+ return vc
202
+ }
203
+ responder = next
204
+ }
205
+ return nil
206
+ }
207
+ }
@@ -0,0 +1,29 @@
1
+ #import <React/RCTViewManager.h>
2
+
3
+ // Import the Swift-generated header for ShortKitFeedView
4
+ #if __has_include(<ShortKitReactNative/ShortKitReactNative-Swift.h>)
5
+ #import <ShortKitReactNative/ShortKitReactNative-Swift.h>
6
+ #else
7
+ #import "ShortKitReactNative-Swift.h"
8
+ #endif
9
+
10
+ @interface ShortKitFeedViewManager : RCTViewManager
11
+ @end
12
+
13
+ @implementation ShortKitFeedViewManager
14
+
15
+ RCT_EXPORT_MODULE(ShortKitFeedView)
16
+
17
+ + (BOOL)requiresMainQueueSetup {
18
+ return YES;
19
+ }
20
+
21
+ - (UIView *)view {
22
+ return [[ShortKitFeedView alloc] init];
23
+ }
24
+
25
+ RCT_EXPORT_VIEW_PROPERTY(config, NSString)
26
+ RCT_EXPORT_VIEW_PROPERTY(overlayType, NSString)
27
+ RCT_EXPORT_VIEW_PROPERTY(templateName, NSString)
28
+
29
+ @end
@@ -0,0 +1,25 @@
1
+ #import <React/RCTEventEmitter.h>
2
+
3
+ #ifdef RCT_NEW_ARCH_ENABLED
4
+ #import <ShortKitSpecs/ShortKitSpecs.h>
5
+ #endif
6
+
7
+ @protocol ShortKitBridgeDelegateProtocol;
8
+
9
+ /// TurboModule bridge for the ShortKit iOS SDK.
10
+ /// Receives commands from JS, emits events back via codegen EventEmitter.
11
+ #ifdef RCT_NEW_ARCH_ENABLED
12
+ @interface ShortKitModule : RCTEventEmitter <NativeShortKitModuleSpec, ShortKitBridgeDelegateProtocol> {
13
+ @protected
14
+ facebook::react::EventEmitterCallback _eventEmitterCallback;
15
+ }
16
+ #else
17
+ @interface ShortKitModule : RCTEventEmitter <RCTBridgeModule, ShortKitBridgeDelegateProtocol>
18
+ #endif
19
+
20
+ @end
21
+
22
+ /// Protocol for ShortKitBridge (Swift) to emit events back to the module.
23
+ @protocol ShortKitBridgeDelegateProtocol <NSObject>
24
+ - (void)emitEvent:(NSString * _Nonnull)name body:(NSDictionary * _Nonnull)body;
25
+ @end
@@ -0,0 +1,204 @@
1
+ #import "ShortKitModule.h"
2
+ #import <React/RCTLog.h>
3
+
4
+ // Import the Swift-generated header for ShortKitBridge
5
+ #if __has_include(<ShortKitReactNative/ShortKitReactNative-Swift.h>)
6
+ #import <ShortKitReactNative/ShortKitReactNative-Swift.h>
7
+ #else
8
+ #import "ShortKitReactNative-Swift.h"
9
+ #endif
10
+
11
+ @implementation ShortKitModule {
12
+ ShortKitBridge *_shortKitBridge;
13
+ BOOL _hasListeners;
14
+ }
15
+
16
+ RCT_EXPORT_MODULE(ShortKitModule)
17
+
18
+ + (BOOL)requiresMainQueueSetup {
19
+ return YES;
20
+ }
21
+
22
+ /// All TurboModule methods run on the main queue.
23
+ /// This ensures thread safety for UIKit-backed ShortKit operations
24
+ /// and eliminates races on _hasListeners / _shortKitBridge.
25
+ - (dispatch_queue_t)methodQueue {
26
+ return dispatch_get_main_queue();
27
+ }
28
+
29
+ - (instancetype)init {
30
+ self = [super init];
31
+ return self;
32
+ }
33
+
34
+ /// Called when the RN bridge tears down (hot reload, app shutdown).
35
+ - (void)invalidate {
36
+ [_shortKitBridge teardown];
37
+ _shortKitBridge = nil;
38
+ [super invalidate];
39
+ }
40
+
41
+ // MARK: - RCTEventEmitter
42
+
43
+ - (NSArray<NSString *> *)supportedEvents {
44
+ return @[
45
+ @"onPlayerStateChanged",
46
+ @"onCurrentItemChanged",
47
+ @"onTimeUpdate",
48
+ @"onMutedChanged",
49
+ @"onPlaybackRateChanged",
50
+ @"onCaptionsEnabledChanged",
51
+ @"onActiveCaptionTrackChanged",
52
+ @"onActiveCueChanged",
53
+ @"onDidLoop",
54
+ @"onFeedTransition",
55
+ @"onFormatChange",
56
+ @"onPrefetchedAheadCountChanged",
57
+ @"onError",
58
+ @"onShareTapped",
59
+ @"onSurveyResponse",
60
+ @"onArticleTapped",
61
+ @"onCommentTapped",
62
+ @"onOverlayShareTapped",
63
+ @"onSaveTapped",
64
+ @"onLikeTapped",
65
+ @"onOverlayConfigure",
66
+ @"onOverlayActivate",
67
+ @"onOverlayReset",
68
+ @"onOverlayFadeOut",
69
+ @"onOverlayRestore",
70
+ @"onOverlayTap",
71
+ @"onOverlayDoubleTap",
72
+ ];
73
+ }
74
+
75
+ - (void)startObserving {
76
+ _hasListeners = YES;
77
+ }
78
+
79
+ - (void)stopObserving {
80
+ _hasListeners = NO;
81
+ }
82
+
83
+ // MARK: - Event Emitter Callback (New Architecture)
84
+
85
+ #ifdef RCT_NEW_ARCH_ENABLED
86
+ - (void)setEventEmitterCallback:(EventEmitterCallbackWrapper *)eventEmitterCallbackWrapper {
87
+ _eventEmitterCallback = std::move(eventEmitterCallbackWrapper->_eventEmitterCallback);
88
+ }
89
+ #endif
90
+
91
+ // MARK: - ShortKitBridgeDelegateProtocol
92
+
93
+ - (void)emitEvent:(NSString *)name body:(NSDictionary *)body {
94
+ #ifdef RCT_NEW_ARCH_ENABLED
95
+ // New Architecture: use the codegen EventEmitterCallback directly
96
+ if (_eventEmitterCallback) {
97
+ _eventEmitterCallback(std::string([name UTF8String]), body);
98
+ }
99
+ #else
100
+ if (_hasListeners) {
101
+ [self sendEventWithName:name body:body];
102
+ }
103
+ #endif
104
+ }
105
+
106
+ // MARK: - Lifecycle
107
+
108
+ RCT_EXPORT_METHOD(initialize:(NSString *)apiKey
109
+ config:(NSString *)config
110
+ clientAppName:(NSString *)clientAppName
111
+ clientAppVersion:(NSString *)clientAppVersion
112
+ customDimensions:(NSString *)customDimensions) {
113
+ // Tear down any existing instance to prevent leaks on re-initialize
114
+ [_shortKitBridge teardown];
115
+
116
+ _shortKitBridge = [[ShortKitBridge alloc] initWithApiKey:apiKey
117
+ config:config
118
+ clientAppName:clientAppName
119
+ clientAppVersion:clientAppVersion
120
+ customDimensions:customDimensions
121
+ delegate:self];
122
+ }
123
+
124
+ RCT_EXPORT_METHOD(destroy) {
125
+ [_shortKitBridge teardown];
126
+ _shortKitBridge = nil;
127
+ }
128
+
129
+ RCT_EXPORT_METHOD(setUserId:(NSString *)userId) {
130
+ [_shortKitBridge setUserId:userId];
131
+ }
132
+
133
+ RCT_EXPORT_METHOD(clearUserId) {
134
+ [_shortKitBridge clearUserId];
135
+ }
136
+
137
+ RCT_EXPORT_METHOD(onPause) {
138
+ [_shortKitBridge onPause];
139
+ }
140
+
141
+ RCT_EXPORT_METHOD(onResume) {
142
+ [_shortKitBridge onResume];
143
+ }
144
+
145
+ // MARK: - Player Controls
146
+
147
+ RCT_EXPORT_METHOD(play) {
148
+ [_shortKitBridge play];
149
+ }
150
+
151
+ RCT_EXPORT_METHOD(pause) {
152
+ [_shortKitBridge doPause];
153
+ }
154
+
155
+ RCT_EXPORT_METHOD(seek:(double)seconds) {
156
+ [_shortKitBridge seekTo:seconds];
157
+ }
158
+
159
+ RCT_EXPORT_METHOD(seekAndPlay:(double)seconds) {
160
+ [_shortKitBridge seekAndPlayTo:seconds];
161
+ }
162
+
163
+ RCT_EXPORT_METHOD(skipToNext) {
164
+ [_shortKitBridge skipToNext];
165
+ }
166
+
167
+ RCT_EXPORT_METHOD(skipToPrevious) {
168
+ [_shortKitBridge skipToPrevious];
169
+ }
170
+
171
+ RCT_EXPORT_METHOD(setMuted:(BOOL)muted) {
172
+ [_shortKitBridge setMuted:muted];
173
+ }
174
+
175
+ RCT_EXPORT_METHOD(setPlaybackRate:(double)rate) {
176
+ [_shortKitBridge setPlaybackRate:rate];
177
+ }
178
+
179
+ RCT_EXPORT_METHOD(setCaptionsEnabled:(BOOL)enabled) {
180
+ [_shortKitBridge setCaptionsEnabled:enabled];
181
+ }
182
+
183
+ RCT_EXPORT_METHOD(selectCaptionTrack:(NSString *)language) {
184
+ [_shortKitBridge selectCaptionTrack:language];
185
+ }
186
+
187
+ RCT_EXPORT_METHOD(sendContentSignal:(NSString *)signal) {
188
+ [_shortKitBridge sendContentSignal:signal];
189
+ }
190
+
191
+ RCT_EXPORT_METHOD(setMaxBitrate:(double)bitrate) {
192
+ [_shortKitBridge setMaxBitrate:bitrate];
193
+ }
194
+
195
+ // MARK: - New Architecture (TurboModule)
196
+
197
+ #ifdef RCT_NEW_ARCH_ENABLED
198
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
199
+ (const facebook::react::ObjCTurboModule::InitParams &)params {
200
+ return std::make_shared<facebook::react::NativeShortKitModuleSpecJSI>(params);
201
+ }
202
+ #endif
203
+
204
+ @end
@@ -0,0 +1,91 @@
1
+ import UIKit
2
+ import ShortKit
3
+
4
+ /// A transparent UIView that conforms to `FeedOverlay` and bridges overlay
5
+ /// lifecycle calls to JS events via `ShortKitBridge`.
6
+ ///
7
+ /// The actual overlay UI is rendered by React Native on the JS side through
8
+ /// the `OverlayManager` component. This view simply relays the SDK lifecycle
9
+ /// events so the JS overlay knows when to configure, activate, reset, etc.
10
+ @objc public class ShortKitOverlayBridge: UIView, @unchecked Sendable, FeedOverlay {
11
+
12
+ // MARK: - Bridge Reference
13
+
14
+ /// Weak reference to the bridge, set by the factory closure in `parseFeedConfig`.
15
+ weak var bridge: ShortKitBridge?
16
+
17
+ // MARK: - State
18
+
19
+ /// Stores the last configured ContentItem so we can pass it with lifecycle
20
+ /// events that don't receive the item as a parameter.
21
+ private var currentItem: ContentItem?
22
+
23
+ // MARK: - Init
24
+
25
+ override init(frame: CGRect) {
26
+ super.init(frame: frame)
27
+ backgroundColor = .clear
28
+ isUserInteractionEnabled = true
29
+ setupGestures()
30
+ }
31
+
32
+ required init?(coder: NSCoder) {
33
+ fatalError("init(coder:) is not supported")
34
+ }
35
+
36
+ // MARK: - Gestures
37
+
38
+ private func setupGestures() {
39
+ let doubleTap = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap(_:)))
40
+ doubleTap.numberOfTapsRequired = 2
41
+ addGestureRecognizer(doubleTap)
42
+
43
+ let singleTap = UITapGestureRecognizer(target: self, action: #selector(handleSingleTap))
44
+ singleTap.require(toFail: doubleTap)
45
+ addGestureRecognizer(singleTap)
46
+ }
47
+
48
+ @objc private func handleSingleTap() {
49
+ bridge?.emitOverlayEvent("onOverlayTap", body: [:])
50
+ }
51
+
52
+ @objc private func handleDoubleTap(_ gesture: UITapGestureRecognizer) {
53
+ let location = gesture.location(in: self)
54
+ bridge?.emitOverlayEvent("onOverlayDoubleTap", body: [
55
+ "x": location.x,
56
+ "y": location.y,
57
+ ])
58
+ }
59
+
60
+ // MARK: - FeedOverlay
61
+
62
+ public func attach(player: ShortKitPlayer) {
63
+ // No-op on the native side. The JS overlay subscribes to player
64
+ // state via the TurboModule's Combine publishers.
65
+ }
66
+
67
+ public func configure(with item: ContentItem) {
68
+ currentItem = item
69
+ bridge?.emitOverlayEvent("onOverlayConfigure", item: item)
70
+ }
71
+
72
+ public func resetPlaybackProgress() {
73
+ guard let item = currentItem else { return }
74
+ bridge?.emitOverlayEvent("onOverlayReset", item: item)
75
+ }
76
+
77
+ public func activatePlayback() {
78
+ guard let item = currentItem else { return }
79
+ bridge?.emitOverlayEvent("onOverlayActivate", item: item)
80
+ }
81
+
82
+ public func fadeOutForTransition() {
83
+ guard let item = currentItem else { return }
84
+ bridge?.emitOverlayEvent("onOverlayFadeOut", item: item)
85
+ }
86
+
87
+ public func restoreFromTransition() {
88
+ guard let item = currentItem else { return }
89
+ bridge?.emitOverlayEvent("onOverlayRestore", item: item)
90
+ }
91
+ }
@@ -0,0 +1,3 @@
1
+ #import <React/RCTBridgeModule.h>
2
+ #import <React/RCTEventEmitter.h>
3
+ #import "ShortKitModule.h"
@@ -0,0 +1,19 @@
1
+ require 'json'
2
+ package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
3
+
4
+ Pod::Spec.new do |s|
5
+ s.name = "ShortKitReactNative"
6
+ s.version = package["version"]
7
+ s.summary = package["description"]
8
+ s.homepage = "https://github.com/shortkit/shortkit"
9
+ s.license = package["license"]
10
+ s.author = "ShortKit"
11
+ s.platforms = { :ios => "16.0" }
12
+ s.source = { :git => "https://github.com/shortkit/shortkit.git", :tag => "v#{s.version}" }
13
+ s.source_files = "*.{h,m,mm,cpp,swift}"
14
+ s.requires_arc = true
15
+
16
+ s.dependency "ShortKit"
17
+
18
+ install_modules_dependencies(s)
19
+ end
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@shortkitsdk/react-native",
3
+ "version": "0.1.0",
4
+ "description": "ShortKit React Native SDK — short-form video feed",
5
+ "react-native": "src/index",
6
+ "source": "src/index",
7
+ "main": "src/index.ts",
8
+ "types": "src/index.ts",
9
+ "license": "MIT",
10
+ "files": [
11
+ "src",
12
+ "ios",
13
+ "android",
14
+ "ShortKitReactNative.podspec",
15
+ "app.plugin.js",
16
+ "plugin/build",
17
+ "react-native.config.js",
18
+ "!android/build",
19
+ "!ios/build",
20
+ "!**/__tests__",
21
+ "!**/__mocks__"
22
+ ],
23
+ "scripts": {
24
+ "build:plugin": "tsc --project plugin/tsconfig.json",
25
+ "test": "jest",
26
+ "lint": "tsc --noEmit"
27
+ },
28
+ "peerDependencies": {
29
+ "react": ">= 18.3.1",
30
+ "react-native": ">= 0.76.0"
31
+ },
32
+ "devDependencies": {
33
+ "@expo/config-plugins": "^9.0.0",
34
+ "@types/react": "^19.0.0",
35
+ "react": "^19.0.0",
36
+ "react-native": "^0.76.0",
37
+ "typescript": "^5.8.0",
38
+ "jest": "^29.0.0",
39
+ "@testing-library/react-native": "^12.0.0",
40
+ "react-test-renderer": "^19.0.0"
41
+ },
42
+ "codegenConfig": {
43
+ "name": "ShortKitSpecs",
44
+ "type": "all",
45
+ "jsSrcsDir": "./src/specs",
46
+ "android": {
47
+ "javaPackageName": "com.shortkit.reactnative"
48
+ }
49
+ }
50
+ }
@@ -0,0 +1,3 @@
1
+ import { ConfigPlugin } from '@expo/config-plugins';
2
+ declare const _default: ConfigPlugin<void>;
3
+ export default _default;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const config_plugins_1 = require("@expo/config-plugins");
4
+ const withShortKitIOS_1 = require("./withShortKitIOS");
5
+ const withShortKitAndroid_1 = require("./withShortKitAndroid");
6
+ // Read version from package.json for the plugin wrapper
7
+ const pkg = require('../../package.json');
8
+ const withShortKit = (config) => {
9
+ config = (0, withShortKitIOS_1.withShortKitIOS)(config);
10
+ config = (0, withShortKitAndroid_1.withShortKitAndroid)(config);
11
+ return config;
12
+ };
13
+ exports.default = (0, config_plugins_1.createRunOncePlugin)(withShortKit, pkg.name, pkg.version);
@@ -0,0 +1,8 @@
1
+ import { ConfigPlugin } from '@expo/config-plugins';
2
+ /**
3
+ * Android config plugin for ShortKit.
4
+ *
5
+ * - Ensures INTERNET permission is present (usually already added by Expo).
6
+ * - Sets minSdkVersion to 24 if lower.
7
+ */
8
+ export declare const withShortKitAndroid: ConfigPlugin;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.withShortKitAndroid = void 0;
4
+ const config_plugins_1 = require("@expo/config-plugins");
5
+ /**
6
+ * Android config plugin for ShortKit.
7
+ *
8
+ * - Ensures INTERNET permission is present (usually already added by Expo).
9
+ * - Sets minSdkVersion to 24 if lower.
10
+ */
11
+ const withShortKitAndroid = (config) => {
12
+ // Ensure minSdkVersion >= 24
13
+ config = config_plugins_1.AndroidConfig.Version.withBuildScriptExtMinimumVersion(config, {
14
+ name: 'minSdkVersion',
15
+ minVersion: 24,
16
+ });
17
+ // Ensure INTERNET permission
18
+ return (0, config_plugins_1.withAndroidManifest)(config, (mod) => {
19
+ const manifest = mod.modResults.manifest;
20
+ if (!manifest['uses-permission']) {
21
+ manifest['uses-permission'] = [];
22
+ }
23
+ const hasInternet = manifest['uses-permission'].some((p) => p.$?.['android:name'] === 'android.permission.INTERNET');
24
+ if (!hasInternet) {
25
+ manifest['uses-permission'].push({
26
+ $: { 'android:name': 'android.permission.INTERNET' },
27
+ });
28
+ }
29
+ return mod;
30
+ });
31
+ };
32
+ exports.withShortKitAndroid = withShortKitAndroid;