Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions ios/Classes/JitsiMeetPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,27 @@ import UIKit
import JitsiMeetSDK

public class JitsiMeetPlugin: NSObject, FlutterPlugin, FlutterStreamHandler {
var flutterViewController: UIViewController
var jitsiMeetViewController: JitsiMeetViewController?
var eventSink: FlutterEventSink?

init(flutterViewController: UIViewController) {
self.flutterViewController = flutterViewController
private var rootViewController: UIViewController? {
if #available(iOS 15.0, *) {
return UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
.first { $0.activationState == .foregroundActive }?
.keyWindow?.rootViewController
Comment on lines +11 to +14
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The iOS 15.0+ implementation filters for scenes with activationState equal to foregroundActive. This means if the join method is called when the app is in a different state (like foregroundInactive during a transition), rootViewController will be nil and the join will fail with NO_VIEW_CONTROLLER error. Consider checking for foregroundInactive as well, or removing the activation state filter and just taking the first available scene, with a fallback to check other scenes if needed.

Suggested change
return UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
.first { $0.activationState == .foregroundActive }?
.keyWindow?.rootViewController
let windowScenes = UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
if let activeScene = windowScenes.first(where: { $0.activationState == .foregroundActive || $0.activationState == .foregroundInactive }),
let rootVC = activeScene.keyWindow?.rootViewController {
return rootVC
}
if let fallbackScene = windowScenes.first,
let rootVC = fallbackScene.keyWindow?.rootViewController {
return rootVC
}

Copilot uses AI. Check for mistakes.
} else if #available(iOS 13.0, *) {
return UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
.flatMap { $0.windows }
.first { $0.isKeyWindow }?.rootViewController
Comment on lines +16 to +19
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On iOS 13-14, if there are multiple windows and none of them have isKeyWindow set to true (which can happen in certain scenarios), this will return nil even though valid windows exist. Consider adding a fallback to return the rootViewController from the first window if no key window is found, before falling back to the legacy approach.

Suggested change
return UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
.flatMap { $0.windows }
.first { $0.isKeyWindow }?.rootViewController
let windows = UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
.flatMap { $0.windows }
if let keyWindow = windows.first(where: { $0.isKeyWindow }) {
return keyWindow.rootViewController
}
if let firstWindow = windows.first {
return firstWindow.rootViewController
}

Copilot uses AI. Check for mistakes.
}
return UIApplication.shared.delegate?.window??.rootViewController
}

public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "jitsi_meet_flutter_sdk", binaryMessenger: registrar.messenger())
let flutterViewController: UIViewController = (UIApplication.shared.delegate?.window??.rootViewController)!
let instance = JitsiMeetPlugin(flutterViewController: flutterViewController)
let instance = JitsiMeetPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)

let eventChannel = FlutterEventChannel(name: "jitsi_meet_flutter_sdk_events", binaryMessenger: registrar.messenger())
Expand Down Expand Up @@ -104,9 +113,14 @@ public class JitsiMeetPlugin: NSObject, FlutterPlugin, FlutterStreamHandler {
}
}

guard let presenter = rootViewController else {
result(FlutterError(code: "NO_VIEW_CONTROLLER", message: "Root view controller not available", details: nil))
return
}

jitsiMeetViewController = JitsiMeetViewController.init(options: options, eventSink: eventSink!)
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Force-unwrapping eventSink with the ! operator can cause a crash if eventSink is nil. This can happen if the join method is called before the event channel listener has been set up via onListen. Consider using optional chaining or a guard statement to check if eventSink is available before initializing JitsiMeetViewController, and return an appropriate error if it's nil.

Suggested change
jitsiMeetViewController = JitsiMeetViewController.init(options: options, eventSink: eventSink!)
guard let eventSink = eventSink else {
result(FlutterError(code: "NO_EVENT_SINK", message: "Event sink not available. Ensure the event channel listener is attached before joining.", details: nil))
return
}
jitsiMeetViewController = JitsiMeetViewController.init(options: options, eventSink: eventSink)

Copilot uses AI. Check for mistakes.
jitsiMeetViewController!.modalPresentationStyle = .overFullScreen
flutterViewController.present(jitsiMeetViewController!, animated: true)
presenter.present(jitsiMeetViewController!, animated: true)
Comment on lines 122 to +123
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Force-unwrapping jitsiMeetViewController with ! can cause a crash if the initialization of JitsiMeetViewController fails. While this is unlikely with the current API, it's safer to use optional chaining or guard statement to handle potential nil values. This also applies to line 123.

Copilot uses AI. Check for mistakes.
result("Successfully joined meeting \(room)")
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The success message includes string interpolation of room which is an optional String. If room is nil, this will print "Successfully joined meeting nil". While this doesn't cause a crash, it creates an unclear message. Consider using nil coalescing operator to provide a more meaningful message, or unwrap room safely before interpolation.

Suggested change
result("Successfully joined meeting \(room)")
let successMessage: String
if let roomName = room {
successMessage = "Successfully joined meeting \(roomName)"
} else {
successMessage = "Successfully joined meeting"
}
result(successMessage)

Copilot uses AI. Check for mistakes.
}

Expand Down
Loading