Access Capacitor bridge from iOS App Delegate

Hey there,

I am integrating OneSignal native code into a capacitor app.
I’m using the Quasar Framework, which uses Capacitor.

I can receive notifications and process them on the swift side of things. My receive-notification-logic resides in AppDelegate file.

I now want to pass the notification data over to Capacitor. So when the user logs in, I can retrieve his/her/their OneSignal id and send it to my API.
Or the other way around: When AppDelegate knows the OneSignal ID, trigger an event so the bridge can listen to this and send it via event or in a window variable to the Vue code.

Is there any way to achieve this?
I would be very grateful for any help.

Best regards
Nick

See the ways the app template’s AppDelegate accesses the bridge or emits an event. You’d do one of those two things.

Thank you very much for your response. I tried to use “CAPBridge”, but its not working in application(), where I had to put the OneSignal code. Do I have to create a plugin, like @pitahausen noted?

Here’s the code I tried:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // Override point for customization after application launch.

    // START OneSignal initialization code
    ...

    // promptForPushNotifications
    OneSignal.promptForPushNotifications(userResponse: { accepted in
      CAPBridge.triggerWindowJSEvent(eventName: "onOneSignalOptin", data: "test_id_1234")
    })
    // END OneSignal initializataion code

    return true
  }

Error message is:
Instance member "triggerWindowJSEvent" cannot be used on type "CAPBridge"; did you mean to use a value of this type instead?

Best regards

If that doesn’t work I would try to create a plugin (you can just do it right in your native code, you don’t have to create a separate plugin project). Then have that plugin listen for a NotificationCenter event and send it from here instead of accessing CAPBridge

@max Thank you very much again. I created a plugin (Files: OneSignalPlugin.swift and OneSignalPlugin.m).
But how can I initialize the Plugin? Is there any kind of constructor or automatic init method or can I call my initPlugin() method from anywhere?

// OneSignalPlugin.swift

import Capacitor

extension Notification.Name {
    static let onOneSignalOptin = Notification.Name("onOneSignalOptin")
}

@objc(OneSignalPlugin)

public class OneSignalPlugin: CAPPlugin {    
    /**
     * Add event listener for NotificationCenter
     *
     * @see AppDelegate.swift
     */
    @objc func initPlugin(_ call: CAPPluginCall) {
        print("Init OneSignalPlugin")
        NotificationCenter.default.addObserver(self, selector: #selector(optin(_:)), name: .onOneSignalOptin, object: nil)
        
        call.resolve()
    }
    
    /**
     * Executes on "onOneSignalOptin" event.
     *
     * How to listen in Javascript:
     * window.addEventListener('onOneSignalOptin', (oneSignalId) => { /* YOUR CODE HERE */ }
     */
    @objc func optin(_ notification:Notification) {
        print("OneSignalPlugin: optin")
        if let data = notification.userInfo as? [String: String]
        {
            for (identifier, value) in data
            {
                if (identifier == "oneSignalId")
                {
                    self.bridge.triggerWindowJSEvent(eventName: "onOneSignalOptin", data: value)
                }
            }
        }
    }
}

Best regards
Nick

You want to override the load method which is a sort of constructor for the plugin:

Hi, to use your plugin in ionic

first In the .m file define all methods
CAP_PLUGIN_METHOD(initPlugin, CAPPluginReturnPromise);

Ionic .ts file
import { Plugins } from ‘@capacitor/core’;
const { OneSignalPlugin } = Plugins;

now to use it:
OneSignalPlugin. initPlugin();

To send data from your .swift to .ts
@objc func initPlugin(_ call: CAPPluginCall) {
call.resolve([“status”:“ok”])
}

OneSignalPlugin. initPlugin().then((data)=>{
console.log(data);
})

Or events
self.notifyListeners(“eventName”, data: [“status”:“success”])

OneSignalPlugin.addListener(“eventName”, (data) => {
console.log(data);
})

Sorry bad english :'v

@max thank you so much! This is how it worked! I’m so happy! I couldn’t use the window event but instead added a getStatus() to call from my javascript code.

Here is the full plugin:

import Capacitor

extension Notification.Name {
    static let onOneSignalOptin = Notification.Name("onOneSignalOptin")
}

@objc(OneSignalPlugin)

public class OneSignalPlugin: CAPPlugin {
    var notificationsEnabled: Bool = false
    var oneSignalId: String = "undefined"
    
    /**
     * Add event listener for NotificationCenter
     *
     * @see AppDelegate.swift
     */
    public override func load() {
        print("Init OneSignalPlugin")
        NotificationCenter.default.addObserver(self, selector: #selector(optin(_:)), name: .onOneSignalOptin, object: nil)
    }
    
    /**
     * Executes on "onOneSignalOptin" event.
     * Set oneSignalId so you can call getStatus() in your javascript code.
     *
     * e.g.: await window.Capacitor.Plugins.OneSignalPlugin.getStatus()
     */
    @objc func optin(_ notification:Notification) {
        print("OneSignalPlugin: optin")
        
        let oneSignalId = notification.userInfo?["oneSignalId"] ?? "undefined"
        let notificationsEnabled = notification.userInfo?["notificationsEnabled"] ?? false
        
        self.oneSignalId = oneSignalId as! String
        self.notificationsEnabled = notificationsEnabled as! Bool
    }
    
    @objc func getStatus(_ call: CAPPluginCall) {
        call.resolve([
            "notificationsEnabled": self.notificationsEnabled,
            "oneSignalId": self.oneSignalId
        ])
    }
}

and in AppDelegate:

...

OneSignal.promptForPushNotifications(userResponse: { accepted in
    print("User accepted notifications: \(accepted)")
    
    let oneSignalId = OneSignal.getPermissionSubscriptionState()?.subscriptionStatus.userId ?? "undefined"
    
    NotificationCenter.default.post(name: .onOneSignalOptin, object: nil, userInfo: [
        "notificationsEnabled": accepted,
        "oneSignalId": oneSignalId
    ])
  })
  
...

Also thanks to @pitahausen. Unfortunately I’m not using ionic framework, so I don’t have any .ts file. Otherwise I’m sure your solution would have worked.

Best regards
Nick

2 Likes

Awesome! Glad to hear it.

Went ahead and updated the docs to better explain the load methods for iOS and Android: https://capacitorjs.com/docs/plugins/ios#adding-initialization-logic

1 Like

You should also be able to do something like this to get the CAPBridgeViewController and then use its bridge to call triggerWindowJSEvent or other bridge methods.

if let vc = window?.rootViewController as? CAPBridgeViewController {
    vc.bridge?..triggerWindowJSEvent(eventName: "onOneSignalOptin", data: "test_id_1234")
}

@the5kyliner Thanks for suggestion on notifications, I am also using Quasar Capacitor Can you please help me configuring the push notifications in swift IOS.