@capgo/capacitor-stream-call 7.1.23 → 7.1.25
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/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/StreamCallPlugin/CallOverlayView.swift +9 -9
- package/ios/Sources/StreamCallPlugin/StreamCallPlugin.swift +130 -130
- package/ios/Sources/StreamCallPlugin/TouchInterceptView.swift +24 -24
- package/ios/Sources/StreamCallPlugin/UserRepository.swift +1 -1
- package/package.json +1 -1
|
@@ -66,7 +66,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
66
66
|
|
|
67
67
|
// Declare as optional and initialize in load() method
|
|
68
68
|
private var callViewModel: CallViewModel?
|
|
69
|
-
|
|
69
|
+
|
|
70
70
|
// Constants for UserDefaults keys
|
|
71
71
|
private let dynamicApiKeyKey = "stream.video.dynamic.apikey"
|
|
72
72
|
|
|
@@ -90,11 +90,11 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
90
90
|
if let reason = reason {
|
|
91
91
|
data["reason"] = reason
|
|
92
92
|
}
|
|
93
|
-
|
|
93
|
+
|
|
94
94
|
if let caller = caller {
|
|
95
95
|
data["caller"] = caller
|
|
96
96
|
}
|
|
97
|
-
|
|
97
|
+
|
|
98
98
|
if let members = members {
|
|
99
99
|
data["members"] = members
|
|
100
100
|
}
|
|
@@ -105,7 +105,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
105
105
|
|
|
106
106
|
override public func load() {
|
|
107
107
|
print("StreamCallPlugin: load() called")
|
|
108
|
-
|
|
108
|
+
|
|
109
109
|
// Read API key from Info.plist
|
|
110
110
|
if let apiKey = Bundle.main.object(forInfoDictionaryKey: "CAPACITOR_STREAM_VIDEO_APIKEY") as? String {
|
|
111
111
|
self.apiKey = apiKey
|
|
@@ -114,7 +114,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
114
114
|
if self.apiKey == nil {
|
|
115
115
|
fatalError("Cannot get apikey")
|
|
116
116
|
}
|
|
117
|
-
|
|
117
|
+
|
|
118
118
|
// Check if we have a logged in user for handling incoming calls
|
|
119
119
|
if let credentials = SecureUserRepository.shared.loadCurrentUser() {
|
|
120
120
|
print("StreamCallPlugin: Found stored credentials during load() for user: \(credentials.user.name)")
|
|
@@ -187,11 +187,11 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
187
187
|
// Ensure this method is called on the main thread and properly establishes the subscription
|
|
188
188
|
DispatchQueue.main.async { [weak self] in
|
|
189
189
|
guard let self = self else { return }
|
|
190
|
-
|
|
190
|
+
|
|
191
191
|
// Cancel existing subscription if any
|
|
192
192
|
self.activeCallSubscription?.cancel()
|
|
193
193
|
self.activeCallSubscription = nil
|
|
194
|
-
|
|
194
|
+
|
|
195
195
|
// Verify callViewModel exists
|
|
196
196
|
guard let callViewModel = self.callViewModel, let streamVideo = self.streamVideo else {
|
|
197
197
|
print("Warning: setupActiveCallSubscription called but callViewModel or streamVideo is nil")
|
|
@@ -201,31 +201,31 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
201
201
|
}
|
|
202
202
|
return
|
|
203
203
|
}
|
|
204
|
-
|
|
204
|
+
|
|
205
205
|
print("Setting up active call subscription")
|
|
206
|
-
|
|
206
|
+
|
|
207
207
|
// Create a strong reference to callViewModel to ensure it's not deallocated
|
|
208
208
|
// while the subscription is active
|
|
209
209
|
let viewModel = callViewModel
|
|
210
|
-
|
|
210
|
+
|
|
211
211
|
// Subscribe to streamVideo.state.$activeCall to handle CallKit integration
|
|
212
212
|
let callPublisher = streamVideo.state.$activeCall
|
|
213
213
|
.receive(on: DispatchQueue.main)
|
|
214
214
|
.sink { [weak self, weak viewModel] activeCall in
|
|
215
215
|
guard let self = self, let viewModel = viewModel else { return }
|
|
216
|
-
|
|
216
|
+
|
|
217
217
|
print("Active call update from streamVideo: \(String(describing: activeCall?.cId))")
|
|
218
|
-
|
|
218
|
+
|
|
219
219
|
if let activeCall = activeCall {
|
|
220
220
|
// Sync callViewModel with activeCall from streamVideo state
|
|
221
221
|
// This ensures CallKit integration works properly
|
|
222
222
|
viewModel.setActiveCall(activeCall)
|
|
223
223
|
}
|
|
224
224
|
}
|
|
225
|
-
|
|
225
|
+
|
|
226
226
|
// Store the subscription for activeCall updates
|
|
227
227
|
self.activeCallSubscription = callPublisher
|
|
228
|
-
|
|
228
|
+
|
|
229
229
|
// Additionally, subscribe to callingState for other call state changes
|
|
230
230
|
let statePublisher = viewModel.$callingState
|
|
231
231
|
.receive(on: DispatchQueue.main)
|
|
@@ -234,21 +234,21 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
234
234
|
print("Warning: Call state update received but self or viewModel is nil")
|
|
235
235
|
return
|
|
236
236
|
}
|
|
237
|
-
|
|
237
|
+
|
|
238
238
|
do {
|
|
239
239
|
try self.requireInitialized()
|
|
240
240
|
print("Call State Update: \(newState)")
|
|
241
|
-
|
|
241
|
+
|
|
242
242
|
if newState == .inCall {
|
|
243
243
|
print("- In call state detected")
|
|
244
244
|
print("- All participants: \(String(describing: viewModel.participants))")
|
|
245
|
-
|
|
245
|
+
|
|
246
246
|
// Ensure views are set up first (important when accepting call from notification)
|
|
247
247
|
self.setupViews()
|
|
248
|
-
|
|
248
|
+
|
|
249
249
|
// Create/update overlay and make visible when there's an active call
|
|
250
250
|
self.createCallOverlayView()
|
|
251
|
-
|
|
251
|
+
|
|
252
252
|
// Notify that a call has started - but only if we haven't notified for this call yet
|
|
253
253
|
if let callId = viewModel.call?.cId, !self.hasNotifiedCallJoined || callId != self.currentCallId {
|
|
254
254
|
print("Notifying call joined: \(callId)")
|
|
@@ -258,15 +258,15 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
258
258
|
} else if case .incoming(let incomingCall) = newState {
|
|
259
259
|
// Extract caller information
|
|
260
260
|
Task {
|
|
261
|
-
var caller: [String: Any]?
|
|
262
|
-
var members: [[String: Any]]?
|
|
263
|
-
|
|
261
|
+
var caller: [String: Any]?
|
|
262
|
+
var members: [[String: Any]]?
|
|
263
|
+
|
|
264
264
|
do {
|
|
265
265
|
// Get the call from StreamVideo to access detailed information
|
|
266
266
|
if let streamVideo = self.streamVideo {
|
|
267
267
|
let call = streamVideo.call(callType: incomingCall.type, callId: incomingCall.id)
|
|
268
268
|
let callInfo = try await call.get()
|
|
269
|
-
|
|
269
|
+
|
|
270
270
|
// Extract caller information
|
|
271
271
|
let createdBy = callInfo.call.createdBy
|
|
272
272
|
var callerData: [String: Any] = [:]
|
|
@@ -275,7 +275,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
275
275
|
callerData["imageURL"] = createdBy.image
|
|
276
276
|
callerData["role"] = createdBy.role
|
|
277
277
|
caller = callerData
|
|
278
|
-
|
|
278
|
+
|
|
279
279
|
// Extract members information from current participants if available
|
|
280
280
|
var membersArray: [[String: Any]] = []
|
|
281
281
|
if let activeCall = streamVideo.state.activeCall {
|
|
@@ -294,25 +294,25 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
294
294
|
} catch {
|
|
295
295
|
print("Failed to get call info for caller details: \(error)")
|
|
296
296
|
}
|
|
297
|
-
|
|
298
|
-
// Notify with caller information
|
|
297
|
+
|
|
298
|
+
// Notify with caller information
|
|
299
299
|
let fullCallId = "\(incomingCall.type):\(incomingCall.id)"
|
|
300
300
|
self.updateCallStatusAndNotify(callId: fullCallId, state: "ringing", caller: caller, members: members)
|
|
301
301
|
}
|
|
302
302
|
} else if newState == .idle {
|
|
303
303
|
print("Call state changed to idle. CurrentCallId: \(self.currentCallId), ActiveCall: \(String(describing: self.streamVideo?.state.activeCall?.cId))")
|
|
304
|
-
|
|
304
|
+
|
|
305
305
|
// Only notify about call ending if we have a valid stored call ID and there's truly no active call
|
|
306
306
|
// This prevents false "left" events during normal state transitions
|
|
307
307
|
if !self.currentCallId.isEmpty && self.streamVideo?.state.activeCall == nil {
|
|
308
308
|
print("Call actually ending: \(self.currentCallId)")
|
|
309
|
-
|
|
309
|
+
|
|
310
310
|
// Notify that call has ended - use the stored call ID
|
|
311
311
|
self.updateCallStatusAndNotify(callId: self.currentCallId, state: "left")
|
|
312
|
-
|
|
312
|
+
|
|
313
313
|
// Reset notification flag when call ends
|
|
314
314
|
self.hasNotifiedCallJoined = false
|
|
315
|
-
|
|
315
|
+
|
|
316
316
|
// Remove the call overlay view when not in a call
|
|
317
317
|
self.ensureViewRemoved()
|
|
318
318
|
} else {
|
|
@@ -323,26 +323,26 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
323
323
|
log.error("Error handling call state update: \(String(describing: error))")
|
|
324
324
|
}
|
|
325
325
|
}
|
|
326
|
-
|
|
326
|
+
|
|
327
327
|
// Combine both publishers
|
|
328
328
|
self.activeCallSubscription = AnyCancellable {
|
|
329
329
|
callPublisher.cancel()
|
|
330
330
|
statePublisher.cancel()
|
|
331
331
|
}
|
|
332
|
-
|
|
332
|
+
|
|
333
333
|
print("Active call subscription setup completed")
|
|
334
|
-
|
|
334
|
+
|
|
335
335
|
// Schedule a periodic check to ensure subscription is active
|
|
336
336
|
self.scheduleSubscriptionCheck()
|
|
337
337
|
}
|
|
338
338
|
}
|
|
339
|
-
|
|
339
|
+
|
|
340
340
|
// Add a new method to periodically check and restore the subscription if needed
|
|
341
341
|
private func scheduleSubscriptionCheck() {
|
|
342
342
|
// Create a timer that checks the subscription every 5 seconds
|
|
343
343
|
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) { [weak self] in
|
|
344
344
|
guard let self = self else { return }
|
|
345
|
-
|
|
345
|
+
|
|
346
346
|
// Check if we're in a state where we need the subscription but it's not active
|
|
347
347
|
if self.state == .initialized && self.activeCallSubscription == nil && self.callViewModel != nil {
|
|
348
348
|
print("Subscription check: Restoring lost activeCallSubscription")
|
|
@@ -374,7 +374,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
374
374
|
if let pushConfig = call.getObject("pushNotificationsConfig") {
|
|
375
375
|
let pushProviderName = pushConfig["pushProviderName"] as? String ?? "ios-apn"
|
|
376
376
|
let voipProviderName = pushConfig["voipProviderName"] as? String ?? "ios-voip"
|
|
377
|
-
|
|
377
|
+
|
|
378
378
|
self.pushNotificationsConfig = PushNotificationsConfig(
|
|
379
379
|
pushProviderInfo: PushProviderInfo(name: pushProviderName, pushProvider: .apn),
|
|
380
380
|
voipPushProviderInfo: PushProviderInfo(name: voipProviderName, pushProvider: .apn)
|
|
@@ -431,7 +431,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
431
431
|
// self.overlayViewModel?.updateStreamVideo(nil)
|
|
432
432
|
self.overlayView?.isHidden = true
|
|
433
433
|
self.webView?.isOpaque = true
|
|
434
|
-
|
|
434
|
+
|
|
435
435
|
// Remove touch interceptor if it exists
|
|
436
436
|
self.removeTouchInterceptor()
|
|
437
437
|
}
|
|
@@ -524,7 +524,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
524
524
|
case let date as Date:
|
|
525
525
|
let formatter = ISO8601DateFormatter()
|
|
526
526
|
return .string(formatter.string(from: date))
|
|
527
|
-
case let array as
|
|
527
|
+
case let array as [JSValue]:
|
|
528
528
|
let mappedArray = array.compactMap { item -> RawJSON? in
|
|
529
529
|
// Recursive conversion for array elements
|
|
530
530
|
switch item {
|
|
@@ -538,7 +538,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
538
538
|
}
|
|
539
539
|
}
|
|
540
540
|
return .array(mappedArray)
|
|
541
|
-
case let dict as
|
|
541
|
+
case let dict as [String: JSValue]:
|
|
542
542
|
let mappedDict = dict.compactMapValues { value -> RawJSON? in
|
|
543
543
|
// Recursive conversion for dictionary values
|
|
544
544
|
switch value {
|
|
@@ -558,15 +558,15 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
558
558
|
},
|
|
559
559
|
video: video
|
|
560
560
|
)
|
|
561
|
-
|
|
561
|
+
|
|
562
562
|
// Wait for call state to be populated by WebSocket events
|
|
563
563
|
let callStream = streamVideo!.call(callType: callType, callId: callId)
|
|
564
|
-
|
|
564
|
+
|
|
565
565
|
// Wait until we have member data - with timeout to prevent infinite loop
|
|
566
566
|
var allMembers: [[String: Any]] = []
|
|
567
567
|
var attempts = 0
|
|
568
568
|
let maxAttempts = 50 // 5 seconds max
|
|
569
|
-
|
|
569
|
+
|
|
570
570
|
while allMembers.isEmpty && attempts < maxAttempts {
|
|
571
571
|
let membersList = await callStream.state.members
|
|
572
572
|
if !membersList.isEmpty {
|
|
@@ -584,7 +584,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
584
584
|
try? await Task.sleep(nanoseconds: 100_000_000) // 0.1 seconds
|
|
585
585
|
}
|
|
586
586
|
}
|
|
587
|
-
|
|
587
|
+
|
|
588
588
|
// If we still don't have members after timeout, use basic data
|
|
589
589
|
if allMembers.isEmpty {
|
|
590
590
|
allMembers = members.map { userId in
|
|
@@ -596,16 +596,16 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
596
596
|
]
|
|
597
597
|
}
|
|
598
598
|
}
|
|
599
|
-
|
|
599
|
+
|
|
600
600
|
// Now send the created event with complete member data
|
|
601
601
|
let fullCallId = "\(callType):\(callId)"
|
|
602
602
|
self.updateCallStatusAndNotify(callId: fullCallId, state: "created", members: allMembers)
|
|
603
|
-
|
|
603
|
+
|
|
604
604
|
// Update UI on main thread
|
|
605
605
|
await MainActor.run {
|
|
606
606
|
// Add touch interceptor for the call
|
|
607
607
|
self.addTouchInterceptor()
|
|
608
|
-
|
|
608
|
+
|
|
609
609
|
// self.overlayViewModel?.updateCall(streamCall)
|
|
610
610
|
self.overlayView?.isHidden = false
|
|
611
611
|
self.webView?.isOpaque = false
|
|
@@ -633,7 +633,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
633
633
|
// Check both active call and callViewModel's call state to handle outgoing calls
|
|
634
634
|
let activeCall = streamVideo?.state.activeCall
|
|
635
635
|
let viewModelCall = await callViewModel?.call
|
|
636
|
-
|
|
636
|
+
|
|
637
637
|
// Helper function to determine if we should end or leave the call
|
|
638
638
|
func shouldEndCall(for streamCall: Call) async throws -> Bool {
|
|
639
639
|
do {
|
|
@@ -641,25 +641,25 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
641
641
|
let currentUserId = streamVideo?.user.id
|
|
642
642
|
let createdBy = callInfo.call.createdBy.id
|
|
643
643
|
let isCreator = createdBy == currentUserId
|
|
644
|
-
|
|
644
|
+
|
|
645
645
|
// Use call.state.participants.count to get participant count (as per StreamVideo iOS SDK docs)
|
|
646
646
|
let totalParticipants = await streamCall.state.participants.count
|
|
647
647
|
let shouldEnd = isCreator || totalParticipants <= 2
|
|
648
|
-
|
|
648
|
+
|
|
649
649
|
print("Call \(streamCall.cId) - Creator: \(createdBy), CurrentUser: \(currentUserId ?? "nil"), IsCreator: \(isCreator), TotalParticipants: \(totalParticipants), ShouldEnd: \(shouldEnd)")
|
|
650
|
-
|
|
650
|
+
|
|
651
651
|
return shouldEnd
|
|
652
652
|
} catch {
|
|
653
653
|
print("Error getting call info for \(streamCall.cId), defaulting to leave: \(error)")
|
|
654
654
|
return false // Fallback to leave if we can't determine
|
|
655
655
|
}
|
|
656
656
|
}
|
|
657
|
-
|
|
657
|
+
|
|
658
658
|
if let activeCall = activeCall {
|
|
659
659
|
// There's an active call, check if we should end or leave
|
|
660
660
|
do {
|
|
661
661
|
let shouldEnd = try await shouldEndCall(for: activeCall)
|
|
662
|
-
|
|
662
|
+
|
|
663
663
|
if shouldEnd {
|
|
664
664
|
print("Ending active call \(activeCall.cId) for all participants")
|
|
665
665
|
try await activeCall.end()
|
|
@@ -671,15 +671,15 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
671
671
|
print("Error ending/leaving active call: \(error)")
|
|
672
672
|
try await activeCall.leave() // Fallback to leave
|
|
673
673
|
}
|
|
674
|
-
|
|
674
|
+
|
|
675
675
|
await MainActor.run {
|
|
676
676
|
self.overlayView?.isHidden = true
|
|
677
677
|
self.webView?.isOpaque = true
|
|
678
|
-
|
|
678
|
+
|
|
679
679
|
// Remove touch interceptor
|
|
680
680
|
self.removeTouchInterceptor()
|
|
681
681
|
}
|
|
682
|
-
|
|
682
|
+
|
|
683
683
|
call.resolve([
|
|
684
684
|
"success": true
|
|
685
685
|
])
|
|
@@ -687,7 +687,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
687
687
|
// There's a call in the viewModel (likely outgoing/ringing), check if we should end or leave
|
|
688
688
|
do {
|
|
689
689
|
let shouldEnd = try await shouldEndCall(for: viewModelCall)
|
|
690
|
-
|
|
690
|
+
|
|
691
691
|
if shouldEnd {
|
|
692
692
|
print("Ending viewModel call \(viewModelCall.cId) for all participants")
|
|
693
693
|
try await viewModelCall.end()
|
|
@@ -699,18 +699,18 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
699
699
|
print("Error ending/leaving viewModel call: \(error)")
|
|
700
700
|
try await viewModelCall.leave() // Fallback to leave
|
|
701
701
|
}
|
|
702
|
-
|
|
702
|
+
|
|
703
703
|
// Also hang up to reset the calling state
|
|
704
704
|
await callViewModel?.hangUp()
|
|
705
|
-
|
|
705
|
+
|
|
706
706
|
await MainActor.run {
|
|
707
707
|
self.overlayView?.isHidden = true
|
|
708
708
|
self.webView?.isOpaque = true
|
|
709
|
-
|
|
709
|
+
|
|
710
710
|
// Remove touch interceptor
|
|
711
711
|
self.removeTouchInterceptor()
|
|
712
712
|
}
|
|
713
|
-
|
|
713
|
+
|
|
714
714
|
call.resolve([
|
|
715
715
|
"success": true
|
|
716
716
|
])
|
|
@@ -816,17 +816,17 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
816
816
|
// Update the CallOverlayView with the active call
|
|
817
817
|
await MainActor.run {
|
|
818
818
|
print("acceptCall: Setting up UI for accepted call")
|
|
819
|
-
|
|
819
|
+
|
|
820
820
|
// Ensure views are set up first
|
|
821
821
|
self.setupViews()
|
|
822
|
-
|
|
822
|
+
|
|
823
823
|
// Add touch interceptor for the call
|
|
824
824
|
self.addTouchInterceptor()
|
|
825
|
-
|
|
825
|
+
|
|
826
826
|
// self.overlayViewModel?.updateCall(streamCall)
|
|
827
827
|
self.overlayView?.isHidden = false
|
|
828
828
|
self.webView?.isOpaque = false
|
|
829
|
-
|
|
829
|
+
|
|
830
830
|
print("acceptCall: UI setup complete - overlay visible: \(!self.overlayView!.isHidden), touch interceptor: \(self.touchInterceptView != nil)")
|
|
831
831
|
}
|
|
832
832
|
|
|
@@ -844,18 +844,18 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
844
844
|
}
|
|
845
845
|
|
|
846
846
|
private func initializeStreamVideo() {
|
|
847
|
-
if
|
|
847
|
+
if state == .initialized {
|
|
848
848
|
print("initializeStreamVideo already initialized")
|
|
849
849
|
// Try to get user credentials from repository
|
|
850
850
|
guard let savedCredentials = SecureUserRepository.shared.loadCurrentUser() else {
|
|
851
851
|
print("Save credentials not found, skipping initialization")
|
|
852
852
|
return
|
|
853
853
|
}
|
|
854
|
-
if
|
|
854
|
+
if savedCredentials.user.id == streamVideo?.user.id {
|
|
855
855
|
print("Skipping initializeStreamVideo as user is already logged in")
|
|
856
856
|
return
|
|
857
857
|
}
|
|
858
|
-
} else if
|
|
858
|
+
} else if state == .initializing {
|
|
859
859
|
print("initializeStreamVideo rejected - already initializing")
|
|
860
860
|
return
|
|
861
861
|
}
|
|
@@ -881,7 +881,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
881
881
|
print("StreamCallPlugin: Token provider called for token refresh")
|
|
882
882
|
guard let savedCredentials = SecureUserRepository.shared.loadCurrentUser() else {
|
|
883
883
|
print("StreamCallPlugin: Token provider - No saved credentials found, failing token refresh")
|
|
884
|
-
|
|
884
|
+
|
|
885
885
|
completion(.failure(NSError(domain: "No saved credentials or API key found, cannot refresh token", code: 0, userInfo: nil)))
|
|
886
886
|
return
|
|
887
887
|
}
|
|
@@ -889,14 +889,14 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
889
889
|
completion(.success(UserToken(stringLiteral: savedCredentials.tokenValue)))
|
|
890
890
|
}
|
|
891
891
|
)
|
|
892
|
-
|
|
893
|
-
if
|
|
892
|
+
|
|
893
|
+
if self.callViewModel == nil {
|
|
894
894
|
// Initialize on main thread with proper MainActor isolation
|
|
895
895
|
DispatchQueue.main.async {
|
|
896
896
|
Task { @MainActor in
|
|
897
897
|
self.callViewModel = CallViewModel(participantsLayout: .grid)
|
|
898
898
|
// self.callViewModel?.participantAutoLeavePolicy = LastParticipantAutoLeavePolicy()
|
|
899
|
-
|
|
899
|
+
|
|
900
900
|
// Setup subscriptions for new StreamVideo instance
|
|
901
901
|
self.setupActiveCallSubscription()
|
|
902
902
|
}
|
|
@@ -906,7 +906,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
906
906
|
state = .initialized
|
|
907
907
|
callKitAdapter.streamVideo = self.streamVideo
|
|
908
908
|
callKitAdapter.availabilityPolicy = .always
|
|
909
|
-
|
|
909
|
+
|
|
910
910
|
setupTokenSubscription()
|
|
911
911
|
|
|
912
912
|
// Register for incoming calls
|
|
@@ -915,21 +915,21 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
915
915
|
|
|
916
916
|
private func setupViews() {
|
|
917
917
|
guard let webView = self.webView, let parent = webView.superview else { return }
|
|
918
|
-
|
|
918
|
+
|
|
919
919
|
// Create SwiftUI view with view model if not already created
|
|
920
920
|
if self.overlayView == nil, let callViewModel = self.callViewModel {
|
|
921
921
|
let hostingController = UIHostingController(rootView: CallOverlayView(viewModel: callViewModel))
|
|
922
922
|
hostingController.view.backgroundColor = .clear
|
|
923
923
|
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
|
|
924
924
|
hostingController.view.isHidden = true // Initially hidden until a call is active
|
|
925
|
-
|
|
925
|
+
|
|
926
926
|
self.hostingController = hostingController
|
|
927
927
|
self.overlayView = hostingController.view
|
|
928
|
-
|
|
928
|
+
|
|
929
929
|
if let overlayView = self.overlayView {
|
|
930
930
|
// Insert overlay view below webview
|
|
931
931
|
parent.insertSubview(overlayView, belowSubview: webView)
|
|
932
|
-
|
|
932
|
+
|
|
933
933
|
// Setup constraints for overlayView
|
|
934
934
|
let safeGuide = parent.safeAreaLayoutGuide
|
|
935
935
|
NSLayoutConstraint.activate([
|
|
@@ -940,7 +940,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
940
940
|
])
|
|
941
941
|
}
|
|
942
942
|
}
|
|
943
|
-
|
|
943
|
+
|
|
944
944
|
// Check if we have an active call and need to add touch interceptor
|
|
945
945
|
if let activeCall = self.streamVideo?.state.activeCall {
|
|
946
946
|
print("Active call detected during setupViews, ensuring touch interceptor is added")
|
|
@@ -959,26 +959,26 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
959
959
|
}
|
|
960
960
|
}
|
|
961
961
|
}
|
|
962
|
-
|
|
962
|
+
|
|
963
963
|
private func addTouchInterceptor() {
|
|
964
964
|
guard let webView = self.webView, let parent = webView.superview else {
|
|
965
965
|
print("Cannot add touch interceptor - webView or parent not ready, marking for deferred setup")
|
|
966
966
|
self.needsTouchInterceptorSetup = true
|
|
967
967
|
return
|
|
968
968
|
}
|
|
969
|
-
|
|
969
|
+
|
|
970
970
|
// Check if touch interceptor already exists
|
|
971
971
|
if self.touchInterceptView != nil {
|
|
972
972
|
print("Touch interceptor already exists, skipping creation")
|
|
973
973
|
return
|
|
974
974
|
}
|
|
975
|
-
|
|
975
|
+
|
|
976
976
|
// Create the touch intercept view as an overlay for touch passthrough
|
|
977
977
|
let touchInterceptView = TouchInterceptView(frame: parent.bounds)
|
|
978
978
|
touchInterceptView.translatesAutoresizingMaskIntoConstraints = false
|
|
979
979
|
touchInterceptView.backgroundColor = .clear
|
|
980
980
|
touchInterceptView.isOpaque = false
|
|
981
|
-
|
|
981
|
+
|
|
982
982
|
// Setup touch intercept view with references to webview and overlay
|
|
983
983
|
if let overlayView = self.overlayView {
|
|
984
984
|
touchInterceptView.setupWithWebView(webView, overlayView: overlayView)
|
|
@@ -991,15 +991,15 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
991
991
|
parent.addSubview(touchInterceptView)
|
|
992
992
|
parent.bringSubviewToFront(touchInterceptView)
|
|
993
993
|
}
|
|
994
|
-
|
|
994
|
+
|
|
995
995
|
// Set up active call check function
|
|
996
996
|
touchInterceptView.setActiveCallCheck { [weak self] in
|
|
997
997
|
return self?.streamVideo?.state.activeCall != nil
|
|
998
998
|
}
|
|
999
|
-
|
|
999
|
+
|
|
1000
1000
|
// Store reference to touch intercept view
|
|
1001
1001
|
self.touchInterceptView = touchInterceptView
|
|
1002
|
-
|
|
1002
|
+
|
|
1003
1003
|
// Setup constraints for touchInterceptView to cover the entire parent
|
|
1004
1004
|
NSLayoutConstraint.activate([
|
|
1005
1005
|
touchInterceptView.topAnchor.constraint(equalTo: parent.topAnchor),
|
|
@@ -1007,26 +1007,26 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1007
1007
|
touchInterceptView.leadingAnchor.constraint(equalTo: parent.leadingAnchor),
|
|
1008
1008
|
touchInterceptView.trailingAnchor.constraint(equalTo: parent.trailingAnchor)
|
|
1009
1009
|
])
|
|
1010
|
-
|
|
1010
|
+
|
|
1011
1011
|
print("Touch interceptor added for active call - view hierarchy: \(parent.subviews.map { type(of: $0) })")
|
|
1012
1012
|
}
|
|
1013
|
-
|
|
1013
|
+
|
|
1014
1014
|
private func removeTouchInterceptor() {
|
|
1015
1015
|
guard let touchInterceptView = self.touchInterceptView else { return }
|
|
1016
|
-
|
|
1016
|
+
|
|
1017
1017
|
// Remove touch interceptor from view hierarchy
|
|
1018
1018
|
touchInterceptView.removeFromSuperview()
|
|
1019
1019
|
self.touchInterceptView = nil
|
|
1020
1020
|
self.needsTouchInterceptorSetup = false
|
|
1021
|
-
|
|
1021
|
+
|
|
1022
1022
|
print("Touch interceptor removed after call ended")
|
|
1023
1023
|
}
|
|
1024
|
-
|
|
1024
|
+
|
|
1025
1025
|
private func createCallOverlayView() {
|
|
1026
1026
|
guard let webView = self.webView,
|
|
1027
1027
|
let parent = webView.superview,
|
|
1028
1028
|
let callOverlayView = self.callViewModel else { return }
|
|
1029
|
-
|
|
1029
|
+
|
|
1030
1030
|
// Check if we already have an overlay view - do nothing if it exists
|
|
1031
1031
|
if let existingOverlayView = self.overlayView, existingOverlayView.superview != nil {
|
|
1032
1032
|
print("Call overlay view already exists, making it visible")
|
|
@@ -1037,50 +1037,50 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1037
1037
|
webView.scrollView.backgroundColor = .clear
|
|
1038
1038
|
return
|
|
1039
1039
|
}
|
|
1040
|
-
|
|
1040
|
+
|
|
1041
1041
|
print("Creating new call overlay view")
|
|
1042
|
-
|
|
1042
|
+
|
|
1043
1043
|
// First, create the overlay view
|
|
1044
1044
|
let overlayView = UIHostingController(rootView: CallOverlayView(viewModel: callOverlayView))
|
|
1045
1045
|
overlayView.view.translatesAutoresizingMaskIntoConstraints = false
|
|
1046
1046
|
overlayView.view.isHidden = false // Make visible during a call
|
|
1047
|
-
|
|
1047
|
+
|
|
1048
1048
|
// Insert the overlay view below the webView in the view hierarchy
|
|
1049
1049
|
parent.insertSubview(overlayView.view, belowSubview: webView)
|
|
1050
|
-
|
|
1050
|
+
|
|
1051
1051
|
// Set constraints to fill the parent's safe area
|
|
1052
1052
|
let safeGuide = parent.safeAreaLayoutGuide
|
|
1053
|
-
|
|
1053
|
+
|
|
1054
1054
|
NSLayoutConstraint.activate([
|
|
1055
1055
|
overlayView.view.topAnchor.constraint(equalTo: safeGuide.topAnchor),
|
|
1056
1056
|
overlayView.view.bottomAnchor.constraint(equalTo: safeGuide.bottomAnchor),
|
|
1057
1057
|
overlayView.view.leadingAnchor.constraint(equalTo: safeGuide.leadingAnchor),
|
|
1058
1058
|
overlayView.view.trailingAnchor.constraint(equalTo: safeGuide.trailingAnchor)
|
|
1059
1059
|
])
|
|
1060
|
-
|
|
1060
|
+
|
|
1061
1061
|
// Make webview transparent to see StreamCall UI underneath
|
|
1062
1062
|
webView.isOpaque = false
|
|
1063
1063
|
webView.backgroundColor = .clear
|
|
1064
1064
|
webView.scrollView.backgroundColor = .clear
|
|
1065
|
-
|
|
1065
|
+
|
|
1066
1066
|
// Store reference to the hosting controller
|
|
1067
1067
|
self.hostingController = overlayView
|
|
1068
1068
|
self.overlayView = overlayView.view
|
|
1069
|
-
|
|
1069
|
+
|
|
1070
1070
|
// Add touch interceptor if not already present
|
|
1071
1071
|
self.addTouchInterceptor()
|
|
1072
1072
|
}
|
|
1073
|
-
|
|
1073
|
+
|
|
1074
1074
|
// MARK: - Dynamic API Key Management
|
|
1075
|
-
|
|
1075
|
+
|
|
1076
1076
|
func saveDynamicApiKey(_ apiKey: String) {
|
|
1077
1077
|
UserDefaults.standard.set(apiKey, forKey: dynamicApiKeyKey)
|
|
1078
1078
|
}
|
|
1079
|
-
|
|
1079
|
+
|
|
1080
1080
|
func getDynamicApiKey() -> String? {
|
|
1081
1081
|
return UserDefaults.standard.string(forKey: dynamicApiKeyKey)
|
|
1082
1082
|
}
|
|
1083
|
-
|
|
1083
|
+
|
|
1084
1084
|
func getEffectiveApiKey() -> String? {
|
|
1085
1085
|
// A) Check if the key exists in UserDefaults
|
|
1086
1086
|
if let dynamicApiKey = getDynamicApiKey(), !dynamicApiKey.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).isEmpty {
|
|
@@ -1092,15 +1092,15 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1092
1092
|
return self.apiKey
|
|
1093
1093
|
}
|
|
1094
1094
|
}
|
|
1095
|
-
|
|
1095
|
+
|
|
1096
1096
|
func ensureViewRemoved() {
|
|
1097
1097
|
// Check if we have an overlay view
|
|
1098
1098
|
if let existingOverlayView = self.overlayView {
|
|
1099
1099
|
print("Hiding call overlay view")
|
|
1100
|
-
|
|
1100
|
+
|
|
1101
1101
|
// Hide the view instead of removing it
|
|
1102
1102
|
existingOverlayView.isHidden = true
|
|
1103
|
-
|
|
1103
|
+
|
|
1104
1104
|
// Reset opacity for webView
|
|
1105
1105
|
self.webView?.isOpaque = true
|
|
1106
1106
|
self.webView?.backgroundColor = nil
|
|
@@ -1108,7 +1108,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1108
1108
|
} else {
|
|
1109
1109
|
print("No call overlay view to hide")
|
|
1110
1110
|
}
|
|
1111
|
-
|
|
1111
|
+
|
|
1112
1112
|
// Remove touch interceptor
|
|
1113
1113
|
self.removeTouchInterceptor()
|
|
1114
1114
|
}
|
|
@@ -1125,28 +1125,28 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1125
1125
|
"state": currentCallState
|
|
1126
1126
|
])
|
|
1127
1127
|
}
|
|
1128
|
-
|
|
1128
|
+
|
|
1129
1129
|
@objc func getCallInfo(_ call: CAPPluginCall) {
|
|
1130
1130
|
guard let callId = call.getString("callId") else {
|
|
1131
1131
|
call.reject("Missing required parameter: callId")
|
|
1132
1132
|
return
|
|
1133
1133
|
}
|
|
1134
|
-
|
|
1134
|
+
|
|
1135
1135
|
do {
|
|
1136
1136
|
try requireInitialized()
|
|
1137
|
-
|
|
1137
|
+
|
|
1138
1138
|
guard let activeCall = streamVideo?.state.activeCall, activeCall.cId == callId else {
|
|
1139
1139
|
call.reject("Call ID does not match active call")
|
|
1140
1140
|
return
|
|
1141
1141
|
}
|
|
1142
|
-
|
|
1142
|
+
|
|
1143
1143
|
Task {
|
|
1144
1144
|
do {
|
|
1145
1145
|
// Get detailed call information
|
|
1146
1146
|
let callInfo = try await activeCall.get()
|
|
1147
|
-
|
|
1147
|
+
|
|
1148
1148
|
// Extract caller information
|
|
1149
|
-
var caller: [String: Any]?
|
|
1149
|
+
var caller: [String: Any]?
|
|
1150
1150
|
let createdBy = callInfo.call.createdBy
|
|
1151
1151
|
var callerData: [String: Any] = [:]
|
|
1152
1152
|
callerData["userId"] = createdBy.id
|
|
@@ -1154,7 +1154,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1154
1154
|
callerData["imageURL"] = createdBy.image
|
|
1155
1155
|
callerData["role"] = createdBy.role
|
|
1156
1156
|
caller = callerData
|
|
1157
|
-
|
|
1157
|
+
|
|
1158
1158
|
// Extract members information
|
|
1159
1159
|
var membersArray: [[String: Any]] = []
|
|
1160
1160
|
let participants = await activeCall.state.participants
|
|
@@ -1167,7 +1167,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1167
1167
|
membersArray.append(memberData)
|
|
1168
1168
|
}
|
|
1169
1169
|
let members = membersArray
|
|
1170
|
-
|
|
1170
|
+
|
|
1171
1171
|
// Determine call state based on current calling state
|
|
1172
1172
|
let state: String
|
|
1173
1173
|
let callingState = await self.callViewModel?.callingState
|
|
@@ -1189,17 +1189,17 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1189
1189
|
case .none:
|
|
1190
1190
|
state = "unknown"
|
|
1191
1191
|
}
|
|
1192
|
-
|
|
1192
|
+
|
|
1193
1193
|
var result: [String: Any] = [:]
|
|
1194
1194
|
result["callId"] = callId
|
|
1195
1195
|
result["state"] = state
|
|
1196
|
-
|
|
1196
|
+
|
|
1197
1197
|
if let caller = caller {
|
|
1198
1198
|
result["caller"] = caller
|
|
1199
1199
|
}
|
|
1200
|
-
|
|
1200
|
+
|
|
1201
1201
|
result["members"] = members
|
|
1202
|
-
|
|
1202
|
+
|
|
1203
1203
|
call.resolve(result)
|
|
1204
1204
|
} catch {
|
|
1205
1205
|
call.reject("Failed to get call info: \(error.localizedDescription)")
|
|
@@ -1240,7 +1240,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1240
1240
|
call.reject("StreamVideo not initialized")
|
|
1241
1241
|
}
|
|
1242
1242
|
}
|
|
1243
|
-
|
|
1243
|
+
|
|
1244
1244
|
@objc func switchCamera(_ call: CAPPluginCall) {
|
|
1245
1245
|
guard let camera = call.getString("camera") else {
|
|
1246
1246
|
call.reject("Missing required parameter: camera")
|
|
@@ -1254,7 +1254,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1254
1254
|
do {
|
|
1255
1255
|
if let activeCall = streamVideo?.state.activeCall {
|
|
1256
1256
|
if (camera == "front" && activeCall.camera.direction != .front) ||
|
|
1257
|
-
|
|
1257
|
+
(camera == "back" && activeCall.camera.direction != .back) {
|
|
1258
1258
|
try await activeCall.camera.flip()
|
|
1259
1259
|
}
|
|
1260
1260
|
}
|
|
@@ -1270,13 +1270,13 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1270
1270
|
call.reject("StreamVideo not initialized")
|
|
1271
1271
|
}
|
|
1272
1272
|
}
|
|
1273
|
-
|
|
1273
|
+
|
|
1274
1274
|
@objc func setDynamicStreamVideoApikey(_ call: CAPPluginCall) {
|
|
1275
1275
|
guard let apiKey = call.getString("apiKey") else {
|
|
1276
1276
|
call.reject("Missing required parameter: apiKey")
|
|
1277
1277
|
return
|
|
1278
1278
|
}
|
|
1279
|
-
|
|
1279
|
+
|
|
1280
1280
|
do {
|
|
1281
1281
|
saveDynamicApiKey(apiKey)
|
|
1282
1282
|
print("Dynamic API key saved successfully")
|
|
@@ -1288,7 +1288,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1288
1288
|
call.reject("Failed to save API key: \(error.localizedDescription)")
|
|
1289
1289
|
}
|
|
1290
1290
|
}
|
|
1291
|
-
|
|
1291
|
+
|
|
1292
1292
|
@objc func getDynamicStreamVideoApikey(_ call: CAPPluginCall) {
|
|
1293
1293
|
do {
|
|
1294
1294
|
let apiKey = getDynamicApiKey()
|
|
@@ -1301,22 +1301,22 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1301
1301
|
call.reject("Failed to get API key: \(error.localizedDescription)")
|
|
1302
1302
|
}
|
|
1303
1303
|
}
|
|
1304
|
-
|
|
1304
|
+
|
|
1305
1305
|
@objc func getCurrentUser(_ call: CAPPluginCall) {
|
|
1306
1306
|
print("StreamCallPlugin: getCurrentUser called")
|
|
1307
1307
|
print("StreamCallPlugin: getCurrentUser: Current state: \(state), StreamVideo initialized: \(streamVideo != nil)")
|
|
1308
|
-
|
|
1308
|
+
|
|
1309
1309
|
do {
|
|
1310
1310
|
if let savedCredentials = SecureUserRepository.shared.loadCurrentUser() {
|
|
1311
1311
|
print("StreamCallPlugin: getCurrentUser: Found saved credentials for user: \(savedCredentials.user.id)")
|
|
1312
|
-
|
|
1312
|
+
|
|
1313
1313
|
// Check if StreamVideo session matches the stored credentials
|
|
1314
1314
|
let isStreamVideoActive = streamVideo != nil && state == .initialized
|
|
1315
1315
|
let streamVideoUserId = streamVideo?.user.id
|
|
1316
1316
|
let credentialsMatch = streamVideoUserId == savedCredentials.user.id
|
|
1317
|
-
|
|
1317
|
+
|
|
1318
1318
|
print("StreamCallPlugin: getCurrentUser: StreamVideo active: \(isStreamVideoActive), StreamVideo user: \(streamVideoUserId ?? "nil"), Credentials match: \(credentialsMatch)")
|
|
1319
|
-
|
|
1319
|
+
|
|
1320
1320
|
// If credentials exist but StreamVideo session is not active, try to reinitialize
|
|
1321
1321
|
if !isStreamVideoActive || !credentialsMatch {
|
|
1322
1322
|
print("StreamCallPlugin: getCurrentUser: StreamVideo session not active or user mismatch, attempting to reinitialize...")
|
|
@@ -1324,7 +1324,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1324
1324
|
self.initializeStreamVideo()
|
|
1325
1325
|
}
|
|
1326
1326
|
}
|
|
1327
|
-
|
|
1327
|
+
|
|
1328
1328
|
let result: [String: Any] = [
|
|
1329
1329
|
"userId": savedCredentials.user.id,
|
|
1330
1330
|
"name": savedCredentials.user.name,
|
|
@@ -1349,5 +1349,5 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1349
1349
|
call.reject("Failed to get current user: \(error.localizedDescription)")
|
|
1350
1350
|
}
|
|
1351
1351
|
}
|
|
1352
|
-
|
|
1352
|
+
|
|
1353
1353
|
}
|