@capgo/capacitor-realtimekit 7.0.6 → 7.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Martin Donadieu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/Package.swift CHANGED
@@ -11,7 +11,8 @@ let package = Package(
11
11
  ],
12
12
  dependencies: [
13
13
  .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "7.0.0"),
14
- .package(url: "https://github.com/dyte-in/RealtimeKitCoreiOS.git", from: "1.0.0")
14
+ .package(url: "https://github.com/dyte-in/RealtimeKitCoreiOS.git", from: "1.0.0"),
15
+ .package(url: "https://github.com/dyte-in/RealtimeKitUI.git", from: "0.5.3")
15
16
  ],
16
17
  targets: [
17
18
  .target(
@@ -19,7 +20,8 @@ let package = Package(
19
20
  dependencies: [
20
21
  .product(name: "Capacitor", package: "capacitor-swift-pm"),
21
22
  .product(name: "Cordova", package: "capacitor-swift-pm"),
22
- .product(name: "RealtimeKit", package: "RealtimeKitCoreiOS")
23
+ .product(name: "RealtimeKit", package: "RealtimeKitCoreiOS"),
24
+ .product(name: "RealtimeKitUI", package: "RealtimeKitUI")
23
25
  ],
24
26
  path: "ios/Sources/CapacitorRealtimekitPlugin"),
25
27
  .testTarget(
@@ -1,13 +1,20 @@
1
1
  package ee.forgr.plugin.capacitor_realtimekit;
2
2
 
3
3
  import android.Manifest;
4
+ import android.app.Activity;
4
5
  import android.util.Log;
6
+ import com.cloudflare.realtimekit.models.RtkMeetingInfo;
7
+ import com.cloudflare.realtimekit.ui.RealtimeKitUI;
8
+ import com.cloudflare.realtimekit.ui.RealtimeKitUIBuilder;
9
+ import com.cloudflare.realtimekit.ui.RealtimeKitUIInfo;
5
10
  import com.getcapacitor.JSObject;
11
+ import com.getcapacitor.PermissionState;
6
12
  import com.getcapacitor.Plugin;
7
13
  import com.getcapacitor.PluginCall;
8
14
  import com.getcapacitor.PluginMethod;
9
15
  import com.getcapacitor.annotation.CapacitorPlugin;
10
16
  import com.getcapacitor.annotation.Permission;
17
+ import com.getcapacitor.annotation.PermissionCallback;
11
18
 
12
19
  @CapacitorPlugin(
13
20
  name = "CapacitorRealtimekit",
@@ -19,8 +26,23 @@ import com.getcapacitor.annotation.Permission;
19
26
  public class CapacitorRealtimekitPlugin extends Plugin {
20
27
 
21
28
  private static final String TAG = "RealtimekitPlugin";
22
- private final String pluginVersion = "7.0.6";
29
+ private static final String REALTIMEKIT_BASE_DOMAIN = "realtime.cloudflare.com";
30
+ private final String pluginVersion = "7.1.2";
23
31
  private boolean isInitialized = false;
32
+ private PendingMeetingRequest pendingMeetingRequest;
33
+
34
+ private static final class PendingMeetingRequest {
35
+
36
+ private final String authToken;
37
+ private final boolean enableAudio;
38
+ private final boolean enableVideo;
39
+
40
+ private PendingMeetingRequest(String authToken, boolean enableAudio, boolean enableVideo) {
41
+ this.authToken = authToken;
42
+ this.enableAudio = enableAudio;
43
+ this.enableVideo = enableVideo;
44
+ }
45
+ }
24
46
 
25
47
  @Override
26
48
  public void load() {
@@ -30,8 +52,6 @@ public class CapacitorRealtimekitPlugin extends Plugin {
30
52
 
31
53
  @PluginMethod
32
54
  public void initialize(PluginCall call) {
33
- // Initialize the RealtimeKit SDK
34
- // TODO: Add Cloudflare RealtimeKit SDK initialization here
35
55
  isInitialized = true;
36
56
  Log.d(TAG, "RealtimeKit initialized");
37
57
  call.resolve();
@@ -53,20 +73,21 @@ public class CapacitorRealtimekitPlugin extends Plugin {
53
73
  Boolean enableAudio = call.getBoolean("enableAudio", true);
54
74
  Boolean enableVideo = call.getBoolean("enableVideo", true);
55
75
 
56
- // Start meeting with the built-in UI
57
- // TODO: Integrate with Cloudflare RealtimeKit SDK to launch meeting UI
58
- // In a real implementation, this would:
59
- // 1. Create and configure the RealtimeKit meeting activity/fragment
60
- // 2. Set the auth token, audio, and video settings
61
- // 3. Launch the meeting UI
76
+ String trimmedToken = authToken.trim();
77
+ if (trimmedToken.isEmpty()) {
78
+ call.reject("authToken is required");
79
+ return;
80
+ }
81
+
82
+ PendingMeetingRequest request = new PendingMeetingRequest(trimmedToken, enableAudio, enableVideo);
62
83
 
63
- Log.d(TAG, "Starting meeting with authToken: " + authToken);
64
- Log.d(TAG, "Audio enabled: " + enableAudio);
65
- Log.d(TAG, "Video enabled: " + enableVideo);
84
+ if (!hasMediaPermissions()) {
85
+ pendingMeetingRequest = request;
86
+ requestPermissionForAliases(new String[] { "camera", "microphone" }, call, "startMeetingPermissionsCallback");
87
+ return;
88
+ }
66
89
 
67
- // For now, just resolve successfully
68
- // Real implementation would launch the meeting UI here
69
- call.resolve();
90
+ launchMeeting(call, request);
70
91
  }
71
92
 
72
93
  @PluginMethod
@@ -79,4 +100,61 @@ public class CapacitorRealtimekitPlugin extends Plugin {
79
100
  call.reject("Could not get plugin version", e);
80
101
  }
81
102
  }
103
+
104
+ @PermissionCallback
105
+ private void startMeetingPermissionsCallback(PluginCall call) {
106
+ if (call == null) {
107
+ pendingMeetingRequest = null;
108
+ return;
109
+ }
110
+
111
+ if (!hasMediaPermissions()) {
112
+ pendingMeetingRequest = null;
113
+ call.reject("Camera and microphone permissions are required to start a meeting.");
114
+ return;
115
+ }
116
+
117
+ PendingMeetingRequest request = pendingMeetingRequest;
118
+ pendingMeetingRequest = null;
119
+
120
+ if (request == null) {
121
+ call.reject("Missing meeting configuration after granting permissions.");
122
+ return;
123
+ }
124
+
125
+ launchMeeting(call, request);
126
+ }
127
+
128
+ private boolean hasMediaPermissions() {
129
+ return getPermissionState("camera") == PermissionState.GRANTED && getPermissionState("microphone") == PermissionState.GRANTED;
130
+ }
131
+
132
+ private void launchMeeting(PluginCall call, PendingMeetingRequest request) {
133
+ pendingMeetingRequest = null;
134
+ Activity activity = getActivity();
135
+ if (activity == null) {
136
+ call.reject("Unable to access the current Activity to show the meeting UI.");
137
+ return;
138
+ }
139
+
140
+ activity.runOnUiThread(() -> {
141
+ try {
142
+ RtkMeetingInfo meetingInfo = new RtkMeetingInfo(
143
+ request.authToken,
144
+ request.enableAudio,
145
+ request.enableVideo,
146
+ REALTIMEKIT_BASE_DOMAIN
147
+ );
148
+ RealtimeKitUIInfo uiInfo = new RealtimeKitUIInfo(activity, meetingInfo);
149
+ RealtimeKitUI realtimeKitUI = RealtimeKitUIBuilder.build(uiInfo);
150
+ RealtimeKitUIBuilder.getMeeting().setUiKitInfo("capacitor-android", pluginVersion);
151
+ realtimeKitUI.startMeeting();
152
+ Log.d(TAG, "RealtimeKit meeting started successfully");
153
+ call.resolve();
154
+ } catch (Exception e) {
155
+ Log.e(TAG, "Failed to start RealtimeKit meeting", e);
156
+ call.reject("Failed to start meeting: " + e.getMessage(), e);
157
+ }
158
+ });
159
+ }
82
160
  }
@@ -1,12 +1,23 @@
1
+ import Capacitor
1
2
  import Foundation
3
+ import RealtimeKit
4
+ import RealtimeKitUI
2
5
  import UIKit
3
6
 
4
- public class CapacitorRealtimekit: NSObject {
7
+ public final class CapacitorRealtimekit: NSObject {
8
+ private weak var plugin: CAPPlugin?
9
+ private let pluginVersion: String
10
+ private let baseDomain = "realtime.cloudflare.com"
5
11
  private var isInitialized = false
12
+ private var realtimeKitUI: RealtimeKitUI?
13
+
14
+ init(plugin: CAPPlugin, pluginVersion: String) {
15
+ self.plugin = plugin
16
+ self.pluginVersion = pluginVersion
17
+ super.init()
18
+ }
6
19
 
7
20
  public func initialize() {
8
- // Initialize the RealtimeKit SDK
9
- // TODO: Add Cloudflare RealtimeKit SDK initialization here
10
21
  isInitialized = true
11
22
  print("RealtimeKit initialized")
12
23
  }
@@ -18,31 +29,65 @@ public class CapacitorRealtimekit: NSObject {
18
29
  completion: @escaping (Error?) -> Void
19
30
  ) {
20
31
  guard isInitialized else {
21
- let error = NSError(
22
- domain: "CapacitorRealtimekit",
23
- code: -1,
24
- userInfo: [NSLocalizedDescriptionKey: "RealtimeKit not initialized. Call initialize() first."]
25
- )
26
- completion(error)
32
+ completion(makeError("RealtimeKit not initialized. Call initialize() first."))
27
33
  return
28
34
  }
29
35
 
30
- // Start meeting with the built-in UI
31
- // TODO: Integrate with Cloudflare RealtimeKit SDK to launch meeting UI
32
- DispatchQueue.main.async {
33
- // Placeholder implementation
34
- // In a real implementation, this would:
35
- // 1. Create and configure the RealtimeKit meeting view controller
36
- // 2. Set the auth token, audio, and video settings
37
- // 3. Present the meeting UI modally
38
-
39
- print("Starting meeting with authToken: \(authToken)")
40
- print("Audio enabled: \(enableAudio)")
41
- print("Video enabled: \(enableVideo)")
42
-
43
- // For now, just complete successfully
44
- // Real implementation would present the meeting UI here
45
- completion(nil)
36
+ let trimmedToken = authToken.trimmingCharacters(in: .whitespacesAndNewlines)
37
+ guard !trimmedToken.isEmpty else {
38
+ completion(makeError("authToken is required"))
39
+ return
40
+ }
41
+
42
+ guard let presenter = topViewController(from: plugin?.bridge?.viewController) else {
43
+ completion(makeError("Unable to find a view controller to present the meeting UI."))
44
+ return
46
45
  }
46
+
47
+ DispatchQueue.main.async { [weak self] in
48
+ guard let self else { return }
49
+
50
+ let meetingInfo = RtkMeetingInfo(
51
+ authToken: trimmedToken,
52
+ enableAudio: enableAudio,
53
+ enableVideo: enableVideo,
54
+ baseDomain: self.baseDomain
55
+ )
56
+
57
+ let uiKit = RealtimeKitUI(meetingInfo: meetingInfo)
58
+ uiKit.rtkClient.setUiKitInfo(name: "capacitor-ios", version: self.pluginVersion)
59
+ self.realtimeKitUI = uiKit
60
+
61
+ let meetingController = uiKit.startMeeting { [weak self] in
62
+ self?.realtimeKitUI = nil
63
+ }
64
+ meetingController.modalPresentationStyle = .fullScreen
65
+
66
+ presenter.present(meetingController, animated: true) {
67
+ completion(nil)
68
+ }
69
+ }
70
+ }
71
+
72
+ private func topViewController(from root: UIViewController?) -> UIViewController? {
73
+ guard let root else { return nil }
74
+
75
+ if let presented = root.presentedViewController {
76
+ return topViewController(from: presented)
77
+ }
78
+
79
+ if let navigation = root as? UINavigationController {
80
+ return topViewController(from: navigation.visibleViewController ?? navigation)
81
+ }
82
+
83
+ if let tab = root as? UITabBarController {
84
+ return topViewController(from: tab.selectedViewController ?? tab)
85
+ }
86
+
87
+ return root
88
+ }
89
+
90
+ private func makeError(_ message: String) -> NSError {
91
+ NSError(domain: "CapacitorRealtimekit", code: -1, userInfo: [NSLocalizedDescriptionKey: message])
47
92
  }
48
93
  }
@@ -7,7 +7,7 @@ import Capacitor
7
7
  */
8
8
  @objc(CapacitorRealtimekitPlugin)
9
9
  public class CapacitorRealtimekitPlugin: CAPPlugin, CAPBridgedPlugin {
10
- private let pluginVersion: String = "7.0.6"
10
+ private let pluginVersion: String = "7.1.2"
11
11
  public let identifier = "CapacitorRealtimekitPlugin"
12
12
  public let jsName = "CapacitorRealtimekit"
13
13
  public let pluginMethods: [CAPPluginMethod] = [
@@ -16,7 +16,7 @@ public class CapacitorRealtimekitPlugin: CAPPlugin, CAPBridgedPlugin {
16
16
  CAPPluginMethod(name: "getPluginVersion", returnType: CAPPluginReturnPromise)
17
17
  ]
18
18
 
19
- private let implementation = CapacitorRealtimekit()
19
+ private lazy var implementation = CapacitorRealtimekit(plugin: self, pluginVersion: pluginVersion)
20
20
 
21
21
  @objc func initialize(_ call: CAPPluginCall) {
22
22
  implementation.initialize()
@@ -29,11 +29,17 @@ public class CapacitorRealtimekitPlugin: CAPPlugin, CAPBridgedPlugin {
29
29
  return
30
30
  }
31
31
 
32
+ let trimmedToken = authToken.trimmingCharacters(in: .whitespacesAndNewlines)
33
+ if trimmedToken.isEmpty {
34
+ call.reject("authToken is required")
35
+ return
36
+ }
37
+
32
38
  let enableAudio = call.getBool("enableAudio") ?? true
33
39
  let enableVideo = call.getBool("enableVideo") ?? true
34
40
 
35
41
  implementation.startMeeting(
36
- authToken: authToken,
42
+ authToken: trimmedToken,
37
43
  enableAudio: enableAudio,
38
44
  enableVideo: enableVideo
39
45
  ) { error in
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-realtimekit",
3
- "version": "7.0.6",
3
+ "version": "7.1.2",
4
4
  "description": "Cloudflare Calls integration for Capacitor apps with built-in UI for meetings.",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",