From 894c73794bc03179953b2278a56afaa0db351a52 Mon Sep 17 00:00:00 2001 From: Fadi George Date: Thu, 2 Apr 2026 14:41:42 -0700 Subject: [PATCH 01/13] feat: add OneSignal Capacitor plugin implementation --- .gitignore | 15 + OneSignalCapacitorPlugin.podspec | 20 + Package.swift | 29 + android/build.gradle | 56 ++ android/src/main/AndroidManifest.xml | 3 + .../capacitor/OneSignalCapacitorPlugin.kt | 672 ++++++++++++++++++ bun.lock | 442 ++++++++++++ .../OneSignalCapacitor.swift | 34 + .../OneSignalCapacitorPlugin.swift | 620 ++++++++++++++++ mocks/capacitor.ts | 69 ++ mocks/constants.ts | 6 + mocks/data.ts | 35 + mocks/deepmerge.ts | 39 + package.json | 74 ++ src/DebugNamespace.test.ts | 56 ++ src/DebugNamespace.ts | 39 + src/InAppMessagesNamespace.test.ts | 211 ++++++ src/InAppMessagesNamespace.ts | 216 ++++++ src/LiveActivitiesNamespace.test.ts | 149 ++++ src/LiveActivitiesNamespace.ts | 103 +++ src/LocationNamespace.test.ts | 60 ++ src/LocationNamespace.ts | 35 + src/NotificationReceivedEvent.test.ts | 75 ++ src/NotificationReceivedEvent.ts | 39 + src/NotificationsNamespace.test.ts | 312 ++++++++ src/NotificationsNamespace.ts | 232 ++++++ src/OSNotification.test.ts | 120 ++++ src/OSNotification.ts | 165 +++++ src/OneSignalPlugin.ts | 86 +++ src/PushSubscriptionNamespace.test.ts | 238 +++++++ src/PushSubscriptionNamespace.ts | 173 +++++ src/SessionNamespace.test.ts | 56 ++ src/SessionNamespace.ts | 37 + src/UserNamespace.test.ts | 367 ++++++++++ src/UserNamespace.ts | 234 ++++++ src/definitions.ts | 105 +++ src/helpers.ts | 29 + src/index.test.ts | 66 ++ src/index.ts | 42 ++ src/types/InAppMessage.ts | 48 ++ src/types/LiveActivities.ts | 15 + src/types/NotificationClicked.ts | 23 + tsconfig.json | 19 + vite.config.ts | 43 ++ 44 files changed, 5507 insertions(+) create mode 100644 .gitignore create mode 100644 OneSignalCapacitorPlugin.podspec create mode 100644 Package.swift create mode 100644 android/build.gradle create mode 100644 android/src/main/AndroidManifest.xml create mode 100644 android/src/main/kotlin/com/onesignal/capacitor/OneSignalCapacitorPlugin.kt create mode 100644 bun.lock create mode 100644 ios/Sources/OneSignalCapacitorPlugin/OneSignalCapacitor.swift create mode 100644 ios/Sources/OneSignalCapacitorPlugin/OneSignalCapacitorPlugin.swift create mode 100644 mocks/capacitor.ts create mode 100644 mocks/constants.ts create mode 100644 mocks/data.ts create mode 100644 mocks/deepmerge.ts create mode 100644 package.json create mode 100644 src/DebugNamespace.test.ts create mode 100644 src/DebugNamespace.ts create mode 100644 src/InAppMessagesNamespace.test.ts create mode 100644 src/InAppMessagesNamespace.ts create mode 100644 src/LiveActivitiesNamespace.test.ts create mode 100644 src/LiveActivitiesNamespace.ts create mode 100644 src/LocationNamespace.test.ts create mode 100644 src/LocationNamespace.ts create mode 100644 src/NotificationReceivedEvent.test.ts create mode 100644 src/NotificationReceivedEvent.ts create mode 100644 src/NotificationsNamespace.test.ts create mode 100644 src/NotificationsNamespace.ts create mode 100644 src/OSNotification.test.ts create mode 100644 src/OSNotification.ts create mode 100644 src/OneSignalPlugin.ts create mode 100644 src/PushSubscriptionNamespace.test.ts create mode 100644 src/PushSubscriptionNamespace.ts create mode 100644 src/SessionNamespace.test.ts create mode 100644 src/SessionNamespace.ts create mode 100644 src/UserNamespace.test.ts create mode 100644 src/UserNamespace.ts create mode 100644 src/definitions.ts create mode 100644 src/helpers.ts create mode 100644 src/index.test.ts create mode 100644 src/index.ts create mode 100644 src/types/InAppMessage.ts create mode 100644 src/types/LiveActivities.ts create mode 100644 src/types/NotificationClicked.ts create mode 100644 tsconfig.json create mode 100644 vite.config.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f875a7b --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +node_modules +dist +coverage +*.tgz + +.DS_Store +.idea + +# Gradle +.gradle/ +build/ + +# Android +**/capacitor.build.gradle +*.orig diff --git a/OneSignalCapacitorPlugin.podspec b/OneSignalCapacitorPlugin.podspec new file mode 100644 index 0000000..8b973a8 --- /dev/null +++ b/OneSignalCapacitorPlugin.podspec @@ -0,0 +1,20 @@ +require 'json' + +package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) + +Pod::Spec.new do |s| + s.name = 'OneSignalCapacitorPlugin' + s.version = package['version'] + s.summary = 'OneSignal Push Notifications Capacitor Plugin' + s.license = package['license'] + s.homepage = package['homepage'] + s.author = 'OneSignal' + s.source = { :git => package['repository']['url'], :tag => s.version.to_s } + s.source_files = 'ios/Sources/OneSignalCapacitorPlugin/**/*.swift' + + s.ios.deployment_target = '14.0' + s.swift_version = '5.9' + + s.dependency 'Capacitor' + s.dependency 'OneSignalXCFramework', '5.5.0' +end diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..12d19a4 --- /dev/null +++ b/Package.swift @@ -0,0 +1,29 @@ +// swift-tools-version: 5.9 + +import PackageDescription + +let package = Package( + name: "OneSignalCapacitorPlugin", + platforms: [.iOS(.v14)], + products: [ + .library( + name: "OneSignalCapacitorPlugin", + targets: ["OneSignalCapacitorPlugin"] + ) + ], + dependencies: [ + .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "7.0.0"), + .package(url: "https://github.com/nicklama/onesignal-xcframework-spm", from: "5.0.0") + ], + targets: [ + .target( + name: "OneSignalCapacitorPlugin", + dependencies: [ + .product(name: "Capacitor", package: "capacitor-swift-pm"), + .product(name: "Cordova", package: "capacitor-swift-pm"), + .product(name: "OneSignalFramework", package: "onesignal-xcframework-spm") + ], + path: "ios/Sources/OneSignalCapacitorPlugin" + ) + ] +) diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..08abf8d --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,56 @@ +ext { + junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2' + androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.0' +} + +buildscript { + ext.kotlin_version = project.hasProperty("kotlin_version") ? rootProject.ext.kotlin_version : '1.9.25' + repositories { + google() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:8.7.3' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + namespace "com.onesignal.capacitor" + compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35 + defaultConfig { + minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23 + targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35 + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + kotlinOptions { + jvmTarget = '17' + } +} + +repositories { + google() + mavenCentral() +} + +dependencies { + implementation project(':capacitor-android') + implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion" + implementation 'com.onesignal:OneSignal:5.7.6' + testImplementation "junit:junit:$junitVersion" + androidTestImplementation "androidx.test.ext:junit:1.2.1" + androidTestImplementation "androidx.test.espresso:espresso-core:3.6.1" +} diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..c06529f --- /dev/null +++ b/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/android/src/main/kotlin/com/onesignal/capacitor/OneSignalCapacitorPlugin.kt b/android/src/main/kotlin/com/onesignal/capacitor/OneSignalCapacitorPlugin.kt new file mode 100644 index 0000000..54f4f9b --- /dev/null +++ b/android/src/main/kotlin/com/onesignal/capacitor/OneSignalCapacitorPlugin.kt @@ -0,0 +1,672 @@ +package com.onesignal.capacitor + +import com.getcapacitor.JSObject +import com.getcapacitor.Plugin +import com.getcapacitor.PluginCall +import com.getcapacitor.PluginMethod +import com.getcapacitor.annotation.CapacitorPlugin +import com.onesignal.OneSignal +import com.onesignal.common.OneSignalWrapper +import com.onesignal.inAppMessages.IInAppMessageClickEvent +import com.onesignal.inAppMessages.IInAppMessageClickListener +import com.onesignal.inAppMessages.IInAppMessageDidDismissEvent +import com.onesignal.inAppMessages.IInAppMessageDidDisplayEvent +import com.onesignal.inAppMessages.IInAppMessageLifecycleListener +import com.onesignal.inAppMessages.IInAppMessageWillDismissEvent +import com.onesignal.inAppMessages.IInAppMessageWillDisplayEvent +import com.onesignal.notifications.INotificationClickEvent +import com.onesignal.notifications.INotificationClickListener +import com.onesignal.notifications.INotificationLifecycleListener +import com.onesignal.notifications.INotificationWillDisplayEvent +import org.json.JSONObject + +@CapacitorPlugin(name = "OneSignalCapacitor") +class OneSignalCapacitorPlugin : Plugin(), + INotificationLifecycleListener, + INotificationClickListener, + IInAppMessageLifecycleListener, + IInAppMessageClickListener { + + private val notificationWillDisplayCache = mutableMapOf() + private val preventDefaultCache = mutableSetOf() + private var pendingClickEvent: INotificationClickEvent? = null + + // region Core + + @PluginMethod + fun initialize(call: PluginCall) { + val appId = call.getString("appId") + if (appId == null) { + call.reject("appId is required") + return + } + + OneSignalWrapper.sdkType = "capacitor" + OneSignalWrapper.sdkVersion = "010000" + OneSignal.initWithContext(context, appId) + + OneSignal.Notifications.addPermissionObserver { permission -> + val ret = JSObject() + ret.put("permission", permission) + notifyListeners("permissionChange", ret) + } + + OneSignal.Notifications.addForegroundLifecycleListener(this) + OneSignal.Notifications.addClickListener(this) + + OneSignal.User.pushSubscription.addObserver { state -> + val ret = JSObject() + val prev = JSObject() + prev.put("id", state.previous.id ?: JSONObject.NULL) + prev.put("token", state.previous.token ?: JSONObject.NULL) + prev.put("optedIn", state.previous.optedIn) + ret.put("previous", prev) + + val curr = JSObject() + curr.put("id", state.current.id ?: JSONObject.NULL) + curr.put("token", state.current.token ?: JSONObject.NULL) + curr.put("optedIn", state.current.optedIn) + ret.put("current", curr) + + notifyListeners("pushSubscriptionChange", ret) + } + + OneSignal.User.addObserver { state -> + val ret = JSObject() + val curr = JSObject() + curr.put("onesignalId", state.current.onesignalId ?: JSONObject.NULL) + curr.put("externalId", state.current.externalId ?: JSONObject.NULL) + ret.put("current", curr) + notifyListeners("userStateChange", ret) + } + + OneSignal.InAppMessages.addLifecycleListener(this) + OneSignal.InAppMessages.addClickListener(this) + + pendingClickEvent?.let { event -> + val ret = JSObject() + ret.put("result", JSObject(event.result.toJSONObject().toString())) + ret.put("notification", JSObject(event.notification.toJSONObject().toString())) + notifyListeners("notificationClick", ret) + pendingClickEvent = null + } + + call.resolve() + } + + @PluginMethod + fun login(call: PluginCall) { + val externalId = call.getString("externalId") + if (externalId == null) { + call.reject("externalId is required") + return + } + OneSignal.login(externalId) + call.resolve() + } + + @PluginMethod + fun logout(call: PluginCall) { + OneSignal.logout() + call.resolve() + } + + @PluginMethod + fun setConsentRequired(call: PluginCall) { + val required = call.getBoolean("required") ?: false + OneSignal.setConsentRequired(required) + call.resolve() + } + + @PluginMethod + fun setConsentGiven(call: PluginCall) { + val granted = call.getBoolean("granted") ?: false + OneSignal.setConsentGiven(granted) + call.resolve() + } + + // endregion + + // region Debug + + @PluginMethod + fun setLogLevel(call: PluginCall) { + val level = call.getInt("logLevel") ?: 0 + OneSignal.Debug.logLevel = com.onesignal.debug.LogLevel.fromInt(level) + call.resolve() + } + + @PluginMethod + fun setAlertLevel(call: PluginCall) { + val level = call.getInt("logLevel") ?: 0 + OneSignal.Debug.alertLevel = com.onesignal.debug.LogLevel.fromInt(level) + call.resolve() + } + + // endregion + + // region User + + @PluginMethod + fun setLanguage(call: PluginCall) { + val language = call.getString("language") + if (language == null) { + call.reject("language is required") + return + } + OneSignal.User.setLanguage(language) + call.resolve() + } + + @PluginMethod + fun addAliases(call: PluginCall) { + val aliasesObj = call.getObject("aliases") ?: run { + call.reject("aliases is required") + return + } + val aliases = mutableMapOf() + aliasesObj.keys().forEach { key -> aliases[key] = aliasesObj.getString(key) } + OneSignal.User.addAliases(aliases) + call.resolve() + } + + @PluginMethod + fun removeAliases(call: PluginCall) { + val labels = call.getArray("labels") ?: run { + call.reject("labels is required") + return + } + val list = mutableListOf() + for (i in 0 until labels.length()) { + list.add(labels.getString(i)) + } + OneSignal.User.removeAliases(list) + call.resolve() + } + + @PluginMethod + fun addEmail(call: PluginCall) { + val email = call.getString("email") + if (email == null) { + call.reject("email is required") + return + } + OneSignal.User.addEmail(email) + call.resolve() + } + + @PluginMethod + fun removeEmail(call: PluginCall) { + val email = call.getString("email") + if (email == null) { + call.reject("email is required") + return + } + OneSignal.User.removeEmail(email) + call.resolve() + } + + @PluginMethod + fun addSms(call: PluginCall) { + val smsNumber = call.getString("smsNumber") + if (smsNumber == null) { + call.reject("smsNumber is required") + return + } + OneSignal.User.addSms(smsNumber) + call.resolve() + } + + @PluginMethod + fun removeSms(call: PluginCall) { + val smsNumber = call.getString("smsNumber") + if (smsNumber == null) { + call.reject("smsNumber is required") + return + } + OneSignal.User.removeSms(smsNumber) + call.resolve() + } + + @PluginMethod + fun addTags(call: PluginCall) { + val tagsObj = call.getObject("tags") ?: run { + call.reject("tags is required") + return + } + val tags = mutableMapOf() + tagsObj.keys().forEach { key -> tags[key] = tagsObj.getString(key) } + OneSignal.User.addTags(tags) + call.resolve() + } + + @PluginMethod + fun removeTags(call: PluginCall) { + val keysArr = call.getArray("keys") ?: run { + call.reject("keys is required") + return + } + val keys = mutableListOf() + for (i in 0 until keysArr.length()) { + keys.add(keysArr.getString(i)) + } + OneSignal.User.removeTags(keys) + call.resolve() + } + + @PluginMethod + fun getTags(call: PluginCall) { + val tags = OneSignal.User.getTags() + val tagsObj = JSObject() + tags.forEach { (key, value) -> tagsObj.put(key, value) } + val ret = JSObject() + ret.put("tags", tagsObj) + call.resolve(ret) + } + + @PluginMethod + fun getOnesignalId(call: PluginCall) { + val ret = JSObject() + ret.put("onesignalId", OneSignal.User.onesignalId ?: JSONObject.NULL) + call.resolve(ret) + } + + @PluginMethod + fun getExternalId(call: PluginCall) { + val ret = JSObject() + ret.put("externalId", OneSignal.User.externalId ?: JSONObject.NULL) + call.resolve(ret) + } + + @PluginMethod + fun trackEvent(call: PluginCall) { + val name = call.getString("name") + if (name == null) { + call.reject("name is required") + return + } + val propertiesObj = call.getObject("properties") + val properties = if (propertiesObj != null) { + val map = mutableMapOf() + propertiesObj.keys().forEach { key -> + map[key] = propertiesObj.get(key) + } + map + } else null + + OneSignal.User.trackEvent(name, properties) + call.resolve() + } + + // endregion + + // region Push Subscription + + @PluginMethod + fun getPushSubscriptionId(call: PluginCall) { + val ret = JSObject() + ret.put("id", OneSignal.User.pushSubscription.id) + call.resolve(ret) + } + + @PluginMethod + fun getPushSubscriptionToken(call: PluginCall) { + val ret = JSObject() + ret.put("token", OneSignal.User.pushSubscription.token) + call.resolve(ret) + } + + @PluginMethod + fun getPushSubscriptionOptedIn(call: PluginCall) { + val ret = JSObject() + ret.put("optedIn", OneSignal.User.pushSubscription.optedIn) + call.resolve(ret) + } + + @PluginMethod + fun optInPushSubscription(call: PluginCall) { + OneSignal.User.pushSubscription.optIn() + call.resolve() + } + + @PluginMethod + fun optOutPushSubscription(call: PluginCall) { + OneSignal.User.pushSubscription.optOut() + call.resolve() + } + + // endregion + + // region Notifications + + @PluginMethod + fun getPermission(call: PluginCall) { + val ret = JSObject() + ret.put("permission", OneSignal.Notifications.permission) + call.resolve(ret) + } + + @PluginMethod + fun permissionNative(call: PluginCall) { + val ret = JSObject() + ret.put("permission", if (OneSignal.Notifications.permission) 2 else 1) + call.resolve(ret) + } + + @PluginMethod + fun requestPermission(call: PluginCall) { + val fallback = call.getBoolean("fallbackToSettings") ?: false + OneSignal.Notifications.requestPermission(fallback) { accepted -> + val ret = JSObject() + ret.put("permission", accepted) + call.resolve(ret) + } + } + + @PluginMethod + fun canRequestPermission(call: PluginCall) { + val ret = JSObject() + ret.put("canRequest", OneSignal.Notifications.canRequestPermission) + call.resolve(ret) + } + + @PluginMethod + fun registerForProvisionalAuthorization(call: PluginCall) { + val ret = JSObject() + ret.put("accepted", true) + call.resolve(ret) + } + + @PluginMethod + fun clearAllNotifications(call: PluginCall) { + OneSignal.Notifications.clearAllNotifications() + call.resolve() + } + + @PluginMethod + fun removeNotification(call: PluginCall) { + val id = call.getInt("id") ?: run { + call.reject("id is required") + return + } + OneSignal.Notifications.removeNotification(id) + call.resolve() + } + + @PluginMethod + fun removeGroupedNotifications(call: PluginCall) { + val id = call.getString("id") + if (id == null) { + call.reject("id is required") + return + } + OneSignal.Notifications.removeGroupedNotifications(id) + call.resolve() + } + + @PluginMethod + fun preventDefault(call: PluginCall) { + val notificationId = call.getString("notificationId") + if (notificationId == null) { + call.reject("notificationId is required") + return + } + val event = notificationWillDisplayCache[notificationId] + if (event == null) { + call.reject("Could not find notification will display event") + return + } + event.preventDefault() + preventDefaultCache.add(notificationId) + call.resolve() + } + + @PluginMethod + fun proceedWithWillDisplay(call: PluginCall) { + val notificationId = call.getString("notificationId") + if (notificationId == null) { + call.reject("notificationId is required") + return + } + val event = notificationWillDisplayCache[notificationId] + if (event == null) { + call.reject("Could not find notification will display event") + return + } + if (!preventDefaultCache.contains(notificationId)) { + event.notification.display() + } + call.resolve() + } + + @PluginMethod + fun displayNotification(call: PluginCall) { + val notificationId = call.getString("notificationId") + if (notificationId == null) { + call.reject("notificationId is required") + return + } + val event = notificationWillDisplayCache[notificationId] + if (event == null) { + call.reject("Could not find notification will display event") + return + } + event.notification.display() + call.resolve() + } + + // endregion + + // region In-App Messages + + @PluginMethod + fun addTriggers(call: PluginCall) { + val triggersObj = call.getObject("triggers") ?: run { + call.reject("triggers is required") + return + } + val triggers = mutableMapOf() + triggersObj.keys().forEach { key -> triggers[key] = triggersObj.getString(key) } + OneSignal.InAppMessages.addTriggers(triggers) + call.resolve() + } + + @PluginMethod + fun removeTriggers(call: PluginCall) { + val keysArr = call.getArray("keys") ?: run { + call.reject("keys is required") + return + } + val keys = mutableListOf() + for (i in 0 until keysArr.length()) { + keys.add(keysArr.getString(i)) + } + OneSignal.InAppMessages.removeTriggers(keys) + call.resolve() + } + + @PluginMethod + fun clearTriggers(call: PluginCall) { + OneSignal.InAppMessages.clearTriggers() + call.resolve() + } + + @PluginMethod + fun setPaused(call: PluginCall) { + val pause = call.getBoolean("pause") ?: false + OneSignal.InAppMessages.paused = pause + call.resolve() + } + + @PluginMethod + fun isPaused(call: PluginCall) { + val ret = JSObject() + ret.put("paused", OneSignal.InAppMessages.paused) + call.resolve(ret) + } + + // endregion + + // region Session / Outcomes + + @PluginMethod + fun addOutcome(call: PluginCall) { + val name = call.getString("name") + if (name == null) { + call.reject("name is required") + return + } + OneSignal.Session.addOutcome(name) + call.resolve() + } + + @PluginMethod + fun addUniqueOutcome(call: PluginCall) { + val name = call.getString("name") + if (name == null) { + call.reject("name is required") + return + } + OneSignal.Session.addUniqueOutcome(name) + call.resolve() + } + + @PluginMethod + fun addOutcomeWithValue(call: PluginCall) { + val name = call.getString("name") + if (name == null) { + call.reject("name is required") + return + } + val value = call.getFloat("value") ?: 0f + OneSignal.Session.addOutcomeWithValue(name, value) + call.resolve() + } + + // endregion + + // region Location + + @PluginMethod + fun requestLocationPermission(call: PluginCall) { + OneSignal.Location.requestPermission() + call.resolve() + } + + @PluginMethod + fun setLocationShared(call: PluginCall) { + val shared = call.getBoolean("shared") ?: false + OneSignal.Location.isShared = shared + call.resolve() + } + + @PluginMethod + fun isLocationShared(call: PluginCall) { + val ret = JSObject() + ret.put("shared", OneSignal.Location.isShared) + call.resolve(ret) + } + + // endregion + + // region Live Activities (no-op on Android) + + @PluginMethod + fun enterLiveActivity(call: PluginCall) { + call.resolve() + } + + @PluginMethod + fun exitLiveActivity(call: PluginCall) { + call.resolve() + } + + @PluginMethod + fun setPushToStartToken(call: PluginCall) { + call.resolve() + } + + @PluginMethod + fun removePushToStartToken(call: PluginCall) { + call.resolve() + } + + @PluginMethod + fun setupDefaultLiveActivity(call: PluginCall) { + call.resolve() + } + + @PluginMethod + fun startDefaultLiveActivity(call: PluginCall) { + call.resolve() + } + + // endregion + + // region Observer Callbacks + + override fun onWillDisplay(event: INotificationWillDisplayEvent) { + val notificationId = event.notification.notificationId + notificationWillDisplayCache[notificationId] = event + event.preventDefault() + val ret = JSObject(event.notification.toJSONObject().toString()) + notifyListeners("notificationForegroundWillDisplay", ret) + } + + override fun onClick(event: INotificationClickEvent) { + if (bridge != null) { + val ret = JSObject() + ret.put("result", JSObject(event.result.toJSONObject().toString())) + ret.put("notification", JSObject(event.notification.toJSONObject().toString())) + notifyListeners("notificationClick", ret) + } else { + pendingClickEvent = event + } + } + + override fun onWillDisplay(event: IInAppMessageWillDisplayEvent) { + val ret = JSObject() + ret.put("message", JSObject().put("messageId", event.message.messageId)) + notifyListeners("inAppMessageWillDisplay", ret) + } + + override fun onDidDisplay(event: IInAppMessageDidDisplayEvent) { + val ret = JSObject() + ret.put("message", JSObject().put("messageId", event.message.messageId)) + notifyListeners("inAppMessageDidDisplay", ret) + } + + override fun onWillDismiss(event: IInAppMessageWillDismissEvent) { + val ret = JSObject() + ret.put("message", JSObject().put("messageId", event.message.messageId)) + notifyListeners("inAppMessageWillDismiss", ret) + } + + override fun onDidDismiss(event: IInAppMessageDidDismissEvent) { + val ret = JSObject() + ret.put("message", JSObject().put("messageId", event.message.messageId)) + notifyListeners("inAppMessageDidDismiss", ret) + } + + override fun onClick(event: IInAppMessageClickEvent) { + val urlTarget = when (event.result.urlTarget.ordinal) { + 0 -> "browser" + 1 -> "webview" + 2 -> "replacement" + else -> "browser" + } + + val clickResult = JSObject() + clickResult.put("closingMessage", event.result.closingMessage) + clickResult.put("actionId", event.result.actionId) + clickResult.put("url", event.result.url) + clickResult.put("urlTarget", urlTarget) + + val ret = JSObject() + ret.put("message", JSObject().put("messageId", event.message.messageId)) + ret.put("result", clickResult) + notifyListeners("inAppMessageClick", ret) + } + + // endregion +} diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..66ca764 --- /dev/null +++ b/bun.lock @@ -0,0 +1,442 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "onesignal-capacitor-plugin", + "devDependencies": { + "@capacitor/core": "^7.0.0", + "@capacitor/docgen": "^0.2.2", + "@types/bun": "latest", + "@vitest/coverage-v8": "^4.0.2", + "happy-dom": "^20.0.8", + "typescript": "^5.9.3", + "vite": "^7.1.12", + "vite-plugin-dts": "^4.5.4", + "vitest": "^4.0.2", + }, + "peerDependencies": { + "@capacitor/core": "^7.0.0", + }, + }, + }, + "packages": { + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + + "@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="], + + "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "@bcoe/v8-coverage": ["@bcoe/v8-coverage@1.0.2", "", {}, "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA=="], + + "@capacitor/core": ["@capacitor/core@7.6.1", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-nsNouCMxgYenyemy20sZwZYMtFi93LSZVWm2KqHTYIPIDgwx24+PzwHIdRQBZdK7hpvD5jQEhWuo/QyLLnAyBQ=="], + + "@capacitor/docgen": ["@capacitor/docgen@0.2.2", "", { "dependencies": { "@types/node": "^14.18.0", "colorette": "^2.0.16", "github-slugger": "^1.4.0", "minimist": "^1.2.5", "typescript": "~4.2.4" }, "bin": { "docgen": "bin/docgen" } }, "sha512-617jB5DlKjUljxfoANORWeqffsHqHekckH48oslKWFGzfCHYIAEPfJfMa5M4mVSzn2ifSvWB5C3st4augwhl5w=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.7", "", { "os": "aix", "cpu": "ppc64" }, "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.27.7", "", { "os": "android", "cpu": "arm" }, "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.7", "", { "os": "android", "cpu": "arm64" }, "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.27.7", "", { "os": "android", "cpu": "x64" }, "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.7", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.7", "", { "os": "linux", "cpu": "arm" }, "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.7", "", { "os": "linux", "cpu": "ia32" }, "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.7", "", { "os": "linux", "cpu": "ppc64" }, "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.7", "", { "os": "linux", "cpu": "s390x" }, "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.7", "", { "os": "linux", "cpu": "x64" }, "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.7", "", { "os": "none", "cpu": "x64" }, "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.7", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.7", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.7", "", { "os": "sunos", "cpu": "x64" }, "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.7", "", { "os": "win32", "cpu": "ia32" }, "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@microsoft/api-extractor": ["@microsoft/api-extractor@7.58.0", "", { "dependencies": { "@microsoft/api-extractor-model": "7.33.5", "@microsoft/tsdoc": "~0.16.0", "@microsoft/tsdoc-config": "~0.18.1", "@rushstack/node-core-library": "5.21.0", "@rushstack/rig-package": "0.7.2", "@rushstack/terminal": "0.22.4", "@rushstack/ts-command-line": "5.3.4", "diff": "~8.0.2", "lodash": "~4.17.23", "minimatch": "10.2.3", "resolve": "~1.22.1", "semver": "~7.5.4", "source-map": "~0.6.1", "typescript": "5.9.3" }, "bin": { "api-extractor": "bin/api-extractor" } }, "sha512-XcHlDylX5GblbGbs1eBoexjVyvdJMioTPuBCgCorE2rqijzTYxi6eudXyez3xACRxtH9aDtahoL9fYM4XTvQmg=="], + + "@microsoft/api-extractor-model": ["@microsoft/api-extractor-model@7.33.5", "", { "dependencies": { "@microsoft/tsdoc": "~0.16.0", "@microsoft/tsdoc-config": "~0.18.1", "@rushstack/node-core-library": "5.21.0" } }, "sha512-Xh4dXuusndVQqVz4nEN9xOp0DyzsKxeD2FFJkSPg4arAjDSKPcy6cAc7CaeBPA7kF2wV1fuDlo2p/bNMpVr8yg=="], + + "@microsoft/tsdoc": ["@microsoft/tsdoc@0.16.0", "", {}, "sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA=="], + + "@microsoft/tsdoc-config": ["@microsoft/tsdoc-config@0.18.1", "", { "dependencies": { "@microsoft/tsdoc": "0.16.0", "ajv": "~8.18.0", "jju": "~1.4.0", "resolve": "~1.22.2" } }, "sha512-9brPoVdfN9k9g0dcWkFeA7IH9bbcttzDJlXvkf8b2OBzd5MueR1V2wkKBL0abn0otvmkHJC6aapBOTJDDeMCZg=="], + + "@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.1", "", { "os": "android", "cpu": "arm" }, "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.1", "", { "os": "android", "cpu": "arm64" }, "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.60.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.60.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.60.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.60.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.60.1", "", { "os": "linux", "cpu": "arm" }, "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.60.1", "", { "os": "linux", "cpu": "arm" }, "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.60.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.60.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA=="], + + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ=="], + + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.60.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw=="], + + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.60.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.60.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.60.1", "", { "os": "linux", "cpu": "x64" }, "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.60.1", "", { "os": "linux", "cpu": "x64" }, "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w=="], + + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.60.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw=="], + + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.60.1", "", { "os": "none", "cpu": "arm64" }, "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.60.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.60.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg=="], + + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.60.1", "", { "os": "win32", "cpu": "x64" }, "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.60.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ=="], + + "@rushstack/node-core-library": ["@rushstack/node-core-library@5.21.0", "", { "dependencies": { "ajv": "~8.18.0", "ajv-draft-04": "~1.0.0", "ajv-formats": "~3.0.1", "fs-extra": "~11.3.0", "import-lazy": "~4.0.0", "jju": "~1.4.0", "resolve": "~1.22.1", "semver": "~7.5.4" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-LFzN+1lyWROit/P8Md6yxAth7lLYKn37oCKJHirEE2TQB25NDUM7bALf0ar+JAtwFfRCH+D+DGOA7DAzIi2r+g=="], + + "@rushstack/problem-matcher": ["@rushstack/problem-matcher@0.2.1", "", { "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-gulfhBs6n+I5b7DvjKRfhMGyUejtSgOHTclF/eONr8hcgF1APEDjhxIsfdUYYMzC3rvLwGluqLjbwCFZ8nxrog=="], + + "@rushstack/rig-package": ["@rushstack/rig-package@0.7.2", "", { "dependencies": { "resolve": "~1.22.1", "strip-json-comments": "~3.1.1" } }, "sha512-9XbFWuqMYcHUso4mnETfhGVUSaADBRj6HUAAEYk50nMPn8WRICmBuCphycQGNB3duIR6EEZX3Xj3SYc2XiP+9A=="], + + "@rushstack/terminal": ["@rushstack/terminal@0.22.4", "", { "dependencies": { "@rushstack/node-core-library": "5.21.0", "@rushstack/problem-matcher": "0.2.1", "supports-color": "~8.1.1" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-fhtLjnXCc/4WleVbVl6aoc7jcWnU6yqjS1S8WoaNREG3ycu/viZ9R/9QM7Y/b4CDvcXoiDyMNIay7JMwBptM3g=="], + + "@rushstack/ts-command-line": ["@rushstack/ts-command-line@5.3.4", "", { "dependencies": { "@rushstack/terminal": "0.22.4", "@types/argparse": "1.0.38", "argparse": "~1.0.9", "string-argv": "~0.3.1" } }, "sha512-MLkVKVEN6/2clKTrjN2B2KqKCuPxRwnNsWY7a+FCAq2EMdkj10cM8YgiBSMeGFfzM0mDMzargpHNnNzaBi9Whg=="], + + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@types/argparse": ["@types/argparse@1.0.38", "", {}, "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA=="], + + "@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="], + + "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], + + "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], + + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/node": ["@types/node@14.18.63", "", {}, "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ=="], + + "@types/whatwg-mimetype": ["@types/whatwg-mimetype@3.0.2", "", {}, "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA=="], + + "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], + + "@vitest/coverage-v8": ["@vitest/coverage-v8@4.1.2", "", { "dependencies": { "@bcoe/v8-coverage": "^1.0.2", "@vitest/utils": "4.1.2", "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-reports": "^3.2.0", "magicast": "^0.5.2", "obug": "^2.1.1", "std-env": "^4.0.0-rc.1", "tinyrainbow": "^3.1.0" }, "peerDependencies": { "@vitest/browser": "4.1.2", "vitest": "4.1.2" }, "optionalPeers": ["@vitest/browser"] }, "sha512-sPK//PHO+kAkScb8XITeB1bf7fsk85Km7+rt4eeuRR3VS1/crD47cmV5wicisJmjNdfeokTZwjMk4Mj2d58Mgg=="], + + "@vitest/expect": ["@vitest/expect@4.1.2", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.2", "@vitest/utils": "4.1.2", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ=="], + + "@vitest/mocker": ["@vitest/mocker@4.1.2", "", { "dependencies": { "@vitest/spy": "4.1.2", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q=="], + + "@vitest/pretty-format": ["@vitest/pretty-format@4.1.2", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA=="], + + "@vitest/runner": ["@vitest/runner@4.1.2", "", { "dependencies": { "@vitest/utils": "4.1.2", "pathe": "^2.0.3" } }, "sha512-Gr+FQan34CdiYAwpGJmQG8PgkyFVmARK8/xSijia3eTFgVfpcpztWLuP6FttGNfPLJhaZVP/euvujeNYar36OQ=="], + + "@vitest/snapshot": ["@vitest/snapshot@4.1.2", "", { "dependencies": { "@vitest/pretty-format": "4.1.2", "@vitest/utils": "4.1.2", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-g7yfUmxYS4mNxk31qbOYsSt2F4m1E02LFqO53Xpzg3zKMhLAPZAjjfyl9e6z7HrW6LvUdTwAQR3HHfLjpko16A=="], + + "@vitest/spy": ["@vitest/spy@4.1.2", "", {}, "sha512-DU4fBnbVCJGNBwVA6xSToNXrkZNSiw59H8tcuUspVMsBDBST4nfvsPsEHDHGtWRRnqBERBQu7TrTKskmjqTXKA=="], + + "@vitest/utils": ["@vitest/utils@4.1.2", "", { "dependencies": { "@vitest/pretty-format": "4.1.2", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ=="], + + "@volar/language-core": ["@volar/language-core@2.4.28", "", { "dependencies": { "@volar/source-map": "2.4.28" } }, "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ=="], + + "@volar/source-map": ["@volar/source-map@2.4.28", "", {}, "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ=="], + + "@volar/typescript": ["@volar/typescript@2.4.28", "", { "dependencies": { "@volar/language-core": "2.4.28", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw=="], + + "@vue/compiler-core": ["@vue/compiler-core@3.5.31", "", { "dependencies": { "@babel/parser": "^7.29.2", "@vue/shared": "3.5.31", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-k/ueL14aNIEy5Onf0OVzR8kiqF/WThgLdFhxwa4e/KF/0qe38IwIdofoSWBTvvxQOesaz6riAFAUaYjoF9fLLQ=="], + + "@vue/compiler-dom": ["@vue/compiler-dom@3.5.31", "", { "dependencies": { "@vue/compiler-core": "3.5.31", "@vue/shared": "3.5.31" } }, "sha512-BMY/ozS/xxjYqRFL+tKdRpATJYDTTgWSo0+AJvJNg4ig+Hgb0dOsHPXvloHQ5hmlivUqw1Yt2pPIqp4e0v1GUw=="], + + "@vue/compiler-vue2": ["@vue/compiler-vue2@2.7.16", "", { "dependencies": { "de-indent": "^1.0.2", "he": "^1.2.0" } }, "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A=="], + + "@vue/language-core": ["@vue/language-core@2.2.0", "", { "dependencies": { "@volar/language-core": "~2.4.11", "@vue/compiler-dom": "^3.5.0", "@vue/compiler-vue2": "^2.7.16", "@vue/shared": "^3.5.0", "alien-signals": "^0.4.9", "minimatch": "^9.0.3", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw=="], + + "@vue/shared": ["@vue/shared@3.5.31", "", {}, "sha512-nBxuiuS9Lj5bPkPbWogPUnjxxWpkRniX7e5UBQDWl6Fsf4roq9wwV+cR7ezQ4zXswNvPIlsdj1slcLB7XCsRAw=="], + + "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + + "ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="], + + "ajv-draft-04": ["ajv-draft-04@1.0.0", "", { "peerDependencies": { "ajv": "^8.5.0" }, "optionalPeers": ["ajv"] }, "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw=="], + + "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], + + "alien-signals": ["alien-signals@0.4.14", "", {}, "sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q=="], + + "argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], + + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + + "ast-v8-to-istanbul": ["ast-v8-to-istanbul@1.0.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.31", "estree-walker": "^3.0.3", "js-tokens": "^10.0.0" } }, "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg=="], + + "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + + "brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="], + + "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], + + "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], + + "colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="], + + "compare-versions": ["compare-versions@6.1.1", "", {}, "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg=="], + + "confbox": ["confbox@0.2.4", "", {}, "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "de-indent": ["de-indent@1.0.2", "", {}, "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "diff": ["diff@8.0.4", "", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="], + + "entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], + + "es-module-lexer": ["es-module-lexer@2.0.0", "", {}, "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw=="], + + "esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="], + + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], + + "exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "fs-extra": ["fs-extra@11.3.4", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "github-slugger": ["github-slugger@1.5.0", "", {}, "sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "happy-dom": ["happy-dom@20.8.9", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.18.3" } }, "sha512-Tz23LR9T9jOGVZm2x1EPdXqwA37G/owYMxRwU0E4miurAtFsPMQ1d2Jc2okUaSjZqAFz2oEn3FLXC5a0a+siyA=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + + "he": ["he@1.2.0", "", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="], + + "html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="], + + "import-lazy": ["import-lazy@4.0.0", "", {}, "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw=="], + + "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], + + "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="], + + "istanbul-lib-report": ["istanbul-lib-report@3.0.1", "", { "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw=="], + + "istanbul-reports": ["istanbul-reports@3.2.0", "", { "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA=="], + + "jju": ["jju@1.4.0", "", {}, "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA=="], + + "js-tokens": ["js-tokens@10.0.0", "", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="], + + "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], + + "kolorist": ["kolorist@1.8.0", "", {}, "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ=="], + + "local-pkg": ["local-pkg@1.1.2", "", { "dependencies": { "mlly": "^1.7.4", "pkg-types": "^2.3.0", "quansync": "^0.2.11" } }, "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A=="], + + "lodash": ["lodash@4.17.23", "", {}, "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="], + + "lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "magicast": ["magicast@0.5.2", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "source-map-js": "^1.2.1" } }, "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ=="], + + "make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="], + + "minimatch": ["minimatch@10.2.3", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-Rwi3pnapEqirPSbWbrZaa6N3nmqq4Xer/2XooiOKyV3q12ML06f7MOuc5DVH8ONZIFhwIYQ3yzPH4nt7iWHaTg=="], + + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + + "mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="], + + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], + + "path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="], + + "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + + "pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="], + + "postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], + + "quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="], + + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + + "resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], + + "rollup": ["rollup@4.60.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.1", "@rollup/rollup-android-arm64": "4.60.1", "@rollup/rollup-darwin-arm64": "4.60.1", "@rollup/rollup-darwin-x64": "4.60.1", "@rollup/rollup-freebsd-arm64": "4.60.1", "@rollup/rollup-freebsd-x64": "4.60.1", "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", "@rollup/rollup-linux-arm-musleabihf": "4.60.1", "@rollup/rollup-linux-arm64-gnu": "4.60.1", "@rollup/rollup-linux-arm64-musl": "4.60.1", "@rollup/rollup-linux-loong64-gnu": "4.60.1", "@rollup/rollup-linux-loong64-musl": "4.60.1", "@rollup/rollup-linux-ppc64-gnu": "4.60.1", "@rollup/rollup-linux-ppc64-musl": "4.60.1", "@rollup/rollup-linux-riscv64-gnu": "4.60.1", "@rollup/rollup-linux-riscv64-musl": "4.60.1", "@rollup/rollup-linux-s390x-gnu": "4.60.1", "@rollup/rollup-linux-x64-gnu": "4.60.1", "@rollup/rollup-linux-x64-musl": "4.60.1", "@rollup/rollup-openbsd-x64": "4.60.1", "@rollup/rollup-openharmony-arm64": "4.60.1", "@rollup/rollup-win32-arm64-msvc": "4.60.1", "@rollup/rollup-win32-ia32-msvc": "4.60.1", "@rollup/rollup-win32-x64-gnu": "4.60.1", "@rollup/rollup-win32-x64-msvc": "4.60.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w=="], + + "semver": ["semver@7.5.4", "", { "dependencies": { "lru-cache": "^6.0.0" }, "bin": { "semver": "bin/semver.js" } }, "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA=="], + + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], + + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="], + + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], + + "std-env": ["std-env@4.0.0", "", {}, "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ=="], + + "string-argv": ["string-argv@0.3.2", "", {}, "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q=="], + + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], + + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], + + "tinyexec": ["tinyexec@1.0.4", "", {}, "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw=="], + + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + + "tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="], + + "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + + "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], + + "vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="], + + "vite-plugin-dts": ["vite-plugin-dts@4.5.4", "", { "dependencies": { "@microsoft/api-extractor": "^7.50.1", "@rollup/pluginutils": "^5.1.4", "@volar/typescript": "^2.4.11", "@vue/language-core": "2.2.0", "compare-versions": "^6.1.1", "debug": "^4.4.0", "kolorist": "^1.8.0", "local-pkg": "^1.0.0", "magic-string": "^0.30.17" }, "peerDependencies": { "typescript": "*", "vite": "*" }, "optionalPeers": ["vite"] }, "sha512-d4sOM8M/8z7vRXHHq/ebbblfaxENjogAAekcfcDCCwAyvGqnPrc7f4NZbvItS+g4WTgerW0xDwSz5qz11JT3vg=="], + + "vitest": ["vitest@4.1.2", "", { "dependencies": { "@vitest/expect": "4.1.2", "@vitest/mocker": "4.1.2", "@vitest/pretty-format": "4.1.2", "@vitest/runner": "4.1.2", "@vitest/snapshot": "4.1.2", "@vitest/spy": "4.1.2", "@vitest/utils": "4.1.2", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.2", "@vitest/browser-preview": "4.1.2", "@vitest/browser-webdriverio": "4.1.2", "@vitest/ui": "4.1.2", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg=="], + + "vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="], + + "whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="], + + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + + "ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="], + + "yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "@capacitor/docgen/typescript": ["typescript@4.2.4", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg=="], + + "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + + "@rushstack/terminal/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], + + "@types/ws/@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], + + "@vue/compiler-core/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + + "@vue/language-core/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], + + "bun-types/@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], + + "happy-dom/@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], + + "make-dir/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + + "mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], + + "@vue/language-core/minimatch/brace-expansion": ["brace-expansion@2.0.3", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA=="], + + "mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], + + "@vue/language-core/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + } +} diff --git a/ios/Sources/OneSignalCapacitorPlugin/OneSignalCapacitor.swift b/ios/Sources/OneSignalCapacitorPlugin/OneSignalCapacitor.swift new file mode 100644 index 0000000..b3f44f7 --- /dev/null +++ b/ios/Sources/OneSignalCapacitorPlugin/OneSignalCapacitor.swift @@ -0,0 +1,34 @@ +import Foundation +import OneSignalFramework + +@objc public class OneSignalCapacitor: NSObject { + @objc public func initialize(_ appId: String) { + OneSignalWrapper.sdkType = "capacitor" + OneSignalWrapper.sdkVersion = "010000" + OneSignal.initialize(appId, withLaunchOptions: nil) + } + + @objc public func login(_ externalId: String) { + OneSignal.login(externalId) + } + + @objc public func logout() { + OneSignal.logout() + } + + @objc public func setConsentRequired(_ required: Bool) { + OneSignal.setConsentRequired(required) + } + + @objc public func setConsentGiven(_ granted: Bool) { + OneSignal.setConsentGiven(granted) + } + + @objc public func setLogLevel(_ logLevel: Int) { + OneSignal.Debug.setLogLevel(ONE_S_LOG_LEVEL(rawValue: UInt32(logLevel))!) + } + + @objc public func setAlertLevel(_ logLevel: Int) { + OneSignal.Debug.setAlertLevel(ONE_S_LOG_LEVEL(rawValue: UInt32(logLevel))!) + } +} diff --git a/ios/Sources/OneSignalCapacitorPlugin/OneSignalCapacitorPlugin.swift b/ios/Sources/OneSignalCapacitorPlugin/OneSignalCapacitorPlugin.swift new file mode 100644 index 0000000..d8b7884 --- /dev/null +++ b/ios/Sources/OneSignalCapacitorPlugin/OneSignalCapacitorPlugin.swift @@ -0,0 +1,620 @@ +import Foundation +import Capacitor +import OneSignalFramework +import OneSignalLiveActivities + +@objc(OneSignalCapacitorPlugin) +public class OneSignalCapacitorPlugin: CAPPlugin, CAPBridgedPlugin, + OSNotificationPermissionObserver, + OSNotificationLifecycleListener, + OSNotificationClickListener, + OSPushSubscriptionObserver, + OSInAppMessageLifecycleListener, + OSInAppMessageClickListener, + OSUserStateObserver { + + public let identifier = "OneSignalCapacitorPlugin" + public let jsName = "OneSignalCapacitor" + public let pluginMethods: [CAPPluginMethod] = [ + CAPPluginMethod(name: "initialize", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "login", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "logout", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "setConsentRequired", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "setConsentGiven", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "setLogLevel", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "setAlertLevel", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "setLanguage", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "addAliases", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "removeAliases", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "addEmail", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "removeEmail", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "addSms", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "removeSms", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "addTags", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "removeTags", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "getTags", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "getOnesignalId", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "getExternalId", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "trackEvent", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "getPushSubscriptionId", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "getPushSubscriptionToken", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "getPushSubscriptionOptedIn", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "optInPushSubscription", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "optOutPushSubscription", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "getPermission", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "permissionNative", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "requestPermission", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "canRequestPermission", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "registerForProvisionalAuthorization", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "clearAllNotifications", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "removeNotification", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "removeGroupedNotifications", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "preventDefault", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "proceedWithWillDisplay", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "displayNotification", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "addTriggers", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "removeTriggers", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "clearTriggers", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "setPaused", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "isPaused", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "addOutcome", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "addUniqueOutcome", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "addOutcomeWithValue", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "requestLocationPermission", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "setLocationShared", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "isLocationShared", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "enterLiveActivity", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "exitLiveActivity", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "setPushToStartToken", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "removePushToStartToken", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "setupDefaultLiveActivity", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "startDefaultLiveActivity", returnType: CAPPluginReturnPromise), + ] + + private let implementation = OneSignalCapacitor() + private var notificationWillDisplayCache = [String: OSNotificationWillDisplayEvent]() + private var preventDefaultCache = [String: OSNotificationWillDisplayEvent]() + private var pendingClickEvent: OSNotificationClickEvent? + + // MARK: - Core + + @objc func initialize(_ call: CAPPluginCall) { + guard let appId = call.getString("appId") else { + call.reject("appId is required") + return + } + implementation.initialize(appId) + OneSignal.Notifications.addPermissionObserver(self) + OneSignal.Notifications.addForegroundLifecycleListener(self) + OneSignal.Notifications.addClickListener(self) + OneSignal.User.pushSubscription.addObserver(self) + OneSignal.User.addObserver(self) + OneSignal.InAppMessages.addLifecycleListener(self) + OneSignal.InAppMessages.addClickListener(self) + + if let pending = pendingClickEvent { + notifyListeners("notificationClick", data: pending.jsonRepresentation) + pendingClickEvent = nil + } + call.resolve() + } + + @objc func login(_ call: CAPPluginCall) { + guard let externalId = call.getString("externalId") else { + call.reject("externalId is required") + return + } + implementation.login(externalId) + call.resolve() + } + + @objc func logout(_ call: CAPPluginCall) { + implementation.logout() + call.resolve() + } + + @objc func setConsentRequired(_ call: CAPPluginCall) { + let required = call.getBool("required") ?? false + implementation.setConsentRequired(required) + call.resolve() + } + + @objc func setConsentGiven(_ call: CAPPluginCall) { + let granted = call.getBool("granted") ?? false + implementation.setConsentGiven(granted) + call.resolve() + } + + // MARK: - Debug + + @objc func setLogLevel(_ call: CAPPluginCall) { + let level = call.getInt("logLevel") ?? 0 + implementation.setLogLevel(level) + call.resolve() + } + + @objc func setAlertLevel(_ call: CAPPluginCall) { + let level = call.getInt("logLevel") ?? 0 + implementation.setAlertLevel(level) + call.resolve() + } + + // MARK: - User + + @objc func setLanguage(_ call: CAPPluginCall) { + guard let language = call.getString("language") else { + call.reject("language is required") + return + } + OneSignal.User.setLanguage(language) + call.resolve() + } + + @objc func addAliases(_ call: CAPPluginCall) { + guard let aliases = call.getObject("aliases") as? [String: String] else { + call.reject("aliases is required") + return + } + OneSignal.User.addAliases(aliases) + call.resolve() + } + + @objc func removeAliases(_ call: CAPPluginCall) { + guard let labels = call.getArray("labels", String.self) else { + call.reject("labels is required") + return + } + OneSignal.User.removeAliases(labels) + call.resolve() + } + + @objc func addEmail(_ call: CAPPluginCall) { + guard let email = call.getString("email") else { + call.reject("email is required") + return + } + OneSignal.User.addEmail(email) + call.resolve() + } + + @objc func removeEmail(_ call: CAPPluginCall) { + guard let email = call.getString("email") else { + call.reject("email is required") + return + } + OneSignal.User.removeEmail(email) + call.resolve() + } + + @objc func addSms(_ call: CAPPluginCall) { + guard let smsNumber = call.getString("smsNumber") else { + call.reject("smsNumber is required") + return + } + OneSignal.User.addSms(smsNumber) + call.resolve() + } + + @objc func removeSms(_ call: CAPPluginCall) { + guard let smsNumber = call.getString("smsNumber") else { + call.reject("smsNumber is required") + return + } + OneSignal.User.removeSms(smsNumber) + call.resolve() + } + + @objc func addTags(_ call: CAPPluginCall) { + guard let tags = call.getObject("tags") as? [String: String] else { + call.reject("tags is required") + return + } + OneSignal.User.addTags(tags) + call.resolve() + } + + @objc func removeTags(_ call: CAPPluginCall) { + guard let keys = call.getArray("keys", String.self) else { + call.reject("keys is required") + return + } + OneSignal.User.removeTags(keys) + call.resolve() + } + + @objc func getTags(_ call: CAPPluginCall) { + let tags = OneSignal.User.getTags() + call.resolve(["tags": tags]) + } + + @objc func getOnesignalId(_ call: CAPPluginCall) { + call.resolve(["onesignalId": OneSignal.User.onesignalId ?? NSNull()]) + } + + @objc func getExternalId(_ call: CAPPluginCall) { + call.resolve(["externalId": OneSignal.User.externalId ?? NSNull()]) + } + + @objc func trackEvent(_ call: CAPPluginCall) { + guard let name = call.getString("name") else { + call.reject("name is required") + return + } + let properties = call.getObject("properties") + OneSignal.User.trackEvent(withName: name, properties: properties) + call.resolve() + } + + // MARK: - Push Subscription + + @objc func getPushSubscriptionId(_ call: CAPPluginCall) { + call.resolve(["id": OneSignal.User.pushSubscription.id ?? NSNull()]) + } + + @objc func getPushSubscriptionToken(_ call: CAPPluginCall) { + call.resolve(["token": OneSignal.User.pushSubscription.token ?? NSNull()]) + } + + @objc func getPushSubscriptionOptedIn(_ call: CAPPluginCall) { + call.resolve(["optedIn": OneSignal.User.pushSubscription.optedIn]) + } + + @objc func optInPushSubscription(_ call: CAPPluginCall) { + OneSignal.User.pushSubscription.optIn() + call.resolve() + } + + @objc func optOutPushSubscription(_ call: CAPPluginCall) { + OneSignal.User.pushSubscription.optOut() + call.resolve() + } + + // MARK: - Notifications + + @objc func getPermission(_ call: CAPPluginCall) { + call.resolve(["permission": OneSignal.Notifications.permission]) + } + + @objc func permissionNative(_ call: CAPPluginCall) { + call.resolve(["permission": OneSignal.Notifications.permissionNative().rawValue]) + } + + @objc func requestPermission(_ call: CAPPluginCall) { + let fallback = call.getBool("fallbackToSettings") ?? false + OneSignal.Notifications.requestPermission({ accepted in + call.resolve(["permission": accepted]) + }, fallbackToSettings: fallback) + } + + @objc func canRequestPermission(_ call: CAPPluginCall) { + call.resolve(["canRequest": OneSignal.Notifications.canRequestPermission]) + } + + @objc func registerForProvisionalAuthorization(_ call: CAPPluginCall) { + OneSignal.Notifications.registerForProvisionalAuthorization { accepted in + call.resolve(["accepted": accepted]) + } + } + + @objc func clearAllNotifications(_ call: CAPPluginCall) { + OneSignal.Notifications.clearAll() + call.resolve() + } + + @objc func removeNotification(_ call: CAPPluginCall) { + call.resolve() + } + + @objc func removeGroupedNotifications(_ call: CAPPluginCall) { + call.resolve() + } + + @objc func preventDefault(_ call: CAPPluginCall) { + guard let notificationId = call.getString("notificationId") else { + call.reject("notificationId is required") + return + } + guard let event = notificationWillDisplayCache[notificationId] else { + call.reject("Could not find notification will display event") + return + } + event.preventDefault() + preventDefaultCache[notificationId] = event + call.resolve() + } + + @objc func proceedWithWillDisplay(_ call: CAPPluginCall) { + guard let notificationId = call.getString("notificationId") else { + call.reject("notificationId is required") + return + } + guard let event = notificationWillDisplayCache[notificationId] else { + call.reject("Could not find notification will display event") + return + } + if preventDefaultCache[notificationId] == nil { + event.notification.display() + } + call.resolve() + } + + @objc func displayNotification(_ call: CAPPluginCall) { + guard let notificationId = call.getString("notificationId") else { + call.reject("notificationId is required") + return + } + guard let event = notificationWillDisplayCache[notificationId] else { + call.reject("Could not find notification will display event") + return + } + event.notification.display() + call.resolve() + } + + // MARK: - In-App Messages + + @objc func addTriggers(_ call: CAPPluginCall) { + guard let triggers = call.getObject("triggers") as? [String: String] else { + call.reject("triggers is required") + return + } + OneSignal.InAppMessages.addTriggers(triggers) + call.resolve() + } + + @objc func removeTriggers(_ call: CAPPluginCall) { + guard let keys = call.getArray("keys", String.self) else { + call.reject("keys is required") + return + } + OneSignal.InAppMessages.removeTriggers(keys) + call.resolve() + } + + @objc func clearTriggers(_ call: CAPPluginCall) { + OneSignal.InAppMessages.clearTriggers() + call.resolve() + } + + @objc func setPaused(_ call: CAPPluginCall) { + let pause = call.getBool("pause") ?? false + OneSignal.InAppMessages.paused = pause + call.resolve() + } + + @objc func isPaused(_ call: CAPPluginCall) { + call.resolve(["paused": OneSignal.InAppMessages.paused]) + } + + // MARK: - Session / Outcomes + + @objc func addOutcome(_ call: CAPPluginCall) { + guard let name = call.getString("name") else { + call.reject("name is required") + return + } + OneSignal.Session.addOutcome(name) + call.resolve() + } + + @objc func addUniqueOutcome(_ call: CAPPluginCall) { + guard let name = call.getString("name") else { + call.reject("name is required") + return + } + OneSignal.Session.addUniqueOutcome(name) + call.resolve() + } + + @objc func addOutcomeWithValue(_ call: CAPPluginCall) { + guard let name = call.getString("name") else { + call.reject("name is required") + return + } + let value = call.getFloat("value") ?? 0 + OneSignal.Session.addOutcome(withValue: name, value: NSNumber(value: value)) + call.resolve() + } + + // MARK: - Location + + @objc func requestLocationPermission(_ call: CAPPluginCall) { + OneSignal.Location.requestPermission() + call.resolve() + } + + @objc func setLocationShared(_ call: CAPPluginCall) { + let shared = call.getBool("shared") ?? false + OneSignal.Location.isShared = shared + call.resolve() + } + + @objc func isLocationShared(_ call: CAPPluginCall) { + call.resolve(["shared": OneSignal.Location.isShared]) + } + + // MARK: - Live Activities + + @objc func enterLiveActivity(_ call: CAPPluginCall) { + guard let activityId = call.getString("activityId"), + let token = call.getString("token") else { + call.reject("activityId and token are required") + return + } + OneSignal.LiveActivities.enter(activityId, withToken: token, withSuccess: { _ in + call.resolve() + }, withFailure: { error in + call.reject(error.localizedDescription) + }) + } + + @objc func exitLiveActivity(_ call: CAPPluginCall) { + guard let activityId = call.getString("activityId") else { + call.reject("activityId is required") + return + } + OneSignal.LiveActivities.exit(activityId, withSuccess: { _ in + call.resolve() + }, withFailure: { error in + call.reject(error.localizedDescription) + }) + } + + @objc func setPushToStartToken(_ call: CAPPluginCall) { + #if !targetEnvironment(macCatalyst) + guard let activityType = call.getString("activityType"), + let token = call.getString("token") else { + call.reject("activityType and token are required") + return + } + if #available(iOS 17.2, *) { + do { + try OneSignalLiveActivitiesManagerImpl.setPushToStartToken(activityType, withToken: token) + } catch { + call.reject("activityType must be the name of your ActivityAttributes struct") + return + } + } + #endif + call.resolve() + } + + @objc func removePushToStartToken(_ call: CAPPluginCall) { + #if !targetEnvironment(macCatalyst) + guard let activityType = call.getString("activityType") else { + call.reject("activityType is required") + return + } + if #available(iOS 17.2, *) { + do { + try OneSignalLiveActivitiesManagerImpl.removePushToStartToken(activityType) + } catch { + call.reject("activityType must be the name of your ActivityAttributes struct") + return + } + } + #endif + call.resolve() + } + + @objc func setupDefaultLiveActivity(_ call: CAPPluginCall) { + #if !targetEnvironment(macCatalyst) + if #available(iOS 16.1, *) { + var laOptions: LiveActivitySetupOptions? = nil + if let enablePushToStart = call.getBool("enablePushToStart"), + let enablePushToUpdate = call.getBool("enablePushToUpdate") { + laOptions = LiveActivitySetupOptions() + laOptions?.enablePushToStart = enablePushToStart + laOptions?.enablePushToUpdate = enablePushToUpdate + } + OneSignalLiveActivitiesManagerImpl.setupDefault(withOptions: laOptions) + } + #endif + call.resolve() + } + + @objc func startDefaultLiveActivity(_ call: CAPPluginCall) { + #if !targetEnvironment(macCatalyst) + guard let activityId = call.getString("activityId"), + let attributes = call.getObject("attributes"), + let content = call.getObject("content") else { + call.reject("activityId, attributes, and content are required") + return + } + if #available(iOS 16.1, *) { + OneSignalLiveActivitiesManagerImpl.startDefault(activityId, attributes: attributes, content: content) + } + #endif + call.resolve() + } + + // MARK: - Observer Callbacks + + public func onNotificationPermissionDidChange(_ permission: Bool) { + notifyListeners("permissionChange", data: ["permission": permission]) + } + + public func onPushSubscriptionDidChange(state: OSPushSubscriptionChangedState) { + var result: [String: Any] = [:] + result["previous"] = [ + "id": state.previous.id ?? NSNull(), + "token": state.previous.token ?? NSNull(), + "optedIn": state.previous.optedIn + ] + result["current"] = [ + "id": state.current.id ?? NSNull(), + "token": state.current.token ?? NSNull(), + "optedIn": state.current.optedIn + ] + notifyListeners("pushSubscriptionChange", data: result) + } + + public func onUserStateDidChange(state: OSUserChangedState) { + var result: [String: Any] = [:] + result["current"] = [ + "onesignalId": state.current.onesignalId ?? NSNull(), + "externalId": state.current.externalId ?? NSNull() + ] + notifyListeners("userStateChange", data: result) + } + + public func onWillDisplay(event: OSNotificationWillDisplayEvent) { + let notificationId = event.notification.notificationId + notificationWillDisplayCache[notificationId] = event + event.preventDefault() + if let data = event.notification.stringify().data(using: .utf8), + let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] { + notifyListeners("notificationForegroundWillDisplay", data: json) + } + } + + public func onClick(event: OSNotificationClickEvent) { + if bridge != nil { + notifyListeners("notificationClick", data: event.jsonRepresentation) + } else { + pendingClickEvent = event + } + } + + public func onWillDisplay(event: OSInAppMessageWillDisplayEvent) { + notifyListeners("inAppMessageWillDisplay", data: event.jsonRepresentation) + } + + public func onDidDisplay(event: OSInAppMessageDidDisplayEvent) { + notifyListeners("inAppMessageDidDisplay", data: event.jsonRepresentation) + } + + public func onWillDismiss(event: OSInAppMessageWillDismissEvent) { + notifyListeners("inAppMessageWillDismiss", data: event.jsonRepresentation) + } + + public func onDidDismiss(event: OSInAppMessageDidDismissEvent) { + notifyListeners("inAppMessageDidDismiss", data: event.jsonRepresentation) + } + + public func onClick(event: OSInAppMessageClickEvent) { + let urlTargetStr: String + switch event.result.urlTarget.rawValue { + case 0: urlTargetStr = "browser" + case 1: urlTargetStr = "webview" + case 2: urlTargetStr = "replacement" + default: urlTargetStr = "browser" + } + + var clickResult: [String: Any] = [ + "closingMessage": event.result.closingMessage + ] + if let actionId = event.result.actionId { + clickResult["actionId"] = actionId + } + if let url = event.result.url { + clickResult["url"] = url + } + clickResult["urlTarget"] = urlTargetStr + + notifyListeners("inAppMessageClick", data: [ + "message": event.message.jsonRepresentation, + "result": clickResult + ]) + } +} diff --git a/mocks/capacitor.ts b/mocks/capacitor.ts new file mode 100644 index 0000000..41e4a2c --- /dev/null +++ b/mocks/capacitor.ts @@ -0,0 +1,69 @@ +import { vi } from 'vitest'; + +import type { OneSignalCapacitorPlugin } from '../src/definitions'; + +export type MockPlugin = { + [K in keyof OneSignalCapacitorPlugin]: ReturnType; +} & OneSignalCapacitorPlugin; + +export function createMockPlugin(): MockPlugin { + return { + initialize: vi.fn().mockResolvedValue(undefined), + login: vi.fn().mockResolvedValue(undefined), + logout: vi.fn().mockResolvedValue(undefined), + setConsentRequired: vi.fn().mockResolvedValue(undefined), + setConsentGiven: vi.fn().mockResolvedValue(undefined), + setLogLevel: vi.fn().mockResolvedValue(undefined), + setAlertLevel: vi.fn().mockResolvedValue(undefined), + setLanguage: vi.fn().mockResolvedValue(undefined), + addAliases: vi.fn().mockResolvedValue(undefined), + removeAliases: vi.fn().mockResolvedValue(undefined), + addEmail: vi.fn().mockResolvedValue(undefined), + removeEmail: vi.fn().mockResolvedValue(undefined), + addSms: vi.fn().mockResolvedValue(undefined), + removeSms: vi.fn().mockResolvedValue(undefined), + addTags: vi.fn().mockResolvedValue(undefined), + removeTags: vi.fn().mockResolvedValue(undefined), + getTags: vi.fn().mockResolvedValue({ tags: {} }), + getOnesignalId: vi.fn().mockResolvedValue({ onesignalId: null }), + getExternalId: vi.fn().mockResolvedValue({ externalId: null }), + trackEvent: vi.fn().mockResolvedValue(undefined), + getPushSubscriptionId: vi.fn().mockResolvedValue({ id: null }), + getPushSubscriptionToken: vi.fn().mockResolvedValue({ token: null }), + getPushSubscriptionOptedIn: vi.fn().mockResolvedValue({ optedIn: false }), + optInPushSubscription: vi.fn().mockResolvedValue(undefined), + optOutPushSubscription: vi.fn().mockResolvedValue(undefined), + getPermission: vi.fn().mockResolvedValue({ permission: false }), + permissionNative: vi.fn().mockResolvedValue({ permission: 0 }), + requestPermission: vi.fn().mockResolvedValue({ permission: false }), + canRequestPermission: vi.fn().mockResolvedValue({ canRequest: true }), + registerForProvisionalAuthorization: vi + .fn() + .mockResolvedValue({ accepted: false }), + clearAllNotifications: vi.fn().mockResolvedValue(undefined), + removeNotification: vi.fn().mockResolvedValue(undefined), + removeGroupedNotifications: vi.fn().mockResolvedValue(undefined), + preventDefault: vi.fn().mockResolvedValue(undefined), + proceedWithWillDisplay: vi.fn().mockResolvedValue(undefined), + displayNotification: vi.fn().mockResolvedValue(undefined), + addTriggers: vi.fn().mockResolvedValue(undefined), + removeTriggers: vi.fn().mockResolvedValue(undefined), + clearTriggers: vi.fn().mockResolvedValue(undefined), + setPaused: vi.fn().mockResolvedValue(undefined), + isPaused: vi.fn().mockResolvedValue({ paused: false }), + addOutcome: vi.fn().mockResolvedValue(undefined), + addUniqueOutcome: vi.fn().mockResolvedValue(undefined), + addOutcomeWithValue: vi.fn().mockResolvedValue(undefined), + requestLocationPermission: vi.fn().mockResolvedValue(undefined), + setLocationShared: vi.fn().mockResolvedValue(undefined), + isLocationShared: vi.fn().mockResolvedValue({ shared: false }), + enterLiveActivity: vi.fn().mockResolvedValue(undefined), + exitLiveActivity: vi.fn().mockResolvedValue(undefined), + setPushToStartToken: vi.fn().mockResolvedValue(undefined), + removePushToStartToken: vi.fn().mockResolvedValue(undefined), + setupDefaultLiveActivity: vi.fn().mockResolvedValue(undefined), + startDefaultLiveActivity: vi.fn().mockResolvedValue(undefined), + addListener: vi.fn().mockResolvedValue({ remove: vi.fn() }), + removeAllListeners: vi.fn().mockResolvedValue(undefined), + }; +} diff --git a/mocks/constants.ts b/mocks/constants.ts new file mode 100644 index 0000000..8eb62bf --- /dev/null +++ b/mocks/constants.ts @@ -0,0 +1,6 @@ +export const APP_ID = 'test-app-id'; +export const SUB_TOKEN = 'test-token'; +export const SUB_ID = 'test-sub-id'; + +export const PREV_SUB_TOKEN = 'prev-sub-token'; +export const PREV_SUB_ID = 'prev-sub-id'; diff --git a/mocks/data.ts b/mocks/data.ts new file mode 100644 index 0000000..2428100 --- /dev/null +++ b/mocks/data.ts @@ -0,0 +1,35 @@ +import { OSNotification, type ReceivedEvent } from '../src/OSNotification'; +import type { NotificationClickEvent } from '../src/types/NotificationClicked'; +import { deepMerge } from './deepmerge'; + +export const mockNotification = ( + props: Partial = {}, +): OSNotification => { + return new OSNotification( + deepMerge( + { + body: 'Test Notification', + rawPayload: '{"test": "payload"}', + additionalData: {}, + notificationId: '123', + title: 'Test Title', + }, + props, + ), + ); +}; + +export const mockNotificationClickEvent = ( + props: Partial = {}, +): NotificationClickEvent => { + return deepMerge( + { + result: { + actionId: 'test', + url: 'https://test.com', + }, + notification: mockNotification(), + }, + props, + ); +}; diff --git a/mocks/deepmerge.ts b/mocks/deepmerge.ts new file mode 100644 index 0000000..9acf0a9 --- /dev/null +++ b/mocks/deepmerge.ts @@ -0,0 +1,39 @@ +export type DeepPartial = T extends Function + ? T + : T extends readonly (infer U)[] + ? ReadonlyArray> + : T extends (infer U)[] + ? DeepPartial[] + : T extends object + ? { [K in keyof T]?: DeepPartial } + : T; + +/** + * Recursively merges a patch object into a target object. + */ +export function deepMerge(target: T, patch: DeepPartial): T { + if (patch === undefined || patch === null) return target; + + const isObj = (v: unknown): v is Record => + Object.prototype.toString.call(v) === '[object Object]'; + + if (!isObj(target) || !isObj(patch)) { + return patch as unknown as T; + } + + const out: Record = { ...(target as any) }; + + for (const [k, v] of Object.entries(patch)) { + const tk = (target as any)[k]; + + if (Array.isArray(v)) { + out[k] = v.slice(); + } else if (isObj(v) && isObj(tk)) { + out[k] = deepMerge(tk, v as any); + } else if (v !== undefined) { + out[k] = v; + } + } + + return out as T; +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..244f473 --- /dev/null +++ b/package.json @@ -0,0 +1,74 @@ +{ + "version": "1.0.0", + "name": "onesignal-capacitor-plugin", + "files": [ + "dist", + "ios", + "android", + "Package.swift", + "OneSignalCapacitorPlugin.podspec" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/OneSignal/OneSignal-Capacitor-SDK.git" + }, + "main": "dist/plugin.cjs.js", + "module": "dist/esm/index.js", + "types": "dist/esm/index.d.ts", + "unpkg": "dist/plugin.js", + "scripts": { + "build": "vp pack", + "format": "vp fmt", + "format:check": "vp fmt --check", + "lint": "vp lint", + "lint:fix": "vp lint --fix", + "test": "vp test", + "test:coverage": "vp test run --coverage", + "check": "vp check", + "verify": "vp build && vp test run && vp lint", + "docgen": "docgen --api OneSignalCapacitorPlugin --output-readme README.md --output-json dist/docs.json" + }, + "peerDependencies": { + "@capacitor/core": "^7.0.0" + }, + "devDependencies": { + "@capacitor/core": "^7.0.0", + "@capacitor/docgen": "^0.2.2", + "@types/bun": "latest", + "@vitest/coverage-v8": "^4.0.2", + "happy-dom": "^20.0.8", + "typescript": "^5.9.3", + "vite": "^7.1.12", + "vite-plugin-dts": "^4.5.4", + "vitest": "^4.0.2" + }, + "capacitor": { + "ios": { + "src": "ios" + }, + "android": { + "src": "android" + } + }, + "bugs": { + "url": "https://github.com/OneSignal/OneSignal-Capacitor-SDK/issues" + }, + "description": "OneSignal is a high volume Push Notification service for mobile apps. This is the pure Capacitor plugin for OneSignal, providing push notifications, in-app messaging, and more.", + "homepage": "https://github.com/OneSignal/OneSignal-Capacitor-SDK#readme", + "keywords": [ + "capacitor", + "plugin", + "native", + "push", + "notification", + "push notification", + "push notifications", + "apns", + "fcm", + "onesignal" + ], + "license": "MIT", + "publishConfig": { + "access": "public" + } +} diff --git a/src/DebugNamespace.test.ts b/src/DebugNamespace.test.ts new file mode 100644 index 0000000..bf1b517 --- /dev/null +++ b/src/DebugNamespace.test.ts @@ -0,0 +1,56 @@ +import { describe, test, expect, beforeEach } from 'vitest'; + +import { createMockPlugin } from '../mocks/capacitor'; +import Debug, { LogLevel } from './DebugNamespace'; + +describe('Debug', () => { + let mockPlugin: ReturnType; + let debug: Debug; + + beforeEach(() => { + mockPlugin = createMockPlugin(); + debug = new Debug(mockPlugin); + }); + + test('should instantiate Debug class', () => { + expect(debug).toBeInstanceOf(Debug); + }); + + test.each([ + [LogLevel.None, 0], + [LogLevel.Fatal, 1], + [LogLevel.Error, 2], + [LogLevel.Warn, 3], + [LogLevel.Info, 4], + [LogLevel.Debug, 5], + [LogLevel.Verbose, 6], + ])( + 'should call plugin for setLogLevel with %s', + (logLevel, logLevelValue) => { + debug.setLogLevel(logLevel); + + expect(mockPlugin.setLogLevel).toHaveBeenCalledWith({ + logLevel: logLevelValue, + }); + }, + ); + + test.each([ + [LogLevel.None, 0], + [LogLevel.Fatal, 1], + [LogLevel.Error, 2], + [LogLevel.Warn, 3], + [LogLevel.Info, 4], + [LogLevel.Debug, 5], + [LogLevel.Verbose, 6], + ])( + 'should call plugin for setAlertLevel with %s', + (logLevel, logLevelValue) => { + debug.setAlertLevel(logLevel); + + expect(mockPlugin.setAlertLevel).toHaveBeenCalledWith({ + logLevel: logLevelValue, + }); + }, + ); +}); diff --git a/src/DebugNamespace.ts b/src/DebugNamespace.ts new file mode 100644 index 0000000..13714ca --- /dev/null +++ b/src/DebugNamespace.ts @@ -0,0 +1,39 @@ +import type { OneSignalCapacitorPlugin } from './definitions'; + +export const LogLevel = { + None: 0, + Fatal: 1, + Error: 2, + Warn: 3, + Info: 4, + Debug: 5, + Verbose: 6, +} as const; + +export type LogLevel = (typeof LogLevel)[keyof typeof LogLevel]; + +export default class Debug { + private _plugin: OneSignalCapacitorPlugin; + + constructor(plugin: OneSignalCapacitorPlugin) { + this._plugin = plugin; + } + + /** + * Enable logging to help debug if you run into an issue setting up OneSignal. + * @param {LogLevel} logLevel - Sets the logging level to print to the Android LogCat log or Xcode log. + * @returns void + */ + setLogLevel(logLevel: LogLevel): void { + void this._plugin.setLogLevel({ logLevel }); + } + + /** + * Enable logging to help debug if you run into an issue setting up OneSignal. + * @param {LogLevel} visualLogLevel - Sets the logging level to show as alert dialogs. + * @returns void + */ + setAlertLevel(visualLogLevel: LogLevel): void { + void this._plugin.setAlertLevel({ logLevel: visualLogLevel }); + } +} diff --git a/src/InAppMessagesNamespace.test.ts b/src/InAppMessagesNamespace.test.ts new file mode 100644 index 0000000..ae777df --- /dev/null +++ b/src/InAppMessagesNamespace.test.ts @@ -0,0 +1,211 @@ +import { describe, test, expect, vi, beforeEach } from 'vitest'; + +import { createMockPlugin } from '../mocks/capacitor'; +import * as helpers from './helpers'; +import InAppMessages from './InAppMessagesNamespace'; +import type { + InAppMessageClickEvent, + InAppMessageDidDismissEvent, + InAppMessageDidDisplayEvent, + InAppMessageWillDisplayEvent, +} from './types/InAppMessage'; + +describe('InAppMessages', () => { + let mockPlugin: ReturnType; + let inAppMessages: InAppMessages; + + beforeEach(() => { + mockPlugin = createMockPlugin(); + inAppMessages = new InAppMessages(mockPlugin); + }); + + test('should instantiate InAppMessages class', () => { + expect(inAppMessages).toBeInstanceOf(InAppMessages); + }); + + describe('addEventListener', () => { + const messageData = { + click: { + message: { messageId: 'test' }, + result: { closingMessage: true, actionId: 'test' }, + } satisfies InAppMessageClickEvent, + willDisplay: { + message: { messageId: 'test' }, + } satisfies InAppMessageWillDisplayEvent, + didDisplay: { + message: { messageId: 'test' }, + } satisfies InAppMessageDidDisplayEvent, + willDismiss: { + message: { messageId: 'test' }, + } satisfies InAppMessageDidDismissEvent, + didDismiss: { + message: { messageId: 'test' }, + } satisfies InAppMessageDidDismissEvent, + }; + + test.each([ + ['click', 'inAppMessageClick', messageData.click], + ['willDisplay', 'inAppMessageWillDisplay', messageData.willDisplay], + ['didDisplay', 'inAppMessageDidDisplay', messageData.didDisplay], + ['willDismiss', 'inAppMessageWillDismiss', messageData.willDismiss], + ['didDismiss', 'inAppMessageDidDismiss', messageData.didDismiss], + ] as const)( + 'should call addListener for %s event', + (eventType, listenerName, data) => { + const mockListener = vi.fn(); + inAppMessages.addEventListener(eventType, mockListener); + + expect(mockPlugin.addListener).toHaveBeenCalledWith( + listenerName, + expect.any(Function), + ); + + const callback = mockPlugin.addListener.mock.calls[0][1]; + callback(data); + expect(mockListener).toHaveBeenCalledWith(data); + }, + ); + + test('should not add listener for unknown event type', () => { + const mockListener = vi.fn(); + // @ts-expect-error - testing unknown event type + inAppMessages.addEventListener('unknown', mockListener); + + expect(mockPlugin.addListener).not.toHaveBeenCalled(); + }); + }); + + describe('removeEventListener', () => { + test.each([ + ['click'], + ['willDisplay'], + ['didDisplay'], + ['willDismiss'], + ['didDismiss'], + ] as const)('should remove %s event listener', (eventType) => { + const mockListener = vi.fn(); + inAppMessages.addEventListener(eventType, mockListener); + inAppMessages.removeEventListener(eventType, mockListener); + + expect(mockPlugin.addListener).toHaveBeenCalledTimes(1); + + const callback = mockPlugin.addListener.mock.calls[0][1]; + callback('some-data'); + expect(mockListener).not.toHaveBeenCalled(); + }); + + test('should not remove listener for unknown event type', () => { + vi.spyOn(helpers, 'removeListener').mockImplementation(() => {}); + const mockListener = vi.fn(); + + // @ts-expect-error - testing unknown event type + inAppMessages.removeEventListener('unknown', mockListener); + expect(helpers.removeListener).not.toHaveBeenCalled(); + }); + }); + + describe('addTrigger', () => { + test('should call plugin for addTrigger', () => { + inAppMessages.addTrigger('key', 'value'); + + expect(mockPlugin.addTriggers).toHaveBeenCalledWith({ + triggers: { key: 'value' }, + }); + }); + }); + + describe('addTriggers', () => { + test('should call plugin for addTriggers with string values', () => { + const triggers = { key1: 'value1', key2: 'value2' }; + inAppMessages.addTriggers(triggers); + + expect(mockPlugin.addTriggers).toHaveBeenCalledWith({ triggers }); + }); + + test('should convert non-string values to JSON strings', () => { + const triggers = { key1: 'value1', key2: 123, key3: true }; + + // @ts-expect-error - testing non-string values + inAppMessages.addTriggers(triggers); + + expect(mockPlugin.addTriggers).toHaveBeenCalledWith({ + triggers: { key1: 'value1', key2: '123', key3: 'true' }, + }); + }); + }); + + describe('removeTrigger', () => { + test('should call plugin for removeTrigger', () => { + inAppMessages.removeTrigger('key'); + + expect(mockPlugin.removeTriggers).toHaveBeenCalledWith({ + keys: ['key'], + }); + }); + }); + + describe('removeTriggers', () => { + test('should call plugin for removeTriggers with valid array', () => { + const keys = ['key1', 'key2']; + inAppMessages.removeTriggers(keys); + + expect(mockPlugin.removeTriggers).toHaveBeenCalledWith({ keys }); + }); + + test('should handle non-array input gracefully', () => { + const consoleSpy = vi + .spyOn(console, 'error') + .mockImplementation(() => {}); + + inAppMessages.removeTriggers('not-an-array' as any); + + expect(consoleSpy).toHaveBeenCalledWith( + 'OneSignal: removeTriggers: argument must be of type Array', + ); + + consoleSpy.mockRestore(); + }); + }); + + describe('clearTriggers', () => { + test('should call plugin for clearTriggers', () => { + inAppMessages.clearTriggers(); + + expect(mockPlugin.clearTriggers).toHaveBeenCalled(); + }); + }); + + describe('setPaused', () => { + test.each([[true], [false]])( + 'should call plugin for setPaused with %s', + (pauseValue) => { + inAppMessages.setPaused(pauseValue); + + expect(mockPlugin.setPaused).toHaveBeenCalledWith({ + pause: pauseValue, + }); + }, + ); + }); + + describe('getPaused', () => { + test('should return a Promise', () => { + const promise = inAppMessages.getPaused(); + expect(promise).toBeInstanceOf(Promise); + }); + + test('should resolve Promise when plugin succeeds', async () => { + mockPlugin.isPaused.mockResolvedValue({ paused: true }); + + const result = await inAppMessages.getPaused(); + expect(result).toBe(true); + }); + + test('should reject Promise when plugin fails', async () => { + const mockError = new Error('Test error'); + mockPlugin.isPaused.mockRejectedValue(mockError); + + await expect(inAppMessages.getPaused()).rejects.toThrow('Test error'); + }); + }); +}); diff --git a/src/InAppMessagesNamespace.ts b/src/InAppMessagesNamespace.ts new file mode 100644 index 0000000..7f0eced --- /dev/null +++ b/src/InAppMessagesNamespace.ts @@ -0,0 +1,216 @@ +import type { OneSignalCapacitorPlugin } from './definitions'; +import { removeListener } from './helpers'; +import type { + InAppMessageClickEvent, + InAppMessageDidDismissEvent, + InAppMessageDidDisplayEvent, + InAppMessageEventName, + InAppMessageEventTypeMap, + InAppMessageWillDismissEvent, + InAppMessageWillDisplayEvent, +} from './types/InAppMessage'; + +export default class InAppMessages { + private _plugin: OneSignalCapacitorPlugin; + private _inAppMessageClickListeners: (( + action: InAppMessageClickEvent, + ) => void)[] = []; + private _willDisplayInAppMessageListeners: (( + event: InAppMessageWillDisplayEvent, + ) => void)[] = []; + private _didDisplayInAppMessageListeners: (( + event: InAppMessageDidDisplayEvent, + ) => void)[] = []; + private _willDismissInAppMessageListeners: (( + event: InAppMessageWillDismissEvent, + ) => void)[] = []; + private _didDismissInAppMessageListeners: (( + event: InAppMessageDidDismissEvent, + ) => void)[] = []; + + constructor(plugin: OneSignalCapacitorPlugin) { + this._plugin = plugin; + } + + private _processFunctionList( + array: ((event: T) => void)[], + param: T, + ): void { + for (let i = 0; i < array.length; i++) { + array[i](param); + } + } + + /** + * Add event listeners for In-App Message click and/or lifecycle events. + * @param event + * @param listener + * @returns + */ + addEventListener( + event: K, + listener: (event: InAppMessageEventTypeMap[K]) => void, + ): void { + if (event === 'click') { + this._inAppMessageClickListeners.push( + listener as (event: InAppMessageClickEvent) => void, + ); + void this._plugin.addListener( + 'inAppMessageClick', + (json: InAppMessageClickEvent) => { + this._processFunctionList(this._inAppMessageClickListeners, json); + }, + ); + } else if (event === 'willDisplay') { + this._willDisplayInAppMessageListeners.push( + listener as (event: InAppMessageWillDisplayEvent) => void, + ); + void this._plugin.addListener( + 'inAppMessageWillDisplay', + (event: InAppMessageWillDisplayEvent) => { + this._processFunctionList( + this._willDisplayInAppMessageListeners, + event, + ); + }, + ); + } else if (event === 'didDisplay') { + this._didDisplayInAppMessageListeners.push( + listener as (event: InAppMessageDidDisplayEvent) => void, + ); + void this._plugin.addListener( + 'inAppMessageDidDisplay', + (event: InAppMessageDidDisplayEvent) => { + this._processFunctionList( + this._didDisplayInAppMessageListeners, + event, + ); + }, + ); + } else if (event === 'willDismiss') { + this._willDismissInAppMessageListeners.push( + listener as (event: InAppMessageWillDismissEvent) => void, + ); + void this._plugin.addListener( + 'inAppMessageWillDismiss', + (event: InAppMessageWillDismissEvent) => { + this._processFunctionList( + this._willDismissInAppMessageListeners, + event, + ); + }, + ); + } else if (event === 'didDismiss') { + this._didDismissInAppMessageListeners.push( + listener as (event: InAppMessageDidDismissEvent) => void, + ); + void this._plugin.addListener( + 'inAppMessageDidDismiss', + (event: InAppMessageDidDismissEvent) => { + this._processFunctionList( + this._didDismissInAppMessageListeners, + event, + ); + }, + ); + } + } + + /** + * Remove event listeners for In-App Message click and/or lifecycle events. + * @param event + * @param listener + * @returns + */ + removeEventListener( + event: K, + listener: (obj: InAppMessageEventTypeMap[K]) => void, + ): void { + if (event === 'click') { + removeListener(this._inAppMessageClickListeners, listener); + } else if (event === 'willDisplay') { + removeListener(this._willDisplayInAppMessageListeners, listener); + } else if (event === 'didDisplay') { + removeListener(this._didDisplayInAppMessageListeners, listener); + } else if (event === 'willDismiss') { + removeListener(this._willDismissInAppMessageListeners, listener); + } else if (event === 'didDismiss') { + removeListener(this._didDismissInAppMessageListeners, listener); + } + } + + /** + * Add a trigger for the current user. Triggers are currently explicitly used to determine whether a specific IAM should be displayed to the user. + * @param {string} key + * @param {string} value + * @returns void + */ + addTrigger(key: string, value: string): void { + this.addTriggers({ [key]: value }); + } + + /** + * Add multiple triggers for the current user. + * @param {[key: string]: string} triggers + * @returns void + */ + addTriggers(triggers: { [key: string]: string }): void { + Object.keys(triggers).forEach(function (key) { + if (typeof triggers[key] !== 'string') { + triggers[key] = JSON.stringify(triggers[key]); + } + }); + + void this._plugin.addTriggers({ triggers }); + } + + /** + * Remove the trigger with the provided key from the current user. + * @param {string} key + * @returns void + */ + removeTrigger(key: string): void { + this.removeTriggers([key]); + } + + /** + * Remove multiple triggers from the current user. + * @param {string[]} keys + * @returns void + */ + removeTriggers(keys: string[]): void { + if (!Array.isArray(keys)) { + console.error( + 'OneSignal: removeTriggers: argument must be of type Array', + ); + } + + void this._plugin.removeTriggers({ keys }); + } + + /** + * Clear all triggers from the current user. + * @returns void + */ + clearTriggers(): void { + void this._plugin.clearTriggers(); + } + + /** + * Set whether in-app messaging is currently paused. + * @param {boolean} pause + * @returns void + */ + setPaused(pause: boolean): void { + void this._plugin.setPaused({ pause }); + } + + /** + * Whether in-app messaging is currently paused. + * @returns {Promise} + */ + async getPaused(): Promise { + const result = await this._plugin.isPaused(); + return result.paused; + } +} diff --git a/src/LiveActivitiesNamespace.test.ts b/src/LiveActivitiesNamespace.test.ts new file mode 100644 index 0000000..a3af62f --- /dev/null +++ b/src/LiveActivitiesNamespace.test.ts @@ -0,0 +1,149 @@ +import { describe, test, expect, vi, beforeEach } from 'vitest'; + +import { SUB_TOKEN } from '../mocks/constants'; +import { createMockPlugin } from '../mocks/capacitor'; +import LiveActivities from './LiveActivitiesNamespace'; +import type { LiveActivitySetupOptions } from './types/LiveActivities'; + +const ACTIVITY_ID = 'test-activity-id'; +const ACTIVITY_TYPE = 'test-activity-type'; + +describe('LiveActivities', () => { + let mockPlugin: ReturnType; + let liveActivities: LiveActivities; + + beforeEach(() => { + mockPlugin = createMockPlugin(); + liveActivities = new LiveActivities(mockPlugin); + }); + + test('should instantiate LiveActivities class', () => { + expect(liveActivities).toBeInstanceOf(LiveActivities); + }); + + describe('enter', () => { + test('should call plugin for enter with required parameters', () => { + liveActivities.enter(ACTIVITY_ID, SUB_TOKEN); + + expect(mockPlugin.enterLiveActivity).toHaveBeenCalledWith({ + activityId: ACTIVITY_ID, + token: SUB_TOKEN, + }); + }); + + test('should call onSuccess callback', async () => { + const onSuccess = vi.fn(); + mockPlugin.enterLiveActivity.mockResolvedValue({ success: true }); + + liveActivities.enter(ACTIVITY_ID, SUB_TOKEN, onSuccess); + + await vi.waitFor(() => { + expect(onSuccess).toHaveBeenCalled(); + }); + }); + + test('should call onFailure callback on error', async () => { + const onFailure = vi.fn(); + mockPlugin.enterLiveActivity.mockRejectedValue( + new Error('Enter failed'), + ); + + liveActivities.enter(ACTIVITY_ID, SUB_TOKEN, undefined, onFailure); + + await vi.waitFor(() => { + expect(onFailure).toHaveBeenCalled(); + }); + }); + }); + + describe('exit', () => { + test('should call plugin for exit with required parameters', () => { + liveActivities.exit(ACTIVITY_ID); + + expect(mockPlugin.exitLiveActivity).toHaveBeenCalledWith({ + activityId: ACTIVITY_ID, + }); + }); + + test('should call onSuccess callback', async () => { + const onSuccess = vi.fn(); + mockPlugin.exitLiveActivity.mockResolvedValue({ success: true }); + + liveActivities.exit(ACTIVITY_ID, onSuccess); + + await vi.waitFor(() => { + expect(onSuccess).toHaveBeenCalled(); + }); + }); + + test('should call onFailure callback on error', async () => { + const onFailure = vi.fn(); + mockPlugin.exitLiveActivity.mockRejectedValue(new Error('Exit failed')); + + liveActivities.exit(ACTIVITY_ID, undefined, onFailure); + + await vi.waitFor(() => { + expect(onFailure).toHaveBeenCalled(); + }); + }); + }); + + describe('setPushToStartToken', () => { + test('should call plugin for setPushToStartToken', () => { + liveActivities.setPushToStartToken(ACTIVITY_TYPE, SUB_TOKEN); + + expect(mockPlugin.setPushToStartToken).toHaveBeenCalledWith({ + activityType: ACTIVITY_TYPE, + token: SUB_TOKEN, + }); + }); + }); + + describe('removePushToStartToken', () => { + test('should call plugin for removePushToStartToken', () => { + liveActivities.removePushToStartToken(ACTIVITY_TYPE); + + expect(mockPlugin.removePushToStartToken).toHaveBeenCalledWith({ + activityType: ACTIVITY_TYPE, + }); + }); + }); + + describe('setupDefault', () => { + test('should call plugin for setupDefault without options', () => { + liveActivities.setupDefault(); + + expect(mockPlugin.setupDefaultLiveActivity).toHaveBeenCalledWith( + undefined, + ); + }); + + test('should call plugin for setupDefault with options', () => { + const options: LiveActivitySetupOptions = { + enablePushToStart: true, + enablePushToUpdate: false, + }; + + liveActivities.setupDefault(options); + + expect(mockPlugin.setupDefaultLiveActivity).toHaveBeenCalledWith( + options, + ); + }); + }); + + describe('startDefault', () => { + test('should call plugin for startDefault', () => { + const attributes = { key1: 'value1', key2: 'value2' }; + const content = { title: 'Test Title', message: 'Test Message' }; + + liveActivities.startDefault(ACTIVITY_ID, attributes, content); + + expect(mockPlugin.startDefaultLiveActivity).toHaveBeenCalledWith({ + activityId: ACTIVITY_ID, + attributes, + content, + }); + }); + }); +}); diff --git a/src/LiveActivitiesNamespace.ts b/src/LiveActivitiesNamespace.ts new file mode 100644 index 0000000..c4ca170 --- /dev/null +++ b/src/LiveActivitiesNamespace.ts @@ -0,0 +1,103 @@ +import type { OneSignalCapacitorPlugin } from './definitions'; +import type { LiveActivitySetupOptions } from './types/LiveActivities'; + +export default class LiveActivities { + private _plugin: OneSignalCapacitorPlugin; + + constructor(plugin: OneSignalCapacitorPlugin) { + this._plugin = plugin; + } + + /** + * Enter a live activity + * @param {string} activityId + * @param {string} token + * @param {Function} onSuccess + * @param {Function} onFailure + * @returns void + */ + enter( + activityId: string, + token: string, + onSuccess?: (data: unknown) => void, + onFailure?: (data: unknown) => void, + ): void { + this._plugin + .enterLiveActivity({ activityId, token }) + .then((result) => { + onSuccess?.(result); + }) + .catch((error: unknown) => { + onFailure?.(error); + }); + } + + /** + * Exit a live activity + * @param {string} activityId + * @param {Function} onSuccess + * @param {Function} onFailure + * @returns void + */ + exit( + activityId: string, + onSuccess?: (data: unknown) => void, + onFailure?: (data: unknown) => void, + ): void { + this._plugin + .exitLiveActivity({ activityId }) + .then((result) => { + onSuccess?.(result); + }) + .catch((error: unknown) => { + onFailure?.(error); + }); + } + + /** + * Indicate this device is capable of receiving pushToStart live activities for the + * `activityType`. Only applies to iOS. + * @param {string} activityType + * @param {string} token + */ + setPushToStartToken(activityType: string, token: string) { + void this._plugin.setPushToStartToken({ activityType, token }); + } + + /** + * Indicate this device is no longer capable of receiving pushToStart live activities + * for the `activityType`. Only applies to iOS. + * @param {string} activityType + */ + removePushToStartToken(activityType: string) { + void this._plugin.removePushToStartToken({ activityType }); + } + + /** + * Enable the OneSignalSDK to setup the default `DefaultLiveActivityAttributes` structure. + * Only applies to iOS. + * @param {LiveActivitySetupOptions} options + */ + setupDefault(options?: LiveActivitySetupOptions) { + void this._plugin.setupDefaultLiveActivity(options); + } + + /** + * Start a new LiveActivity that is modelled by the default `DefaultLiveActivityAttributes` + * structure. Only applies to iOS. + * @param {string} activityId + * @param {object} attributes + * @param {object} content + */ + startDefault( + activityId: string, + attributes: Record, + content: Record, + ) { + void this._plugin.startDefaultLiveActivity({ + activityId, + attributes, + content, + }); + } +} diff --git a/src/LocationNamespace.test.ts b/src/LocationNamespace.test.ts new file mode 100644 index 0000000..7f6b527 --- /dev/null +++ b/src/LocationNamespace.test.ts @@ -0,0 +1,60 @@ +import { describe, test, expect, beforeEach } from 'vitest'; + +import { createMockPlugin } from '../mocks/capacitor'; +import Location from './LocationNamespace'; + +describe('Location', () => { + let mockPlugin: ReturnType; + let location: Location; + + beforeEach(() => { + mockPlugin = createMockPlugin(); + location = new Location(mockPlugin); + }); + + test('should instantiate Location class', () => { + expect(location).toBeInstanceOf(Location); + }); + + describe('requestPermission', () => { + test('should call plugin for requestPermission', () => { + location.requestPermission(); + + expect(mockPlugin.requestLocationPermission).toHaveBeenCalled(); + }); + }); + + describe('setShared', () => { + test.each([[true], [false]])( + 'should call plugin for setShared with %s', + (sharedValue) => { + location.setShared(sharedValue); + + expect(mockPlugin.setLocationShared).toHaveBeenCalledWith({ + shared: sharedValue, + }); + }, + ); + }); + + describe('isShared', () => { + test('should return a Promise', () => { + const promise = location.isShared(); + expect(promise).toBeInstanceOf(Promise); + }); + + test('should resolve Promise when plugin succeeds', async () => { + mockPlugin.isLocationShared.mockResolvedValue({ shared: true }); + + const result = await location.isShared(); + expect(result).toBe(true); + }); + + test('should reject Promise when plugin fails', async () => { + const mockError = new Error('Location permission denied'); + mockPlugin.isLocationShared.mockRejectedValue(mockError); + + await expect(location.isShared()).rejects.toThrow(mockError.message); + }); + }); +}); diff --git a/src/LocationNamespace.ts b/src/LocationNamespace.ts new file mode 100644 index 0000000..e4f113c --- /dev/null +++ b/src/LocationNamespace.ts @@ -0,0 +1,35 @@ +import type { OneSignalCapacitorPlugin } from './definitions'; + +export default class Location { + private _plugin: OneSignalCapacitorPlugin; + + constructor(plugin: OneSignalCapacitorPlugin) { + this._plugin = plugin; + } + + /** + * Prompts the user for location permissions to allow geotagging from the OneSignal dashboard. + * @returns void + */ + requestPermission(): void { + void this._plugin.requestLocationPermission(); + } + + /** + * Disable or enable location collection (defaults to enabled if your app has location permission). + * @param {boolean} shared + * @returns void + */ + setShared(shared: boolean): void { + void this._plugin.setLocationShared({ shared }); + } + + /** + * Whether location is currently shared with OneSignal. + * @returns {Promise} + */ + async isShared(): Promise { + const result = await this._plugin.isLocationShared(); + return result.shared; + } +} diff --git a/src/NotificationReceivedEvent.test.ts b/src/NotificationReceivedEvent.test.ts new file mode 100644 index 0000000..ec5737e --- /dev/null +++ b/src/NotificationReceivedEvent.test.ts @@ -0,0 +1,75 @@ +import { describe, test, expect, beforeEach } from 'vitest'; + +import { createMockPlugin } from '../mocks/capacitor'; +import { mockNotification } from '../mocks/data'; +import { + NotificationWillDisplayEvent, + _setNotificationEventPlugin, +} from './NotificationReceivedEvent'; +import { OSNotification } from './OSNotification'; + +describe('NotificationWillDisplayEvent', () => { + let notificationData = mockNotification(); + let notificationEvent: NotificationWillDisplayEvent; + + beforeEach(() => { + const mockPlugin = createMockPlugin(); + _setNotificationEventPlugin(mockPlugin); + notificationEvent = new NotificationWillDisplayEvent(notificationData); + }); + + test('should instantiate NotificationWillDisplayEvent class', () => { + expect(notificationEvent).toBeInstanceOf(NotificationWillDisplayEvent); + }); + + test('should create OSNotification instance in constructor', () => { + const notification = notificationEvent.getNotification(); + + expect(notification).toBeInstanceOf(OSNotification); + expect(notification.notificationId).toBe(notificationData.notificationId); + expect(notification.body).toBe(notificationData.body); + expect(notification.title).toBe(notificationData.title); + }); + + describe('preventDefault', () => { + test('should call plugin for preventDefault with default (false)', () => { + const mockPlugin = createMockPlugin(); + _setNotificationEventPlugin(mockPlugin); + const event = new NotificationWillDisplayEvent(notificationData); + + event.preventDefault(); + + expect(mockPlugin.preventDefault).toHaveBeenCalledWith({ + notificationId: notificationData.notificationId, + discard: false, + }); + }); + + test('should call plugin for preventDefault with discard true', () => { + const mockPlugin = createMockPlugin(); + _setNotificationEventPlugin(mockPlugin); + const event = new NotificationWillDisplayEvent(notificationData); + + event.preventDefault(true); + + expect(mockPlugin.preventDefault).toHaveBeenCalledWith({ + notificationId: notificationData.notificationId, + discard: true, + }); + }); + }); + + describe('getNotification', () => { + test('should return the OSNotification instance', () => { + const notification1 = notificationEvent.getNotification(); + const notification2 = notificationEvent.getNotification(); + + expect(notification1).toBeInstanceOf(OSNotification); + expect(notification1.notificationId).toBe( + notificationData.notificationId, + ); + + expect(notification1).toBe(notification2); + }); + }); +}); diff --git a/src/NotificationReceivedEvent.ts b/src/NotificationReceivedEvent.ts new file mode 100644 index 0000000..4a12a77 --- /dev/null +++ b/src/NotificationReceivedEvent.ts @@ -0,0 +1,39 @@ +import type { OneSignalCapacitorPlugin } from './definitions'; +import { OSNotification } from './OSNotification'; + +let _pluginRef: OneSignalCapacitorPlugin | undefined; + +export function _setNotificationEventPlugin( + plugin: OneSignalCapacitorPlugin, +): void { + _pluginRef = plugin; +} + +export class NotificationWillDisplayEvent { + private notification: OSNotification; + + constructor(displayEvent: OSNotification) { + this.notification = new OSNotification(displayEvent); + } + + /** + * Call this to prevent OneSignal from displaying the notification automatically. + * This method can be called up to two times with false and then true, if processing time is needed. + * Typically this is only possible within a short + * time-frame (~30 seconds) after the notification is received on the device. + * @param discard an [preventDefault] set to true to dismiss the notification with no + * possibility of displaying it in the future. + */ + preventDefault(discard: boolean = false): void { + if (_pluginRef) { + void _pluginRef.preventDefault({ + notificationId: this.notification.notificationId, + discard, + }); + } + } + + getNotification(): OSNotification { + return this.notification; + } +} diff --git a/src/NotificationsNamespace.test.ts b/src/NotificationsNamespace.test.ts new file mode 100644 index 0000000..0bbf71e --- /dev/null +++ b/src/NotificationsNamespace.test.ts @@ -0,0 +1,312 @@ +import { describe, test, expect, vi, beforeEach } from 'vitest'; + +import { createMockPlugin } from '../mocks/capacitor'; +import { mockNotification, mockNotificationClickEvent } from '../mocks/data'; +import { NotificationWillDisplayEvent } from './NotificationReceivedEvent'; +import * as helpers from './helpers'; +import Notifications, { + OSNotificationPermission, +} from './NotificationsNamespace'; + +describe('Notifications', () => { + let mockPlugin: ReturnType; + let notifications: Notifications; + + beforeEach(() => { + mockPlugin = createMockPlugin(); + notifications = new Notifications(mockPlugin); + }); + + test('should instantiate Notifications class', () => { + expect(notifications).toBeInstanceOf(Notifications); + }); + + describe('getPermissionAsync', () => { + test('should return a Promise', () => { + mockPlugin.getPermission.mockResolvedValue({ permission: true }); + + const promise = notifications.getPermissionAsync(); + + expect(promise).toBeInstanceOf(Promise); + return promise; + }); + + test('should resolve with boolean value', async () => { + mockPlugin.getPermission.mockResolvedValue({ permission: true }); + + const result = await notifications.getPermissionAsync(); + + expect(result).toBe(true); + }); + + test('should reject Promise when plugin fails', async () => { + const mockError = new Error('Permission check failed'); + mockPlugin.getPermission.mockRejectedValue(mockError); + + await expect(notifications.getPermissionAsync()).rejects.toThrow( + mockError.message, + ); + }); + }); + + describe('permissionNative', () => { + test('should return a Promise', () => { + mockPlugin.permissionNative.mockResolvedValue({ + permission: OSNotificationPermission.Authorized, + }); + + const promise = notifications.permissionNative(); + + expect(promise).toBeInstanceOf(Promise); + return promise; + }); + + test('should resolve with OSNotificationPermission value', async () => { + mockPlugin.permissionNative.mockResolvedValue({ + permission: OSNotificationPermission.Authorized, + }); + + const result = await notifications.permissionNative(); + + expect(result).toBe(OSNotificationPermission.Authorized); + }); + + test('should reject Promise when plugin fails', async () => { + const mockError = new Error('Permission check failed'); + mockPlugin.permissionNative.mockRejectedValue(mockError); + + await expect(notifications.permissionNative()).rejects.toThrow( + mockError.message, + ); + }); + }); + + describe('requestPermission', () => { + test.each([[true], [false]])( + 'should call plugin for requestPermission with fallbackToSettings %s', + async (fallback) => { + mockPlugin.requestPermission.mockResolvedValue({ permission: true }); + + await notifications.requestPermission(fallback); + + expect(mockPlugin.requestPermission).toHaveBeenCalledWith({ + fallbackToSettings: fallback, + }); + }, + ); + + test('should use default fallbackToSettings of false when not provided', async () => { + mockPlugin.requestPermission.mockResolvedValue({ permission: true }); + + await notifications.requestPermission(); + + expect(mockPlugin.requestPermission).toHaveBeenCalledWith({ + fallbackToSettings: false, + }); + }); + }); + + describe('canRequestPermission', () => { + test('should return a Promise', () => { + mockPlugin.canRequestPermission.mockResolvedValue({ canRequest: true }); + + const promise = notifications.canRequestPermission(); + + expect(promise).toBeInstanceOf(Promise); + return promise; + }); + + test('should resolve with boolean value', async () => { + mockPlugin.canRequestPermission.mockResolvedValue({ canRequest: false }); + + const result = await notifications.canRequestPermission(); + + expect(result).toBe(false); + }); + + test('should reject Promise when plugin fails', async () => { + const mockError = new Error('Permission check failed'); + mockPlugin.canRequestPermission.mockRejectedValue(mockError); + + await expect(notifications.canRequestPermission()).rejects.toThrow( + mockError.message, + ); + }); + }); + + describe('registerForProvisionalAuthorization', () => { + test('should call plugin', () => { + notifications.registerForProvisionalAuthorization(); + + expect( + mockPlugin.registerForProvisionalAuthorization, + ).toHaveBeenCalled(); + }); + + test('should call handler with result', async () => { + mockPlugin.registerForProvisionalAuthorization.mockResolvedValue({ + accepted: true, + }); + const handler = vi.fn(); + notifications.registerForProvisionalAuthorization(handler); + + await vi.waitFor(() => { + expect(handler).toHaveBeenCalledWith(true); + }); + }); + }); + + describe('addEventListener', () => { + test('should call addListener for click event', () => { + const mockListener = vi.fn(); + notifications.addEventListener('click', mockListener); + + expect(mockPlugin.addListener).toHaveBeenCalledWith( + 'notificationClick', + expect.any(Function), + ); + }); + + test('should call all click listeners when event fires', () => { + const mockListener1 = vi.fn(); + const mockListener2 = vi.fn(); + notifications.addEventListener('click', mockListener1); + notifications.addEventListener('click', mockListener2); + + const clickData = mockNotificationClickEvent(); + const callback = mockPlugin.addListener.mock.calls[0][1]; + callback(clickData); + + expect(mockListener1).toHaveBeenCalledWith(clickData); + expect(mockListener2).toHaveBeenCalledWith(clickData); + }); + + test('should call addListener for foregroundWillDisplay event', () => { + const mockListener = vi.fn(); + notifications.addEventListener('foregroundWillDisplay', mockListener); + + expect(mockPlugin.addListener).toHaveBeenCalledWith( + 'notificationForegroundWillDisplay', + expect.any(Function), + ); + }); + + test('should call foregroundWillDisplay listeners and proceedWithWillDisplay', () => { + const mockListener = vi.fn(); + notifications.addEventListener('foregroundWillDisplay', mockListener); + + const notificationData = mockNotification(); + const callback = mockPlugin.addListener.mock.calls[0][1]; + callback(notificationData); + + expect(mockListener).toHaveBeenCalledWith( + expect.any(NotificationWillDisplayEvent), + ); + expect(mockPlugin.proceedWithWillDisplay).toHaveBeenCalledWith({ + notificationId: notificationData.notificationId, + }); + }); + + test('should call addListener for permissionChange event', () => { + const mockListener = vi.fn(); + notifications.addEventListener('permissionChange', mockListener); + + expect(mockPlugin.addListener).toHaveBeenCalledWith( + 'permissionChange', + expect.any(Function), + ); + }); + + test('should call all permissionChange listeners when event fires', () => { + const mockListener1 = vi.fn(); + const mockListener2 = vi.fn(); + notifications.addEventListener('permissionChange', mockListener1); + notifications.addEventListener('permissionChange', mockListener2); + + const callback = mockPlugin.addListener.mock.calls[0][1]; + callback({ permission: true }); + + expect(mockListener1).toHaveBeenCalledWith(true); + expect(mockListener2).toHaveBeenCalledWith(true); + }); + + test('should not add listener for unknown event type', () => { + const mockListener = vi.fn(); + // @ts-expect-error - testing unknown event type + notifications.addEventListener('unknown', mockListener); + + expect(mockPlugin.addListener).not.toHaveBeenCalled(); + }); + }); + + describe('removeEventListener', () => { + test.each([ + ['click'], + ['foregroundWillDisplay'], + ['permissionChange'], + ] as const)('should remove %s event listener', (eventType) => { + const mockListener = vi.fn(); + notifications.addEventListener(eventType, mockListener); + notifications.removeEventListener(eventType, mockListener); + + const callback = mockPlugin.addListener.mock.calls[0][1]; + callback('some-data'); + expect(mockListener).not.toHaveBeenCalled(); + }); + + test('should not remove listener for unknown event type', () => { + vi.spyOn(helpers, 'removeListener').mockImplementation(() => {}); + const mockListener = vi.fn(); + // @ts-expect-error - testing unknown event type + notifications.removeEventListener('unknown', mockListener); + + expect(helpers.removeListener).not.toHaveBeenCalled(); + }); + }); + + describe('clearAll', () => { + test('should call plugin for clearAll', () => { + notifications.clearAll(); + + expect(mockPlugin.clearAllNotifications).toHaveBeenCalled(); + }); + }); + + describe('removeNotification', () => { + test('should call plugin for removeNotification', () => { + const notificationId = 123; + notifications.removeNotification(notificationId); + + expect(mockPlugin.removeNotification).toHaveBeenCalledWith({ + id: notificationId, + }); + }); + }); + + describe('removeGroupedNotifications', () => { + test('should call plugin for removeGroupedNotifications', () => { + const groupId = 'test-group-id'; + notifications.removeGroupedNotifications(groupId); + + expect(mockPlugin.removeGroupedNotifications).toHaveBeenCalledWith({ + id: groupId, + }); + }); + }); + + describe('hasPermission (deprecated)', () => { + test('should return false when _permission is undefined', () => { + const result = notifications.hasPermission(); + expect(result).toBe(false); + }); + + test('should return true when permission is set via getPermission', async () => { + mockPlugin.getPermission.mockResolvedValue({ permission: true }); + notifications._setPropertyAndObserver(); + + await vi.waitFor(() => { + expect(notifications.hasPermission()).toBe(true); + }); + }); + }); +}); diff --git a/src/NotificationsNamespace.ts b/src/NotificationsNamespace.ts new file mode 100644 index 0000000..d08aa73 --- /dev/null +++ b/src/NotificationsNamespace.ts @@ -0,0 +1,232 @@ +import type { OneSignalCapacitorPlugin } from './definitions'; +import { removeListener } from './helpers'; +import { NotificationWillDisplayEvent } from './NotificationReceivedEvent'; +import type { OSNotification } from './OSNotification'; +import type { + NotificationClickEvent, + NotificationEventName, + NotificationEventTypeMap, +} from './types/NotificationClicked'; + +export const OSNotificationPermission = { + NotDetermined: 0, + Denied: 1, + Authorized: 2, + Provisional: 3, + Ephemeral: 4, +} as const; + +export type OSNotificationPermission = + (typeof OSNotificationPermission)[keyof typeof OSNotificationPermission]; + +export default class Notifications { + private _plugin: OneSignalCapacitorPlugin; + private _permissionObserverList: ((event: boolean) => void)[] = []; + private _notificationClickedListeners: (( + event: NotificationClickEvent, + ) => void)[] = []; + private _notificationWillDisplayListeners: (( + event: NotificationWillDisplayEvent, + ) => void)[] = []; + + constructor(plugin: OneSignalCapacitorPlugin) { + this._plugin = plugin; + } + + private _processFunctionList( + array: ((event: T) => void)[], + param: T, + ): void { + for (let i = 0; i < array.length; i++) { + array[i](param); + } + } + + private _permission?: boolean; + + /** + * Sets initial permission value and adds observer for changes. + * This internal method is kept to support the deprecated method {@link hasPermission}. + */ + _setPropertyAndObserver(): void { + void this._plugin.getPermission().then((result) => { + this._permission = result.permission; + }); + + this.addEventListener('permissionChange', (result) => { + this._permission = result; + }); + } + + /** + * @deprecated This method is deprecated. It has been replaced by {@link getPermissionAsync}. + */ + hasPermission(): boolean { + return this._permission || false; + } + + /** + * Whether this app has push notification permission. Returns true if the user has accepted permissions, + * or if the app has ephemeral or provisional permission. + */ + async getPermissionAsync(): Promise { + const result = await this._plugin.getPermission(); + return result.permission; + } + + /** + * iOS Only. + * Returns the native permission of the device. + * @returns {Promise} + */ + async permissionNative(): Promise { + const result = await this._plugin.permissionNative(); + return result.permission; + } + + /** + * Prompt the user for permission to receive push notifications. + * Use the fallbackToSettings parameter to prompt to open the settings app if a user has already declined push permissions. + * @param {boolean} fallbackToSettings + * @returns {Promise} + */ + async requestPermission(fallbackToSettings?: boolean): Promise { + const fallback = fallbackToSettings ?? false; + const result = await this._plugin.requestPermission({ + fallbackToSettings: fallback, + }); + return result.permission; + } + + /** + * Whether attempting to request notification permission will show a prompt. + * Returns true if the device has not been prompted for push notification permission already. + * @returns {Promise} + */ + async canRequestPermission(): Promise { + const result = await this._plugin.canRequestPermission(); + return result.canRequest; + } + + /** + * iOS Only. + * Instead of having to prompt the user for permission to send them push notifications, + * your app can request provisional authorization. + * @param {(response: boolean)=>void} handler + * @returns void + */ + registerForProvisionalAuthorization( + handler?: (response: boolean) => void, + ): void { + void this._plugin + .registerForProvisionalAuthorization() + .then((result) => { + handler?.(result.accepted); + }); + } + + /** + * Add listeners for notification events. + * @param event + * @param listener + * @returns + */ + addEventListener( + event: K, + listener: (event: NotificationEventTypeMap[K]) => void, + ): void { + if (event === 'click') { + this._notificationClickedListeners.push( + listener as (event: NotificationClickEvent) => void, + ); + void this._plugin.addListener( + 'notificationClick', + (json: NotificationClickEvent) => { + this._processFunctionList(this._notificationClickedListeners, json); + }, + ); + } else if (event === 'foregroundWillDisplay') { + this._notificationWillDisplayListeners.push( + listener as (event: NotificationWillDisplayEvent) => void, + ); + void this._plugin.addListener( + 'notificationForegroundWillDisplay', + (notification: OSNotification) => { + this._notificationWillDisplayListeners.forEach((listener) => { + listener(new NotificationWillDisplayEvent(notification)); + }); + void this._plugin.proceedWithWillDisplay({ + notificationId: notification.notificationId, + }); + }, + ); + } else if (event === 'permissionChange') { + this._permissionObserverList.push(listener as (event: boolean) => void); + void this._plugin.addListener( + 'permissionChange', + (state: { permission: boolean }) => { + this._processFunctionList( + this._permissionObserverList, + state.permission, + ); + }, + ); + } + } + + /** + * Remove listeners for notification events. + * @param event + * @param listener + * @returns + */ + removeEventListener( + event: K, + listener: (obj: NotificationEventTypeMap[K]) => void, + ): void { + if (event === 'click') { + removeListener( + this._notificationClickedListeners, + listener as (event: NotificationClickEvent) => void, + ); + } else if (event === 'foregroundWillDisplay') { + removeListener( + this._notificationWillDisplayListeners, + listener as (event: NotificationWillDisplayEvent) => void, + ); + } else if (event === 'permissionChange') { + removeListener( + this._permissionObserverList, + listener as (event: boolean) => void, + ); + } + } + + /** + * Removes all OneSignal notifications. + * @returns void + */ + clearAll(): void { + void this._plugin.clearAllNotifications(); + } + + /** + * Android only. + * Cancels a single OneSignal notification based on its Android notification integer ID. + * @param {number} id - notification id to cancel + * @returns void + */ + removeNotification(id: number): void { + void this._plugin.removeNotification({ id }); + } + + /** + * Android only. + * Cancels a group of OneSignal notifications with the provided group key. + * @param {string} id - notification group id to cancel + * @returns void + */ + removeGroupedNotifications(id: string): void { + void this._plugin.removeGroupedNotifications({ id }); + } +} diff --git a/src/OSNotification.test.ts b/src/OSNotification.test.ts new file mode 100644 index 0000000..dc8e5c0 --- /dev/null +++ b/src/OSNotification.test.ts @@ -0,0 +1,120 @@ +import { describe, test, expect, beforeEach } from 'vitest'; + +import { createMockPlugin } from '../mocks/capacitor'; +import { OSNotification, _setOSNotificationPlugin, type ReceivedEvent } from './OSNotification'; + +type AllProperties = Required>; + +describe('OSNotification', () => { + beforeEach(() => { + const mockPlugin = createMockPlugin(); + _setOSNotificationPlugin(mockPlugin); + }); + + describe('constructor', () => { + const props = { + body: 'Some body', + sound: 'Some sound', + title: 'Some title', + launchURL: 'Some launch URL', + rawPayload: '{"test": "data"}', + actionButtons: [{ id: 'Some button', text: 'Some text' }], + additionalData: { custom: 'value' }, + notificationId: 'test-123', + // android only + groupKey: 'test-group', + groupMessage: 'Some group message', + groupedNotifications: [ + { id: '1', body: 'Some body' }, + { id: '2', body: 'Some body' }, + ], + ledColor: 'FFFF0000', + priority: 0, + smallIcon: 'ic_stat_onesignal_default', + largeIcon: 'ic_large_icon', + bigPicture: 'https://example.com/image.png', + collapseId: 'collapse-123', + fromProjectNumber: '123456789', + smallIconAccentColor: 'FF0000FF', + lockScreenVisibility: '1', + androidNotificationId: 987654321, + // ios only + badge: '5', + badgeIncrement: '1', + category: 'MESSAGE_CATEGORY', + threadId: 'thread-123', + subtitle: 'Some subtitle', + templateId: 'template-456', + templateName: 'Welcome Template', + attachments: { id: 'image.jpg', url: 'https://example.com/image.jpg' }, + mutableContent: true, + contentAvailable: '1', + relevanceScore: 0.75, + interruptionLevel: 'time-sensitive', + } satisfies AllProperties; + + describe('rawPayload', () => { + test('should parse rawPayload when it is a string', () => { + const notification = new OSNotification({ + ...props, + rawPayload: JSON.stringify({ key: 'value', nested: { data: true } }), + }); + + expect(notification.rawPayload).toEqual({ + key: 'value', + nested: { data: true }, + }); + }); + + test('should keep rawPayload as-is when it is already an object', () => { + const payloadObj = { key: 'value', nested: { data: true } }; + const notification = new OSNotification({ + ...props, + rawPayload: payloadObj, + }); + + expect(notification.rawPayload).toEqual(payloadObj); + }); + }); + + test('should instantiate OSNotification with required properties', () => { + const notification = new OSNotification({ + notificationId: props.notificationId, + body: props.body, + rawPayload: props.rawPayload, + additionalData: props.additionalData, + }); + + expect(notification).toBeInstanceOf(OSNotification); + expect(notification.notificationId).toBe(props.notificationId); + expect(notification.body).toBe(props.body); + expect(notification.additionalData).toEqual(props.additionalData); + expect(notification.rawPayload).toEqual(JSON.parse(props.rawPayload)); + }); + + test('should handle all properties', () => { + const notification = new OSNotification(props); + + const { rawPayload, ...rest } = props; + + for (const [key, value] of Object.entries(rest)) { + expect(notification[key as keyof OSNotification]).toBe(value); + } + + expect(notification.rawPayload).toEqual(JSON.parse(rawPayload)); + }); + + describe('display', () => { + test('should call plugin with correct parameters', () => { + const mockPlugin = createMockPlugin(); + _setOSNotificationPlugin(mockPlugin); + const notification = new OSNotification(props); + notification.display(); + + expect(mockPlugin.displayNotification).toHaveBeenCalledWith({ + notificationId: props.notificationId, + }); + }); + }); + }); +}); diff --git a/src/OSNotification.ts b/src/OSNotification.ts new file mode 100644 index 0000000..8fc1950 --- /dev/null +++ b/src/OSNotification.ts @@ -0,0 +1,165 @@ +import type { OneSignalCapacitorPlugin } from './definitions'; + +export type ReceivedEvent = Omit & { + rawPayload: string | object; +}; + +let _pluginRef: OneSignalCapacitorPlugin | undefined; + +export function _setOSNotificationPlugin( + plugin: OneSignalCapacitorPlugin, +): void { + _pluginRef = plugin; +} + +export class OSNotification { + body: string; + sound?: string; + title?: string; + launchURL?: string; + rawPayload: object; + actionButtons?: object[]; + additionalData: object; + notificationId: string; + // android only + groupKey?: string; + groupMessage?: string; + groupedNotifications?: object[]; + ledColor?: string; + priority?: number; + smallIcon?: string; + largeIcon?: string; + bigPicture?: string; + collapseId?: string; + fromProjectNumber?: string; + smallIconAccentColor?: string; + lockScreenVisibility?: string; + androidNotificationId?: number; + // ios only + badge?: string; + badgeIncrement?: string; + category?: string; + threadId?: string; + subtitle?: string; + templateId?: string; + templateName?: string; + attachments?: object; + mutableContent?: boolean; + contentAvailable?: string; + relevanceScore?: number; + interruptionLevel?: string; + + constructor(receivedEvent: ReceivedEvent) { + this.notificationId = receivedEvent.notificationId; + this.body = receivedEvent.body; + this.title = receivedEvent.title; + this.additionalData = receivedEvent.additionalData; + + if (typeof receivedEvent.rawPayload === 'string') { + this.rawPayload = JSON.parse(receivedEvent.rawPayload); + } else { + this.rawPayload = receivedEvent.rawPayload; + } + + this.launchURL = receivedEvent.launchURL; + this.sound = receivedEvent.sound; + + if (receivedEvent.actionButtons) { + this.actionButtons = receivedEvent.actionButtons; + } + + // Android + if (receivedEvent.groupKey) { + this.groupKey = receivedEvent.groupKey; + } + if (receivedEvent.ledColor) { + this.ledColor = receivedEvent.ledColor; + } + if (typeof receivedEvent.priority !== 'undefined') { + this.priority = receivedEvent.priority; + } + if (receivedEvent.smallIcon) { + this.smallIcon = receivedEvent.smallIcon; + } + if (receivedEvent.largeIcon) { + this.largeIcon = receivedEvent.largeIcon; + } + if (receivedEvent.bigPicture) { + this.bigPicture = receivedEvent.bigPicture; + } + if (receivedEvent.collapseId) { + this.collapseId = receivedEvent.collapseId; + } + if (receivedEvent.groupMessage) { + this.groupMessage = receivedEvent.groupMessage; + } + if (receivedEvent.fromProjectNumber) { + this.fromProjectNumber = receivedEvent.fromProjectNumber; + } + if (receivedEvent.smallIconAccentColor) { + this.smallIconAccentColor = receivedEvent.smallIconAccentColor; + } + if (receivedEvent.lockScreenVisibility) { + this.lockScreenVisibility = receivedEvent.lockScreenVisibility; + } + if (receivedEvent.androidNotificationId) { + this.androidNotificationId = receivedEvent.androidNotificationId; + } + if ( + receivedEvent.groupedNotifications && + receivedEvent.groupedNotifications.length + ) { + this.groupedNotifications = receivedEvent.groupedNotifications; + } + + // iOS + if (receivedEvent.badge) { + this.badge = receivedEvent.badge; + } + if (receivedEvent.category) { + this.category = receivedEvent.category; + } + if (receivedEvent.threadId) { + this.threadId = receivedEvent.threadId; + } + if (receivedEvent.subtitle) { + this.subtitle = receivedEvent.subtitle; + } + if (receivedEvent.templateId) { + this.templateId = receivedEvent.templateId; + } + if (receivedEvent.attachments) { + this.attachments = receivedEvent.attachments; + } + if (receivedEvent.templateName) { + this.templateName = receivedEvent.templateName; + } + if (receivedEvent.mutableContent) { + this.mutableContent = receivedEvent.mutableContent; + } + if (receivedEvent.badgeIncrement) { + this.badgeIncrement = receivedEvent.badgeIncrement; + } + if (receivedEvent.contentAvailable) { + this.contentAvailable = receivedEvent.contentAvailable; + } + if (receivedEvent.relevanceScore) { + this.relevanceScore = receivedEvent.relevanceScore; + } + if (receivedEvent.interruptionLevel) { + this.interruptionLevel = receivedEvent.interruptionLevel; + } + } + + /** + * Display the notification. + * @returns void + */ + display(): void { + if (_pluginRef) { + void _pluginRef.displayNotification({ + notificationId: this.notificationId, + }); + } + } +} diff --git a/src/OneSignalPlugin.ts b/src/OneSignalPlugin.ts new file mode 100644 index 0000000..1502c42 --- /dev/null +++ b/src/OneSignalPlugin.ts @@ -0,0 +1,86 @@ +import type { OneSignalCapacitorPlugin } from './definitions'; +import Debug from './DebugNamespace'; +import InAppMessages from './InAppMessagesNamespace'; +import LiveActivities from './LiveActivitiesNamespace'; +import Location from './LocationNamespace'; +import { _setNotificationEventPlugin } from './NotificationReceivedEvent'; +import Notifications from './NotificationsNamespace'; +import { _setOSNotificationPlugin } from './OSNotification'; +import Session from './SessionNamespace'; +import User from './UserNamespace'; + +export class OneSignalPlugin { + User: User; + Debug: Debug; + Session: Session; + Location: Location; + InAppMessages: InAppMessages; + Notifications: Notifications; + LiveActivities: LiveActivities; + + private _plugin: OneSignalCapacitorPlugin; + private _appID = ''; + + constructor(plugin: OneSignalCapacitorPlugin) { + this._plugin = plugin; + _setOSNotificationPlugin(plugin); + _setNotificationEventPlugin(plugin); + + this.User = new User(plugin); + this.Debug = new Debug(plugin); + this.Session = new Session(plugin); + this.Location = new Location(plugin); + this.InAppMessages = new InAppMessages(plugin); + this.Notifications = new Notifications(plugin); + this.LiveActivities = new LiveActivities(plugin); + } + + /** + * Initializes the OneSignal SDK. This should be called during startup of the application. + * @param {string} appId + * @returns void + */ + initialize(appId: string): void { + this._appID = appId; + + void this._plugin.initialize({ appId: this._appID }).then(() => { + this.User.pushSubscription._setPropertiesAndObserver(); + this.Notifications._setPropertyAndObserver(); + }); + } + + /** + * Login to OneSignal under the user identified by the [externalId] provided. The act of logging a user into the OneSignal SDK will switch the [user] context to that specific user. + * @param {string} externalId + * @returns void + */ + login(externalId: string): void { + void this._plugin.login({ externalId }); + } + + /** + * Logout the user previously logged in via [login]. The [user] property now references a new device-scoped user. + * @returns void + */ + logout(): void { + void this._plugin.logout(); + } + + /** + * Determines whether a user must consent to privacy prior to their user data being sent up to OneSignal. This should be set to true prior to the invocation of initialization to ensure compliance. + * @param {boolean} required + * @returns void + */ + setConsentRequired(required: boolean): void { + void this._plugin.setConsentRequired({ required }); + } + + /** + * Indicates whether privacy consent has been granted. This field is only relevant when the application has opted into data privacy protections. + * @param {boolean} granted + * @returns void + */ + setConsentGiven(granted: boolean): void { + void this._plugin.setConsentGiven({ granted }); + } +} diff --git a/src/PushSubscriptionNamespace.test.ts b/src/PushSubscriptionNamespace.test.ts new file mode 100644 index 0000000..fc121a5 --- /dev/null +++ b/src/PushSubscriptionNamespace.test.ts @@ -0,0 +1,238 @@ +import { describe, test, expect, vi, beforeEach } from 'vitest'; + +import { + PREV_SUB_ID, + PREV_SUB_TOKEN, + SUB_ID, + SUB_TOKEN, +} from '../mocks/constants'; +import { createMockPlugin } from '../mocks/capacitor'; +import PushSubscription, { + type PushSubscriptionChangedState, +} from './PushSubscriptionNamespace'; + +const SUB_CHANGED_STATE: PushSubscriptionChangedState = { + current: { id: SUB_ID, token: SUB_TOKEN, optedIn: true }, + previous: { id: PREV_SUB_ID, token: PREV_SUB_TOKEN, optedIn: false }, +}; + +describe('PushSubscription', () => { + let mockPlugin: ReturnType; + let pushSubscription: PushSubscription; + + beforeEach(() => { + mockPlugin = createMockPlugin(); + pushSubscription = new PushSubscription(mockPlugin); + }); + + test('should instantiate PushSubscription class', () => { + expect(pushSubscription).toBeInstanceOf(PushSubscription); + }); + + describe('id (deprecated)', () => { + test('should set id via getPushSubscriptionId', async () => { + mockPlugin.getPushSubscriptionId.mockResolvedValue({ id: SUB_ID }); + pushSubscription._setPropertiesAndObserver(); + + await vi.waitFor(() => { + expect(pushSubscription.id).toBe(SUB_ID); + }); + }); + + test('should set id via addEventListener', () => { + pushSubscription._setPropertiesAndObserver(); + + const listener = mockPlugin.addListener.mock.calls.find( + (call) => call[0] === 'pushSubscriptionChange', + )?.[1]; + listener?.({ current: { id: SUB_ID, token: SUB_TOKEN, optedIn: true } }); + expect(pushSubscription.id).toBe(SUB_ID); + }); + }); + + describe('token (deprecated)', () => { + test('should set token via getPushSubscriptionToken', async () => { + mockPlugin.getPushSubscriptionToken.mockResolvedValue({ + token: SUB_TOKEN, + }); + pushSubscription._setPropertiesAndObserver(); + + await vi.waitFor(() => { + expect(pushSubscription.token).toBe(SUB_TOKEN); + }); + }); + + test('should set token via addEventListener', () => { + pushSubscription._setPropertiesAndObserver(); + + const listener = mockPlugin.addListener.mock.calls.find( + (call) => call[0] === 'pushSubscriptionChange', + )?.[1]; + listener?.({ current: { id: SUB_ID, token: SUB_TOKEN, optedIn: true } }); + expect(pushSubscription.token).toBe(SUB_TOKEN); + }); + }); + + describe('optedIn (deprecated)', () => { + test('should set optedIn via getPushSubscriptionOptedIn', async () => { + mockPlugin.getPushSubscriptionOptedIn.mockResolvedValue({ + optedIn: true, + }); + pushSubscription._setPropertiesAndObserver(); + + await vi.waitFor(() => { + expect(pushSubscription.optedIn).toBe(true); + }); + }); + + test('should set optedIn via addEventListener', () => { + pushSubscription._setPropertiesAndObserver(); + + const listener = mockPlugin.addListener.mock.calls.find( + (call) => call[0] === 'pushSubscriptionChange', + )?.[1]; + listener?.({ current: { id: SUB_ID, token: SUB_TOKEN, optedIn: true } }); + expect(pushSubscription.optedIn).toBe(true); + }); + }); + + describe('getIdAsync', () => { + test('should return a Promise', () => { + const promise = pushSubscription.getIdAsync(); + expect(promise).toBeInstanceOf(Promise); + }); + + test('should resolve with push subscription ID', async () => { + const testId = 'test-subscription-id-456'; + mockPlugin.getPushSubscriptionId.mockResolvedValue({ id: testId }); + + const result = await pushSubscription.getIdAsync(); + expect(result).toBe(testId); + }); + + test('should reject Promise when plugin fails', async () => { + const mockError = new Error('Failed to get subscription ID'); + mockPlugin.getPushSubscriptionId.mockRejectedValue(mockError); + + await expect(pushSubscription.getIdAsync()).rejects.toThrow( + mockError.message, + ); + }); + }); + + describe('getTokenAsync', () => { + test('should return a Promise', () => { + const promise = pushSubscription.getTokenAsync(); + expect(promise).toBeInstanceOf(Promise); + }); + + test('should resolve with push token', async () => { + const testToken = 'test-push-token-xyz'; + mockPlugin.getPushSubscriptionToken.mockResolvedValue({ + token: testToken, + }); + + const result = await pushSubscription.getTokenAsync(); + expect(result).toBe(testToken); + }); + + test('should reject Promise when plugin fails', async () => { + const mockError = new Error('Failed to get push token'); + mockPlugin.getPushSubscriptionToken.mockRejectedValue(mockError); + + await expect(pushSubscription.getTokenAsync()).rejects.toThrow( + mockError.message, + ); + }); + }); + + describe('getOptedInAsync', () => { + test('should return a Promise', () => { + const promise = pushSubscription.getOptedInAsync(); + expect(promise).toBeInstanceOf(Promise); + }); + + test('should resolve with true when user is opted in', async () => { + mockPlugin.getPushSubscriptionOptedIn.mockResolvedValue({ + optedIn: true, + }); + + const result = await pushSubscription.getOptedInAsync(); + expect(result).toBe(true); + }); + + test('should reject Promise when plugin fails', async () => { + const mockError = new Error('Failed to get opted in status'); + mockPlugin.getPushSubscriptionOptedIn.mockRejectedValue(mockError); + + await expect(pushSubscription.getOptedInAsync()).rejects.toThrow( + mockError.message, + ); + }); + }); + + describe('addEventListener', () => { + test('should call addListener', () => { + const mockListener = vi.fn(); + pushSubscription.addEventListener('change', mockListener); + + expect(mockPlugin.addListener).toHaveBeenCalledWith( + 'pushSubscriptionChange', + expect.any(Function), + ); + }); + + test('should call listener when subscription state changes', () => { + const mockListener = vi.fn(); + const mockListener2 = vi.fn(); + pushSubscription.addEventListener('change', mockListener); + pushSubscription.addEventListener('change', mockListener2); + + const callback = mockPlugin.addListener.mock.calls[0][1]; + callback(SUB_CHANGED_STATE); + + expect(mockListener).toHaveBeenCalledWith(SUB_CHANGED_STATE); + expect(mockListener2).toHaveBeenCalledWith(SUB_CHANGED_STATE); + }); + }); + + describe('removeEventListener', () => { + test('should remove listener from observer list', () => { + const mockListener = vi.fn(); + pushSubscription.addEventListener('change', mockListener); + pushSubscription.removeEventListener('change', mockListener); + + const callback = mockPlugin.addListener.mock.calls[0][1]; + callback(SUB_CHANGED_STATE); + expect(mockListener).not.toHaveBeenCalled(); + }); + + test('should only remove the specified listener', () => { + const mockListener1 = vi.fn(); + const mockListener2 = vi.fn(); + + pushSubscription.addEventListener('change', mockListener1); + pushSubscription.addEventListener('change', mockListener2); + pushSubscription.removeEventListener('change', mockListener1); + + const callback = mockPlugin.addListener.mock.calls[0][1]; + callback(SUB_CHANGED_STATE); + expect(mockListener1).not.toHaveBeenCalled(); + expect(mockListener2).toHaveBeenCalledWith(SUB_CHANGED_STATE); + }); + }); + + describe('optIn', () => { + test('should call plugin', () => { + pushSubscription.optIn(); + expect(mockPlugin.optInPushSubscription).toHaveBeenCalled(); + }); + }); + + describe('optOut', () => { + test('should call plugin', () => { + pushSubscription.optOut(); + expect(mockPlugin.optOutPushSubscription).toHaveBeenCalled(); + }); + }); +}); diff --git a/src/PushSubscriptionNamespace.ts b/src/PushSubscriptionNamespace.ts new file mode 100644 index 0000000..1695a49 --- /dev/null +++ b/src/PushSubscriptionNamespace.ts @@ -0,0 +1,173 @@ +import type { OneSignalCapacitorPlugin } from './definitions'; +import { removeListener } from './helpers'; + +export interface PushSubscriptionState { + id?: string; + token?: string; + optedIn: boolean; +} + +export interface PushSubscriptionChangedState { + previous: PushSubscriptionState; + current: PushSubscriptionState; +} + +export default class PushSubscription { + private _plugin: OneSignalCapacitorPlugin; + private _id?: string | null; + private _token?: string | null; + private _optedIn?: boolean; + + private _subscriptionObserverList: (( + event: PushSubscriptionChangedState, + ) => void)[] = []; + + constructor(plugin: OneSignalCapacitorPlugin) { + this._plugin = plugin; + } + + private _processFunctionList( + array: ((event: PushSubscriptionChangedState) => void)[], + param: PushSubscriptionChangedState, + ): void { + for (let i = 0; i < array.length; i++) { + array[i](param); + } + } + + /** + * Sets initial Push Subscription properties and adds observer for changes. + * This internal method is kept to support the deprecated methods {@link id}, {@link token}, {@link optedIn}. + */ + _setPropertiesAndObserver(): void { + void this._plugin + .getPushSubscriptionId() + .then((result) => { + this._id = result.id; + }); + + void this._plugin + .getPushSubscriptionToken() + .then((result) => { + this._token = result.token; + }); + + void this._plugin + .getPushSubscriptionOptedIn() + .then((result) => { + this._optedIn = result.optedIn; + }); + + this.addEventListener('change', (subscriptionChange) => { + this._id = subscriptionChange.current.id; + this._token = subscriptionChange.current.token; + this._optedIn = subscriptionChange.current.optedIn; + }); + } + + /** + * @deprecated This method is deprecated. It has been replaced by {@link getIdAsync}. + */ + get id(): string | null | undefined { + console.warn( + 'OneSignal: This method has been deprecated. Use getIdAsync instead for getting push subscription id.', + ); + return this._id; + } + + /** + * @deprecated This method is deprecated. It has been replaced by {@link getTokenAsync}. + */ + get token(): string | null | undefined { + console.warn( + 'OneSignal: This method has been deprecated. Use getTokenAsync instead for getting push subscription token.', + ); + return this._token; + } + + /** + * @deprecated This method is deprecated. It has been replaced by {@link getOptedInAsync}. + */ + get optedIn(): boolean { + console.warn( + 'OneSignal: This method has been deprecated. Use getOptedInAsync instead for getting push subscription opted in status.', + ); + return this._optedIn || false; + } + + /** + * The readonly push subscription ID. + * @returns {Promise} + */ + async getIdAsync(): Promise { + const result = await this._plugin.getPushSubscriptionId(); + return result.id; + } + + /** + * The readonly push token. + * @returns {Promise} + */ + async getTokenAsync(): Promise { + const result = await this._plugin.getPushSubscriptionToken(); + return result.token; + } + + /** + * Gets a boolean value indicating whether the current user is opted in to push notifications. + * This returns true when the app has notifications permission and optOut() is NOT called. + * Note: Does not take into account the existence of the subscription ID and push token. + * This boolean may return true but push notifications may still not be received by the user. + * @returns {Promise} + */ + async getOptedInAsync(): Promise { + const result = await this._plugin.getPushSubscriptionOptedIn(); + return result.optedIn; + } + + /** + * Add a callback that fires when the OneSignal push subscription state changes. + * @param {(event: PushSubscriptionChangedState)=>void} listener + * @returns void + */ + addEventListener( + _event: 'change', + listener: (event: PushSubscriptionChangedState) => void, + ) { + this._subscriptionObserverList.push(listener); + void this._plugin.addListener( + 'pushSubscriptionChange', + (state: PushSubscriptionChangedState) => { + this._processFunctionList(this._subscriptionObserverList, state); + }, + ); + } + + /** + * Remove a push subscription observer that has been previously added. + * @param {(event: PushSubscriptionChangedState)=>void} listener + * @returns void + */ + removeEventListener( + _event: 'change', + listener: (event: PushSubscriptionChangedState) => void, + ) { + removeListener(this._subscriptionObserverList, listener); + } + + /** + * Call this method to receive push notifications on the device or to resume receiving of push notifications after calling optOut. If needed, this method will prompt the user for push notifications permission. + * @returns void + */ + optIn(): void { + void this._plugin.optInPushSubscription(); + } + + /** + * If at any point you want the user to stop receiving push notifications on the current device (regardless of system-level permission status), you can call this method to opt out. + * @returns void + */ + optOut(): void { + void this._plugin.optOutPushSubscription(); + } +} diff --git a/src/SessionNamespace.test.ts b/src/SessionNamespace.test.ts new file mode 100644 index 0000000..13d917e --- /dev/null +++ b/src/SessionNamespace.test.ts @@ -0,0 +1,56 @@ +import { describe, test, expect, beforeEach } from 'vitest'; + +import { createMockPlugin } from '../mocks/capacitor'; +import Session from './SessionNamespace'; + +describe('Session', () => { + let mockPlugin: ReturnType; + let session: Session; + + beforeEach(() => { + mockPlugin = createMockPlugin(); + session = new Session(mockPlugin); + }); + + test('should instantiate Session class', () => { + expect(session).toBeInstanceOf(Session); + }); + + describe('addOutcome', () => { + test('should call plugin with correct parameters', () => { + const outcomeName = 'test_outcome'; + + session.addOutcome(outcomeName); + + expect(mockPlugin.addOutcome).toHaveBeenCalledWith({ + name: outcomeName, + }); + }); + }); + + describe('addUniqueOutcome', () => { + test('should call plugin with correct parameters', () => { + const outcomeName = 'unique_test_outcome'; + + session.addUniqueOutcome(outcomeName); + + expect(mockPlugin.addUniqueOutcome).toHaveBeenCalledWith({ + name: outcomeName, + }); + }); + }); + + describe('addOutcomeWithValue', () => { + test('should call plugin with correct parameters', () => { + const outcomeName = 'purchase_value'; + const outcomeValue = 99.99; + + session.addOutcomeWithValue(outcomeName, outcomeValue); + + expect(mockPlugin.addOutcomeWithValue).toHaveBeenCalledWith({ + name: outcomeName, + value: outcomeValue, + }); + }); + }); +}); diff --git a/src/SessionNamespace.ts b/src/SessionNamespace.ts new file mode 100644 index 0000000..3bca96f --- /dev/null +++ b/src/SessionNamespace.ts @@ -0,0 +1,37 @@ +import type { OneSignalCapacitorPlugin } from './definitions'; + +export default class Session { + private _plugin: OneSignalCapacitorPlugin; + + constructor(plugin: OneSignalCapacitorPlugin) { + this._plugin = plugin; + } + + /** + * Add an outcome with the provided name, captured against the current session. + * @param {string} name + * @returns void + */ + addOutcome(name: string): void { + void this._plugin.addOutcome({ name }); + } + + /** + * Add a unique outcome with the provided name, captured against the current session. + * @param {string} name + * @returns void + */ + addUniqueOutcome(name: string): void { + void this._plugin.addUniqueOutcome({ name }); + } + + /** + * Add an outcome with the provided name and value, captured against the current session. + * @param {string} name + * @param {number} value + * @returns void + */ + addOutcomeWithValue(name: string, value: number): void { + void this._plugin.addOutcomeWithValue({ name, value }); + } +} diff --git a/src/UserNamespace.test.ts b/src/UserNamespace.test.ts new file mode 100644 index 0000000..2842212 --- /dev/null +++ b/src/UserNamespace.test.ts @@ -0,0 +1,367 @@ +import { describe, test, expect, vi, beforeEach } from 'vitest'; + +import { createMockPlugin } from '../mocks/capacitor'; +import PushSubscription from './PushSubscriptionNamespace'; +import User, { type UserChangedState } from './UserNamespace'; + +const USER_CHANGED_STATE: UserChangedState = { + current: { onesignalId: 'test-onesignal-id', externalId: 'test-external-id' }, +}; + +const LABEL = 'user_id'; +const EMAIL = 'test@example.com'; +const SMS_NUMBER = '+1234567890'; +const ONESIGNAL_ID = 'test-onesignal-id'; +const EXTERNAL_ID = 'test-external-id'; + +describe('User', () => { + let mockPlugin: ReturnType; + let user: User; + + beforeEach(() => { + mockPlugin = createMockPlugin(); + user = new User(mockPlugin); + }); + + test('should instantiate User class', () => { + expect(user).toBeInstanceOf(User); + }); + + test('should have pushSubscription property', () => { + expect(user.pushSubscription).toBeInstanceOf(PushSubscription); + }); + + describe('setLanguage', () => { + test('should call plugin with correct parameters', () => { + const language = 'en'; + user.setLanguage(language); + + expect(mockPlugin.setLanguage).toHaveBeenCalledWith({ language }); + }); + }); + + describe('addAlias', () => { + test('should call plugin with correct parameters', () => { + const id = '12345'; + user.addAlias(LABEL, id); + + expect(mockPlugin.addAliases).toHaveBeenCalledWith({ + aliases: { [LABEL]: id }, + }); + }); + }); + + describe('addAliases', () => { + test('should call plugin with correct parameters', () => { + const aliases = { [LABEL]: '12345', custom_id: 'abc-123' }; + user.addAliases(aliases); + + expect(mockPlugin.addAliases).toHaveBeenCalledWith({ aliases }); + }); + + test('should handle empty aliases object', () => { + const aliases = {}; + user.addAliases(aliases); + + expect(mockPlugin.addAliases).toHaveBeenCalledWith({ aliases }); + }); + }); + + describe('removeAlias', () => { + test('should call plugin with correct parameters', () => { + user.removeAlias(LABEL); + + expect(mockPlugin.removeAliases).toHaveBeenCalledWith({ + labels: [LABEL], + }); + }); + }); + + describe('removeAliases', () => { + test('should call plugin with correct parameters', () => { + const labels = [LABEL, 'custom_id', 'external_id']; + user.removeAliases(labels); + + expect(mockPlugin.removeAliases).toHaveBeenCalledWith({ labels }); + }); + + test('should handle empty array', () => { + const labels: string[] = []; + user.removeAliases(labels); + + expect(mockPlugin.removeAliases).toHaveBeenCalledWith({ labels }); + }); + }); + + describe('addEmail', () => { + test('should call plugin with correct parameters', () => { + user.addEmail(EMAIL); + + expect(mockPlugin.addEmail).toHaveBeenCalledWith({ email: EMAIL }); + }); + }); + + describe('removeEmail', () => { + test('should call plugin with correct parameters', () => { + user.removeEmail(EMAIL); + + expect(mockPlugin.removeEmail).toHaveBeenCalledWith({ email: EMAIL }); + }); + }); + + describe('addSms', () => { + test('should call plugin with correct parameters', () => { + user.addSms(SMS_NUMBER); + + expect(mockPlugin.addSms).toHaveBeenCalledWith({ + smsNumber: SMS_NUMBER, + }); + }); + }); + + describe('removeSms', () => { + test('should call plugin with correct parameters', () => { + user.removeSms(SMS_NUMBER); + + expect(mockPlugin.removeSms).toHaveBeenCalledWith({ + smsNumber: SMS_NUMBER, + }); + }); + }); + + describe('addTag', () => { + test('should call plugin with correct parameters', () => { + const key = 'level'; + const value = 'premium'; + user.addTag(key, value); + + expect(mockPlugin.addTags).toHaveBeenCalledWith({ + tags: { [key]: value }, + }); + }); + }); + + describe('addTags', () => { + test('should call plugin with correct parameters', () => { + const tags = { level: 'premium', status: 'active' }; + user.addTags(tags); + + expect(mockPlugin.addTags).toHaveBeenCalledWith({ tags }); + }); + + test('should convert non-string values to JSON strings', () => { + const tags = { count: 42, active: true, data: { nested: 'value' } }; + user.addTags(tags); + + expect(mockPlugin.addTags).toHaveBeenCalledWith({ + tags: { count: '42', active: 'true', data: '{"nested":"value"}' }, + }); + }); + }); + + describe('removeTag', () => { + test('should call plugin with correct parameters', () => { + const key = 'level'; + user.removeTag(key); + + expect(mockPlugin.removeTags).toHaveBeenCalledWith({ keys: [key] }); + }); + }); + + describe('removeTags', () => { + test('should call plugin with correct parameters', () => { + const keys = ['level', 'status', 'premium']; + user.removeTags(keys); + + expect(mockPlugin.removeTags).toHaveBeenCalledWith({ keys }); + }); + + test('should handle empty array', () => { + const keys: string[] = []; + user.removeTags(keys); + + expect(mockPlugin.removeTags).toHaveBeenCalledWith({ keys }); + }); + }); + + describe('getTags', () => { + test('should return a Promise', () => { + const promise = user.getTags(); + expect(promise).toBeInstanceOf(Promise); + }); + + test('should resolve with tags object', async () => { + const testTags = { level: 'premium', status: 'active', count: '5' }; + mockPlugin.getTags.mockResolvedValue({ tags: testTags }); + + const result = await user.getTags(); + expect(result).toEqual(testTags); + }); + + test('should reject Promise when plugin fails', async () => { + mockPlugin.getTags.mockRejectedValue(new Error('Failed to get tags')); + + await expect(user.getTags()).rejects.toThrow('Failed to get tags'); + }); + }); + + describe('addEventListener', () => { + test('should add listener and call addListener', () => { + const mockListener = vi.fn(); + user.addEventListener('change', mockListener); + + expect(mockPlugin.addListener).toHaveBeenCalledWith( + 'userStateChange', + expect.any(Function), + ); + }); + + test('should call all listeners when user state changes', () => { + const mockListener1 = vi.fn(); + const mockListener2 = vi.fn(); + const mockListener3 = vi.fn(); + + user.addEventListener('change', mockListener1); + user.addEventListener('change', mockListener2); + user.addEventListener('change', mockListener3); + + const callback = mockPlugin.addListener.mock.calls[0][1]; + callback(USER_CHANGED_STATE); + + expect(mockListener1).toHaveBeenCalledWith(USER_CHANGED_STATE); + expect(mockListener2).toHaveBeenCalledWith(USER_CHANGED_STATE); + expect(mockListener3).toHaveBeenCalledWith(USER_CHANGED_STATE); + }); + }); + + describe('removeEventListener', () => { + test('should remove listener from observer list', () => { + const mockListener = vi.fn(); + + user.addEventListener('change', mockListener); + user.removeEventListener('change', mockListener); + + const callback = mockPlugin.addListener.mock.calls[0][1]; + callback(USER_CHANGED_STATE); + expect(mockListener).not.toHaveBeenCalled(); + }); + + test('should only remove the specified listener', () => { + const mockListener1 = vi.fn(); + const mockListener2 = vi.fn(); + + user.addEventListener('change', mockListener1); + user.addEventListener('change', mockListener2); + user.removeEventListener('change', mockListener1); + + const callback = mockPlugin.addListener.mock.calls[0][1]; + callback(USER_CHANGED_STATE); + expect(mockListener1).not.toHaveBeenCalled(); + expect(mockListener2).toHaveBeenCalledWith(USER_CHANGED_STATE); + }); + }); + + describe('getOnesignalId', () => { + test('should return a Promise', () => { + const promise = user.getOnesignalId(); + expect(promise).toBeInstanceOf(Promise); + }); + + test('should resolve with onesignal id', async () => { + mockPlugin.getOnesignalId.mockResolvedValue({ + onesignalId: ONESIGNAL_ID, + }); + + const result = await user.getOnesignalId(); + expect(result).toBe(ONESIGNAL_ID); + }); + + test('should reject Promise when plugin fails', async () => { + mockPlugin.getOnesignalId.mockRejectedValue( + new Error('Failed to get onesignal id'), + ); + + await expect(user.getOnesignalId()).rejects.toThrow( + 'Failed to get onesignal id', + ); + }); + }); + + describe('getExternalId', () => { + test('should return a Promise', () => { + const promise = user.getExternalId(); + expect(promise).toBeInstanceOf(Promise); + }); + + test('should resolve with external id', async () => { + mockPlugin.getExternalId.mockResolvedValue({ + externalId: EXTERNAL_ID, + }); + + const result = await user.getExternalId(); + expect(result).toBe(EXTERNAL_ID); + }); + + test('should reject Promise when plugin fails', async () => { + mockPlugin.getExternalId.mockRejectedValue( + new Error('Failed to get external id'), + ); + + await expect(user.getExternalId()).rejects.toThrow( + 'Failed to get external id', + ); + }); + }); + + describe('trackEvent', () => { + test('should call plugin with only event name', () => { + const eventName = 'purchase'; + user.trackEvent(eventName); + + expect(mockPlugin.trackEvent).toHaveBeenCalledWith({ name: eventName }); + }); + + test('should call plugin with event name and properties', () => { + const eventName = 'purchase'; + const properties = { amount: 99.99, currency: 'USD' }; + user.trackEvent(eventName, properties); + + expect(mockPlugin.trackEvent).toHaveBeenCalledWith({ + name: eventName, + properties, + }); + }); + + test('should not call plugin when properties are not serializable', () => { + const eventName = 'purchase'; + const circularObj: Record = { name: 'test' }; + circularObj.self = circularObj; + const consoleSpy = vi + .spyOn(console, 'error') + .mockImplementation(() => {}); + + user.trackEvent(eventName, circularObj); + + expect(consoleSpy).toHaveBeenCalledWith( + 'Properties must be a JSON-serializable object', + ); + expect(mockPlugin.trackEvent).not.toHaveBeenCalled(); + consoleSpy.mockRestore(); + }); + + test('should not call plugin when properties is an array', () => { + const eventName = 'purchase'; + const consoleSpy = vi + .spyOn(console, 'error') + .mockImplementation(() => {}); + + user.trackEvent(eventName, ['item1', 'item2'] as unknown as object); + + expect(consoleSpy).toHaveBeenCalledWith( + 'Properties must be a JSON-serializable object', + ); + expect(mockPlugin.trackEvent).not.toHaveBeenCalled(); + consoleSpy.mockRestore(); + }); + }); +}); diff --git a/src/UserNamespace.ts b/src/UserNamespace.ts new file mode 100644 index 0000000..9388009 --- /dev/null +++ b/src/UserNamespace.ts @@ -0,0 +1,234 @@ +import type { OneSignalCapacitorPlugin } from './definitions'; +import { isObjectSerializable, removeListener } from './helpers'; +import PushSubscription from './PushSubscriptionNamespace'; + +export interface UserState { + onesignalId?: string; + externalId?: string; +} + +export interface UserChangedState { + current: UserState; +} + +export default class User { + pushSubscription: PushSubscription; + + private _plugin: OneSignalCapacitorPlugin; + private _userStateObserverList: ((event: UserChangedState) => void)[] = []; + + constructor(plugin: OneSignalCapacitorPlugin) { + this._plugin = plugin; + this.pushSubscription = new PushSubscription(plugin); + } + + private _processFunctionList( + array: ((event: UserChangedState) => void)[], + param: UserChangedState, + ): void { + for (let i = 0; i < array.length; i++) { + array[i](param); + } + } + + /** + * Explicitly set a 2-character language code for the user. + * @param {string} language + * @returns void + */ + setLanguage(language: string): void { + void this._plugin.setLanguage({ language }); + } + + /** + * Set an alias for the current user. If this alias label already exists on this user, it will be overwritten with the new alias id. + * @param {string} label + * @param {string} id + * @returns void + */ + addAlias(label: string, id: string): void { + void this._plugin.addAliases({ aliases: { [label]: id } }); + } + + /** + * Set aliases for the current user. If any alias already exists, it will be overwritten to the new values. + * @param {object} aliases + * @returns void + */ + addAliases(aliases: Record): void { + void this._plugin.addAliases({ aliases }); + } + + /** + * Remove an alias from the current user. + * @param {string} label + * @returns void + */ + removeAlias(label: string): void { + void this._plugin.removeAliases({ labels: [label] }); + } + + /** + * Remove aliases from the current user. + * @param {string[]} labels + * @returns void + */ + removeAliases(labels: string[]): void { + void this._plugin.removeAliases({ labels }); + } + + /** + * Add a new email subscription to the current user. + * @param {string} email + * @returns void + */ + addEmail(email: string): void { + void this._plugin.addEmail({ email }); + } + + /** + * Remove an email subscription from the current user. + * @param {string} email + * @returns void + */ + removeEmail(email: string): void { + void this._plugin.removeEmail({ email }); + } + + /** + * Add a new SMS subscription to the current user. + * @param {string} smsNumber + * @returns void + */ + addSms(smsNumber: string): void { + void this._plugin.addSms({ smsNumber }); + } + + /** + * Remove an SMS subscription from the current user. + * @param {string} smsNumber + * @returns void + */ + removeSms(smsNumber: string): void { + void this._plugin.removeSms({ smsNumber }); + } + + /** + * Add a tag for the current user. Tags are key:value string pairs used as building blocks for targeting specific users and/or personalizing messages. + * @param {string} key + * @param {string} value + * @returns void + */ + addTag(key: string, value: string): void { + void this._plugin.addTags({ tags: { [key]: value } }); + } + + /** + * Add multiple tags for the current user. Tags are key:value string pairs used as building blocks for targeting specific users and/or personalizing messages. + * @param {object} tags + * @returns void + */ + addTags(tags: object): void { + const convertedTags = tags as { [key: string]: unknown }; + Object.keys(tags).forEach(function (key) { + if (typeof convertedTags[key] !== 'string') { + convertedTags[key] = JSON.stringify(convertedTags[key]); + } + }); + void this._plugin.addTags({ + tags: convertedTags as Record, + }); + } + + /** + * Remove the data tag with the provided key from the current user. + * @param {string} key + * @returns void + */ + removeTag(key: string): void { + void this._plugin.removeTags({ keys: [key] }); + } + + /** + * Remove multiple tags with the provided keys from the current user. + * @param {string[]} keys + * @returns void + */ + removeTags(keys: string[]): void { + void this._plugin.removeTags({ keys }); + } + + /** + * Returns the local tags for the current user. + * @returns Promise<{ [key: string]: string }> + */ + async getTags(): Promise<{ [key: string]: string }> { + const result = await this._plugin.getTags(); + return result.tags; + } + + /** + * Add a callback that fires when the OneSignal User state changes. + * @param {(event: UserChangedState)=>void} listener + * @returns void + */ + addEventListener( + _event: 'change', + listener: (event: UserChangedState) => void, + ) { + this._userStateObserverList.push(listener); + void this._plugin.addListener( + 'userStateChange', + (state: UserChangedState) => { + this._processFunctionList(this._userStateObserverList, state); + }, + ); + } + + /** + * Remove a User State observer that has been previously added. + * @param {(event: UserChangedState)=>void} listener + * @returns void + */ + removeEventListener( + _event: 'change', + listener: (event: UserChangedState) => void, + ) { + removeListener(this._userStateObserverList, listener); + } + + /** + * Get the nullable OneSignal Id associated with the current user. + * @returns {Promise} + */ + async getOnesignalId(): Promise { + const result = await this._plugin.getOnesignalId(); + return result.onesignalId; + } + + /** + * Get the nullable External Id associated with the current user. + * @returns {Promise} + */ + async getExternalId(): Promise { + const result = await this._plugin.getExternalId(); + return result.externalId; + } + + /** + * Track a custom event with the provided name and optional properties. + * @param {string} name - The name of the custom event + * @param {object} [properties] - Optional properties to associate with the event + * @returns void + */ + trackEvent(name: string, properties?: object): void { + if (properties !== undefined && !isObjectSerializable(properties)) { + console.error('Properties must be a JSON-serializable object'); + return; + } + void this._plugin.trackEvent({ + name, + properties: properties as Record | undefined, + }); + } +} diff --git a/src/definitions.ts b/src/definitions.ts new file mode 100644 index 0000000..67feb7b --- /dev/null +++ b/src/definitions.ts @@ -0,0 +1,105 @@ +import type { Plugin } from '@capacitor/core'; +import type { OSNotificationPermission } from './NotificationsNamespace'; + +export interface OneSignalCapacitorPlugin extends Plugin { + // Core + initialize(options: { appId: string }): Promise; + login(options: { externalId: string }): Promise; + logout(): Promise; + setConsentRequired(options: { required: boolean }): Promise; + setConsentGiven(options: { granted: boolean }): Promise; + + // Debug + setLogLevel(options: { logLevel: number }): Promise; + setAlertLevel(options: { logLevel: number }): Promise; + + // User + setLanguage(options: { language: string }): Promise; + addAliases(options: { aliases: Record }): Promise; + removeAliases(options: { labels: string[] }): Promise; + addEmail(options: { email: string }): Promise; + removeEmail(options: { email: string }): Promise; + addSms(options: { smsNumber: string }): Promise; + removeSms(options: { smsNumber: string }): Promise; + addTags(options: { tags: Record }): Promise; + removeTags(options: { keys: string[] }): Promise; + getTags(): Promise<{ tags: Record }>; + getOnesignalId(): Promise<{ onesignalId: string | null }>; + getExternalId(): Promise<{ externalId: string | null }>; + trackEvent(options: { + name: string; + properties?: Record; + }): Promise; + + // Push Subscription + getPushSubscriptionId(): Promise<{ id: string | null }>; + getPushSubscriptionToken(): Promise<{ token: string | null }>; + getPushSubscriptionOptedIn(): Promise<{ optedIn: boolean }>; + optInPushSubscription(): Promise; + optOutPushSubscription(): Promise; + + // Notifications + getPermission(): Promise<{ permission: boolean }>; + permissionNative(): Promise<{ permission: OSNotificationPermission }>; + requestPermission(options: { + fallbackToSettings: boolean; + }): Promise<{ permission: boolean }>; + canRequestPermission(): Promise<{ canRequest: boolean }>; + registerForProvisionalAuthorization(): Promise<{ accepted: boolean }>; + clearAllNotifications(): Promise; + removeNotification(options: { id: number }): Promise; + removeGroupedNotifications(options: { id: string }): Promise; + preventDefault(options: { + notificationId: string; + discard: boolean; + }): Promise; + proceedWithWillDisplay(options: { + notificationId: string; + }): Promise; + displayNotification(options: { notificationId: string }): Promise; + + // In-App Messages + addTriggers(options: { + triggers: Record; + }): Promise; + removeTriggers(options: { keys: string[] }): Promise; + clearTriggers(): Promise; + setPaused(options: { pause: boolean }): Promise; + isPaused(): Promise<{ paused: boolean }>; + + // Session / Outcomes + addOutcome(options: { name: string }): Promise; + addUniqueOutcome(options: { name: string }): Promise; + addOutcomeWithValue(options: { + name: string; + value: number; + }): Promise; + + // Location + requestLocationPermission(): Promise; + setLocationShared(options: { shared: boolean }): Promise; + isLocationShared(): Promise<{ shared: boolean }>; + + // Live Activities + enterLiveActivity(options: { + activityId: string; + token: string; + }): Promise; + exitLiveActivity(options: { activityId: string }): Promise; + setPushToStartToken(options: { + activityType: string; + token: string; + }): Promise; + removePushToStartToken(options: { + activityType: string; + }): Promise; + setupDefaultLiveActivity(options?: { + enablePushToStart: boolean; + enablePushToUpdate: boolean; + }): Promise; + startDefaultLiveActivity(options: { + activityId: string; + attributes: Record; + content: Record; + }): Promise; +} diff --git a/src/helpers.ts b/src/helpers.ts new file mode 100644 index 0000000..1cbe0b3 --- /dev/null +++ b/src/helpers.ts @@ -0,0 +1,29 @@ +/** + * Removes a listener from an array of listeners. + * @param array The array of listeners + * @param listener The listener to remove + */ +export function removeListener( + array: ((event: T) => void)[], + listener: (event: T) => void, +): void { + const index = array.indexOf(listener); + if (index !== -1) { + array.splice(index, 1); + } +} + +/** + * Returns true if the value is a JSON-serializable object. + */ +export function isObjectSerializable(value: unknown): boolean { + if (!(typeof value === 'object' && value !== null && !Array.isArray(value))) { + return false; + } + try { + JSON.stringify(value); + return true; + } catch { + return false; + } +} diff --git a/src/index.test.ts b/src/index.test.ts new file mode 100644 index 0000000..87aa293 --- /dev/null +++ b/src/index.test.ts @@ -0,0 +1,66 @@ +import { describe, test, expect, vi, beforeEach } from 'vitest'; + +import { createMockPlugin } from '../mocks/capacitor'; +import { APP_ID } from '../mocks/constants'; +import { OneSignalPlugin } from './OneSignalPlugin'; + +describe('OneSignalPlugin', () => { + let mockPlugin: ReturnType; + let plugin: OneSignalPlugin; + + beforeEach(() => { + mockPlugin = createMockPlugin(); + plugin = new OneSignalPlugin(mockPlugin); + }); + + test('should instantiate OneSignalPlugin', () => { + expect(plugin).toBeInstanceOf(OneSignalPlugin); + }); + + test('should have all required namespaces', () => { + expect(plugin.User).toBeDefined(); + expect(plugin.Debug).toBeDefined(); + expect(plugin.Session).toBeDefined(); + expect(plugin.Location).toBeDefined(); + expect(plugin.InAppMessages).toBeDefined(); + expect(plugin.Notifications).toBeDefined(); + expect(plugin.LiveActivities).toBeDefined(); + }); + + test('should initialize with appId', async () => { + plugin.initialize(APP_ID); + + expect(mockPlugin.initialize).toHaveBeenCalledWith({ appId: APP_ID }); + + await vi.waitFor(() => { + expect(mockPlugin.getPushSubscriptionId).toHaveBeenCalled(); + }); + }); + + test('should call plugin for login', () => { + const externalId = 'test-user-123'; + plugin.login(externalId); + + expect(mockPlugin.login).toHaveBeenCalledWith({ externalId }); + }); + + test('should call plugin for logout', () => { + plugin.logout(); + + expect(mockPlugin.logout).toHaveBeenCalled(); + }); + + test('should call plugin for setConsentRequired', () => { + plugin.setConsentRequired(true); + + expect(mockPlugin.setConsentRequired).toHaveBeenCalledWith({ + required: true, + }); + }); + + test('should call plugin for setConsentGiven', () => { + plugin.setConsentGiven(true); + + expect(mockPlugin.setConsentGiven).toHaveBeenCalledWith({ granted: true }); + }); +}); diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..bf732c0 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,42 @@ +import { registerPlugin } from '@capacitor/core'; + +import type { OneSignalCapacitorPlugin } from './definitions'; +import { OneSignalPlugin } from './OneSignalPlugin'; + +const OneSignalCapacitor = + registerPlugin('OneSignalCapacitor'); + +const OneSignal = new OneSignalPlugin(OneSignalCapacitor); + +export { LogLevel } from './DebugNamespace'; +export { NotificationWillDisplayEvent } from './NotificationReceivedEvent'; +export { OSNotificationPermission } from './NotificationsNamespace'; +export { OSNotification } from './OSNotification'; +export { OneSignalPlugin } from './OneSignalPlugin'; + +export type { + PushSubscriptionChangedState, + PushSubscriptionState, +} from './PushSubscriptionNamespace'; + +export type { + NotificationClickEvent, + NotificationClickResult, +} from './types/NotificationClicked'; + +export type { + InAppMessageActionUrlType, + InAppMessageClickEvent, + InAppMessageClickResult, + InAppMessageDidDismissEvent, + InAppMessageDidDisplayEvent, + InAppMessageWillDismissEvent, + InAppMessageWillDisplayEvent, + OSInAppMessage, +} from './types/InAppMessage'; + +export type { UserChangedState, UserState } from './UserNamespace'; + +export type { OneSignalCapacitorPlugin } from './definitions'; + +export default OneSignal; diff --git a/src/types/InAppMessage.ts b/src/types/InAppMessage.ts new file mode 100644 index 0000000..cbf8683 --- /dev/null +++ b/src/types/InAppMessage.ts @@ -0,0 +1,48 @@ +export type InAppMessageEventName = + | 'click' + | 'willDisplay' + | 'didDisplay' + | 'willDismiss' + | 'didDismiss'; + +export type InAppMessageEventTypeMap = { + click: InAppMessageClickEvent; + willDisplay: InAppMessageWillDisplayEvent; + didDisplay: InAppMessageDidDisplayEvent; + willDismiss: InAppMessageWillDismissEvent; + didDismiss: InAppMessageDidDismissEvent; +}; + +export interface InAppMessageClickEvent { + message: OSInAppMessage; + result: InAppMessageClickResult; +} + +export interface InAppMessageClickResult { + closingMessage: boolean; + actionId?: string; + url?: string; + urlTarget?: InAppMessageActionUrlType; +} + +export type InAppMessageActionUrlType = 'browser' | 'webview' | 'replacement'; + +export interface InAppMessageWillDisplayEvent { + message: OSInAppMessage; +} + +export interface InAppMessageDidDisplayEvent { + message: OSInAppMessage; +} + +export interface InAppMessageWillDismissEvent { + message: OSInAppMessage; +} + +export interface InAppMessageDidDismissEvent { + message: OSInAppMessage; +} + +export interface OSInAppMessage { + messageId: string; +} diff --git a/src/types/LiveActivities.ts b/src/types/LiveActivities.ts new file mode 100644 index 0000000..191a94b --- /dev/null +++ b/src/types/LiveActivities.ts @@ -0,0 +1,15 @@ +/** + * The setup options for `OneSignal.LiveActivities.setupDefault`. + */ +export type LiveActivitySetupOptions = { + /** + * When true, OneSignal will listen for pushToStart tokens for the `OneSignalLiveActivityAttributes` structure. + */ + enablePushToStart: boolean; + + /** + * When true, OneSignal will listen for pushToUpdate tokens for each start live activity that uses the + * `OneSignalLiveActivityAttributes` structure. + */ + enablePushToUpdate: boolean; +}; diff --git a/src/types/NotificationClicked.ts b/src/types/NotificationClicked.ts new file mode 100644 index 0000000..fc89607 --- /dev/null +++ b/src/types/NotificationClicked.ts @@ -0,0 +1,23 @@ +import type { NotificationWillDisplayEvent } from '../NotificationReceivedEvent'; +import type { OSNotification } from '../OSNotification'; + +export type NotificationEventName = + | 'click' + | 'foregroundWillDisplay' + | 'permissionChange'; + +export type NotificationEventTypeMap = { + click: NotificationClickEvent; + foregroundWillDisplay: NotificationWillDisplayEvent; + permissionChange: boolean; +}; + +export interface NotificationClickEvent { + result: NotificationClickResult; + notification: OSNotification; +} + +export interface NotificationClickResult { + actionId?: string; + url?: string; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..9a3329a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "outDir": "dist/esm", + "target": "es2020", + "module": "es2020", + "moduleResolution": "node", + "noImplicitAny": true, + "lib": ["es2020", "dom"], + "skipLibCheck": true, + "declaration": true, + "declarationDir": "dist/esm", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "strict": true, + "noUnusedLocals": true, + "types": [] + }, + "include": ["src", "mocks"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..613ca2f --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,43 @@ +import { defineConfig } from 'vitest/config'; +import dts from 'vite-plugin-dts'; + +export default defineConfig({ + plugins: [dts({ exclude: ['**/*.test.ts', 'mocks/**'], rollupTypes: true })], + build: { + outDir: 'dist', + emptyOutDir: true, + lib: { + entry: 'src/index.ts', + formats: ['es', 'cjs'], + fileName: (format) => + format === 'es' ? 'esm/index.js' : 'plugin.cjs.js', + }, + rollupOptions: { + external: ['@capacitor/core'], + output: { + exports: 'named', + globals: { + '@capacitor/core': 'capacitorExports', + }, + }, + }, + }, + test: { + clearMocks: true, + environment: 'happy-dom', + include: ['**/*.test.ts', '**/*.test.tsx'], + coverage: { + exclude: ['mocks/**', 'src/helpers.ts'], + enabled: true, + reporter: ['text-summary', 'lcov'], + reportOnFailure: true, + reportsDirectory: 'coverage', + thresholds: { + statements: 95, + branches: 95, + functions: 95, + lines: 95, + }, + }, + }, +}); From ae4704a12ffa25804c2bd807ab0640a7a2e753bc Mon Sep 17 00:00:00 2001 From: Fadi George Date: Thu, 2 Apr 2026 14:46:30 -0700 Subject: [PATCH 02/13] build: convert to ESM-only module format --- .gitignore | 6 ++++-- package.json | 14 ++++++++++---- tsconfig.json | 8 ++++---- vite.config.ts | 8 ++------ 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index f875a7b..1aa4e91 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,10 @@ node_modules -dist -coverage *.tgz +# Build & test output +dist/ +coverage/ + .DS_Store .idea diff --git a/package.json b/package.json index 244f473..21fd033 100644 --- a/package.json +++ b/package.json @@ -12,10 +12,16 @@ "type": "git", "url": "git+https://github.com/OneSignal/OneSignal-Capacitor-SDK.git" }, - "main": "dist/plugin.cjs.js", - "module": "dist/esm/index.js", - "types": "dist/esm/index.d.ts", - "unpkg": "dist/plugin.js", + "type": "module", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, "scripts": { "build": "vp pack", "format": "vp fmt", diff --git a/tsconfig.json b/tsconfig.json index 9a3329a..83b5865 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,14 +1,14 @@ { "compilerOptions": { - "outDir": "dist/esm", + "outDir": "dist", "target": "es2020", - "module": "es2020", - "moduleResolution": "node", + "module": "ESNext", + "moduleResolution": "bundler", "noImplicitAny": true, "lib": ["es2020", "dom"], "skipLibCheck": true, "declaration": true, - "declarationDir": "dist/esm", + "declarationDir": "dist", "allowSyntheticDefaultImports": true, "esModuleInterop": true, "strict": true, diff --git a/vite.config.ts b/vite.config.ts index 613ca2f..af316ec 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -8,17 +8,13 @@ export default defineConfig({ emptyOutDir: true, lib: { entry: 'src/index.ts', - formats: ['es', 'cjs'], - fileName: (format) => - format === 'es' ? 'esm/index.js' : 'plugin.cjs.js', + formats: ['es'], + fileName: 'index', }, rollupOptions: { external: ['@capacitor/core'], output: { exports: 'named', - globals: { - '@capacitor/core': 'capacitorExports', - }, }, }, }, From c5d5e1a06813cdc6b6ced7334a1456355dd1113d Mon Sep 17 00:00:00 2001 From: Fadi George Date: Thu, 2 Apr 2026 14:57:04 -0700 Subject: [PATCH 03/13] ci: add GitHub workflows and update lint scripts --- .github/codeql-config.yml | 7 + .github/pull_request_template.md | 75 ++++++++++ .github/workflows/cd.yml | 25 ++++ .github/workflows/ci.yml | 15 ++ .github/workflows/codeql.yml | 66 +++++++++ .github/workflows/create-release-pr.yml | 174 ++++++++++++++++++++++++ .github/workflows/linear-deployed.yml | 13 ++ .github/workflows/lint-pr-title.yml | 11 ++ .github/workflows/project.yml | 16 +++ package.json | 6 +- 10 files changed, 404 insertions(+), 4 deletions(-) create mode 100644 .github/codeql-config.yml create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/cd.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/codeql.yml create mode 100644 .github/workflows/create-release-pr.yml create mode 100644 .github/workflows/linear-deployed.yml create mode 100644 .github/workflows/lint-pr-title.yml create mode 100644 .github/workflows/project.yml diff --git a/.github/codeql-config.yml b/.github/codeql-config.yml new file mode 100644 index 0000000..790151f --- /dev/null +++ b/.github/codeql-config.yml @@ -0,0 +1,7 @@ +paths: + - src + - android/src/main/java + - .github/actions + +paths-ignore: + - 'examples/**/android/**' diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..ad828eb --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,75 @@ + + +# READ AND DELETE THIS SECTION BEFORE SUBMITTING PR + +- **Fill out each _REQUIRED_ section** +- **Fill out _OPTIONAL_ sections, remove section if it doesn't apply to your PR** +- **Read and fill out each of the checklists below** +- **Remove this section after reading** + + +# Description + +## One Line Summary + +**REQUIRED** - Very short description that summaries the changes in this PR. + +## Details + +### Motivation + +**REQUIRED -** Why is this code change being made? Or what is the goal of this PR? Examples: Fixes a specific bug, provides additional logging to debug future issues, feature to allow X. + +### Scope + +**RECOMMEND - OPTIONAL -** What is intended to be effected. What is known not to change. Example: Notifications are grouped when parameter X is set, not enabled by default. + +### OPTIONAL - Other + +**OPTIONAL -** Feel free to add any other sections or sub-sections that can explain your PR better. + +# Testing + +## Unit testing + +**OPTIONAL -** Explain unit tests added, if not clear in the code. + +## Manual testing + +**RECOMMEND - OPTIONAL -** Explain what scenarios were tested and the environment. +Example: Tested opening a notification while the app was foregrounded, app build with Android Studio 2020.3 with a fresh install of the OneSignal example app on a Pixel 6 with Android 12. + +# Affected code checklist + +- [ ] Notifications + - [ ] Display + - [ ] Open + - [ ] Push Processing + - [ ] Confirm Deliveries +- [ ] Outcomes +- [ ] Sessions +- [ ] In-App Messaging +- [ ] REST API requests +- [ ] Public API changes + +# Checklist + +## Overview + +- [ ] I have filled out all **REQUIRED** sections above +- [ ] PR does one thing + - If it is hard to explain how any codes changes are related to each other then it most likely needs to be more than one PR +- [ ] Any Public API changes are explained in the PR details and conform to existing APIs + +## Testing + +- [ ] I have included test coverage for these changes, or explained why they are not needed +- [ ] All automated tests pass, or I explained why that is not possible +- [ ] I have personally tested this on my device, or explained why that is not possible + +## Final pass + +- [ ] Code is as readable as possible. + - Simplify with less code, followed by splitting up code into well named functions and variables, followed by adding comments to the code. +- [ ] I have reviewed this PR myself, ensuring it meets each checklist item + - WIP (Work In Progress) is ok, but explain what is still in progress and what you would like feedback on. Start the PR title with "WIP" to indicate this. diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 0000000..3a6910c --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,25 @@ +# Filename (cd.yml) should match the workflow filename in Trusted Publisher setting on npmjs +name: Publish to NPM & Github +on: + pull_request: + types: + - closed + branches: + - main + - rel/** + +permissions: + id-token: write # Required for OIDC + contents: write + +jobs: + publish: + if: | + github.event.pull_request.merged == true && + contains(github.event.pull_request.title, 'chore: Release') + uses: OneSignal/sdk-shared/.github/workflows/publish-npm-github.yml@main + secrets: + GH_PUSH_TOKEN: ${{ secrets.GH_PUSH_TOKEN }} + with: + branch: ${{ github.event.pull_request.base.ref }} + toolchain: vite-plus diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..eebee25 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,15 @@ +name: Run Checks +on: + pull_request: + branches: + - '**' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + call: + uses: OneSignal/sdk-shared/.github/workflows/wrapper-js-ci.yml@main + with: + toolchain: vite-plus diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..6fa7cc0 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,66 @@ +name: 'CodeQL' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + push: + branches: ['main'] + pull_request: + branches: ['main'] + schedule: + - cron: '37 19 * * 4' + +jobs: + analyze: + name: Analyze + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners + # Consider using larger runners for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + include: + - language: actions + build-mode: none + dependency-caching: true + - language: javascript-typescript + build-mode: none + dependency-caching: true + - language: java + build-mode: none + dependency-caching: true + # Not analyzing .m (Objective-C) & .h (Objective-C header) files for now unless CodeQL supports it in the future + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + + with: + dependency-caching: ${{ matrix.dependency-caching }} + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + config-file: ./.github/codeql-config.yml + + - name: Autobuild + if: matrix.build-mode == 'autobuild' + uses: github/codeql-action/autobuild@v4 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 + with: + category: '/language:${{matrix.language}}' diff --git a/.github/workflows/create-release-pr.yml b/.github/workflows/create-release-pr.yml new file mode 100644 index 0000000..4500b05 --- /dev/null +++ b/.github/workflows/create-release-pr.yml @@ -0,0 +1,174 @@ +name: Create Release PR + +on: + # For making a release pr from android / ios sdk actions + workflow_call: + secrets: + GH_PUSH_TOKEN: + required: false + description: 'GitHub token for pushing changes' + inputs: + capacitor_version: + description: 'New Capacitor plugin version (e.g., 1.1.0 or 1.1.0-beta.1)' + required: true + type: string + android_version: + description: 'New Android SDK Version (e.g., 5.7.6). Leave blank to skip.' + required: false + type: string + ios_version: + description: 'New iOS SDK Version (e.g., 5.5.0). Leave blank to skip.' + required: false + type: string + target_branch: + description: 'Target branch to create the release PR on. Defaults to main.' + required: false + type: string + default: main + + # For making a release pr from the github actions + workflow_dispatch: + inputs: + capacitor_version: + description: 'New Capacitor plugin version (e.g., 1.1.0 or 1.1.0-beta.1)' + required: true + type: string + android_version: + description: 'New Android SDK Version (e.g., 5.7.6). Leave blank to skip.' + required: false + type: string + ios_version: + description: 'New iOS SDK Version (e.g., 5.5.0). Leave blank to skip.' + required: false + type: string + target_branch: + description: 'Target branch to create the release PR on. Defaults to main.' + required: false + type: string + default: main + +jobs: + prep: + uses: OneSignal/sdk-shared/.github/workflows/prep-release.yml@main + secrets: + # Need this cross-repo token (sdk-shared & this repo) to perform changes + + GH_PUSH_TOKEN: ${{ secrets.GH_PUSH_TOKEN }} + with: + target_repo: OneSignal/OneSignal-Capacitor-SDK + version: ${{ inputs.capacitor_version }} + + # Capacitor plugin: bump npm version + optional native SDK pins + update_version: + needs: prep + runs-on: macos-latest + outputs: + capacitor_from: ${{ steps.current_versions.outputs.capacitor_from }} + ios_from: ${{ steps.current_versions.outputs.ios_from }} + android_from: ${{ steps.current_versions.outputs.android_from }} + + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + # Need repository otherwise caller would set github.repository to the caller itself (e.g. sdk-shared) + repository: OneSignal/OneSignal-Capacitor-SDK + ref: ${{ needs.prep.outputs.release_branch }} + token: ${{ secrets.GH_PUSH_TOKEN || github.token }} + + - name: Setup Git User + uses: OneSignal/sdk-shared/.github/actions/setup-git-user@main + + - name: Set up Vite+ + uses: voidzero-dev/setup-vp@v1 + with: + cache: true + run-install: true + + - name: Get current native SDK versions from target branch + id: current_versions + run: | + git fetch origin ${{ inputs.target_branch }} + + # Get versions from target branch (not the release branch) + CURRENT_VERSION=$(git show origin/${{ inputs.target_branch }}:package.json | jq -r .version) + ANDROID_VERSION=$(git show origin/${{ inputs.target_branch }}:android/build.gradle | grep "com.onesignal:OneSignal:" | sed -E "s/.*OneSignal:([0-9.]+).*/\1/") + IOS_VERSION=$(git show origin/${{ inputs.target_branch }}:OneSignalCapacitorPlugin.podspec | grep "OneSignalXCFramework" | sed -E "s/.*'([0-9.]+)'.*/\1/") + + echo "capacitor_from=$CURRENT_VERSION" >> $GITHUB_OUTPUT + echo "android_from=$ANDROID_VERSION" >> $GITHUB_OUTPUT + echo "ios_from=$IOS_VERSION" >> $GITHUB_OUTPUT + + - name: Update Android SDK version + if: inputs.android_version != '' + run: | + VERSION="${{ inputs.android_version }}" + + # Validate version exists on GitHub + RELEASE=$(curl -s -H "Authorization: token ${{ github.token }}" \ + "https://api.github.com/repos/OneSignal/OneSignal-Android-SDK/releases/tags/${VERSION}") + + + if [ -z "$RELEASE" ]; then + echo "✗ Android SDK version ${VERSION} not found" + exit 1 + fi + + # Update Android SDK version in build.gradle (handles both api '...' and api('...') syntax) + sed -i '' -E "s/(com\.onesignal:OneSignal:)[0-9.]+/\1$VERSION/" android/build.gradle + echo "✓ Updated android/build.gradle with Android SDK ${VERSION}" + + # Only commit if there are changes + git add -A + git diff --staged --quiet && exit 0 + git commit -m "Update Android SDK to ${VERSION}" && git push + + - name: Update iOS SDK version + if: inputs.ios_version != '' + run: | + VERSION="${{ inputs.ios_version }}" + + # Validate version exists on GitHub + RELEASE=$(curl -s -H "Authorization: token ${{ github.token }}" \ + "https://api.github.com/repos/OneSignal/OneSignal-iOS-SDK/releases/tags/${VERSION}") + + if [ -z "$RELEASE" ]; then + echo "✗ iOS SDK version ${VERSION} not found" + exit 1 + fi + + sed -i '' "s/s\.dependency 'OneSignalXCFramework', '[^']*'/s.dependency 'OneSignalXCFramework', '$VERSION'/" OneSignalCapacitorPlugin.podspec + echo "✓ Updated OneSignalCapacitorPlugin.podspec with iOS SDK ${VERSION}" + + # Only commit if there are changes + git add -A + git diff --staged --quiet && exit 0 + git commit -m "Update iOS SDK to ${VERSION}" && git push + + - name: Update sdk version + run: | + NEW_VERSION="${{ inputs.capacitor_version }}" + + # Update package.json version + npm pkg set version="$NEW_VERSION" + + # Only commit if there are changes + git add -A + git diff --staged --quiet && exit 0 + git commit -m "Release $NEW_VERSION" && git push + + create-pr: + needs: [prep, update_version] + uses: OneSignal/sdk-shared/.github/workflows/create-release.yml@main + secrets: + # Need this cross-repo token (sdk-shared & this repo) to perform changes + GH_PUSH_TOKEN: ${{ secrets.GH_PUSH_TOKEN }} + with: + # Need target_repo otherwise caller would set github.repository to the caller itself (e.g. sdk-shared) + target_repo: OneSignal/OneSignal-Capacitor-SDK + release_branch: ${{ needs.prep.outputs.release_branch }} + target_branch: ${{ inputs.target_branch }} + android_from: ${{ needs.update_version.outputs.android_from }} + android_to: ${{ inputs.android_version }} + ios_from: ${{ needs.update_version.outputs.ios_from }} + ios_to: ${{ inputs.ios_version }} diff --git a/.github/workflows/linear-deployed.yml b/.github/workflows/linear-deployed.yml new file mode 100644 index 0000000..b5af2c9 --- /dev/null +++ b/.github/workflows/linear-deployed.yml @@ -0,0 +1,13 @@ +name: Move Linear tickets to Deployed + +on: + release: + types: [published, edited] + +jobs: + linear-deployed: + uses: OneSignal/sdk-shared/.github/workflows/linear-deployed.yml@main + with: + release_body: ${{ github.event.release.body }} + secrets: + LINEAR_GITHUB_API_KEY: ${{ secrets.LINEAR_GITHUB_API_KEY }} diff --git a/.github/workflows/lint-pr-title.yml b/.github/workflows/lint-pr-title.yml new file mode 100644 index 0000000..1f1a24d --- /dev/null +++ b/.github/workflows/lint-pr-title.yml @@ -0,0 +1,11 @@ +# Checks if the PR title follows semantic commit message conventions +name: Lint PR + +on: + pull_request: + types: [opened, edited, synchronize] + +jobs: + call: + uses: OneSignal/sdk-shared/.github/workflows/lint-pr-title.yml@main + secrets: inherit diff --git a/.github/workflows/project.yml b/.github/workflows/project.yml new file mode 100644 index 0000000..cef4e78 --- /dev/null +++ b/.github/workflows/project.yml @@ -0,0 +1,16 @@ +name: Add issues to project +on: + issues: + types: + - opened + +jobs: + add-to-project: + runs-on: ubuntu-latest + steps: + - name: Add issue to project + uses: actions/add-to-project@v1.0.2 + with: + # SDK Cross-Platform Project + project-url: https://github.com/orgs/OneSignal/projects/10 + github-token: ${{ secrets.GH_PROJECTS_TOKEN }} diff --git a/package.json b/package.json index 21fd033..8db9bed 100644 --- a/package.json +++ b/package.json @@ -26,11 +26,9 @@ "build": "vp pack", "format": "vp fmt", "format:check": "vp fmt --check", - "lint": "vp lint", - "lint:fix": "vp lint --fix", + "lint": "vp check", + "lint:fix": "vp check --fix", "test": "vp test", - "test:coverage": "vp test run --coverage", - "check": "vp check", "verify": "vp build && vp test run && vp lint", "docgen": "docgen --api OneSignalCapacitorPlugin --output-readme README.md --output-json dist/docs.json" }, From f48c0f2369fa7967afdbb734c25c76d8925200fc Mon Sep 17 00:00:00 2001 From: Fadi George Date: Thu, 2 Apr 2026 15:35:17 -0700 Subject: [PATCH 04/13] build: migrate from Vitest to Vite Plus toolchain --- bun.lock | 360 ++++++++++++++----------------------------------- package.json | 23 ++-- vite.config.ts | 27 ++-- 3 files changed, 123 insertions(+), 287 deletions(-) diff --git a/bun.lock b/bun.lock index 66ca764..cf3bb68 100644 --- a/bun.lock +++ b/bun.lock @@ -8,435 +8,273 @@ "@capacitor/core": "^7.0.0", "@capacitor/docgen": "^0.2.2", "@types/bun": "latest", - "@vitest/coverage-v8": "^4.0.2", "happy-dom": "^20.0.8", "typescript": "^5.9.3", - "vite": "^7.1.12", - "vite-plugin-dts": "^4.5.4", - "vitest": "^4.0.2", + "vite": "npm:@voidzero-dev/vite-plus-core@latest", + "vite-plus": "0.1.15", }, "peerDependencies": { "@capacitor/core": "^7.0.0", }, }, }, + "overrides": { + "vite": "npm:@voidzero-dev/vite-plus-core@latest", + "vitest": "npm:@voidzero-dev/vite-plus-test@latest", + }, "packages": { - "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], - - "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], - - "@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="], - - "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], - - "@bcoe/v8-coverage": ["@bcoe/v8-coverage@1.0.2", "", {}, "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA=="], - "@capacitor/core": ["@capacitor/core@7.6.1", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-nsNouCMxgYenyemy20sZwZYMtFi93LSZVWm2KqHTYIPIDgwx24+PzwHIdRQBZdK7hpvD5jQEhWuo/QyLLnAyBQ=="], "@capacitor/docgen": ["@capacitor/docgen@0.2.2", "", { "dependencies": { "@types/node": "^14.18.0", "colorette": "^2.0.16", "github-slugger": "^1.4.0", "minimist": "^1.2.5", "typescript": "~4.2.4" }, "bin": { "docgen": "bin/docgen" } }, "sha512-617jB5DlKjUljxfoANORWeqffsHqHekckH48oslKWFGzfCHYIAEPfJfMa5M4mVSzn2ifSvWB5C3st4augwhl5w=="], - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.7", "", { "os": "aix", "cpu": "ppc64" }, "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg=="], - - "@esbuild/android-arm": ["@esbuild/android-arm@0.27.7", "", { "os": "android", "cpu": "arm" }, "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ=="], - - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.7", "", { "os": "android", "cpu": "arm64" }, "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ=="], - - "@esbuild/android-x64": ["@esbuild/android-x64@0.27.7", "", { "os": "android", "cpu": "x64" }, "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg=="], - - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw=="], - - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ=="], - - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.7", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w=="], - - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ=="], - - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.7", "", { "os": "linux", "cpu": "arm" }, "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA=="], - - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A=="], - - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.7", "", { "os": "linux", "cpu": "ia32" }, "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg=="], - - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q=="], - - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw=="], - - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.7", "", { "os": "linux", "cpu": "ppc64" }, "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ=="], - - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ=="], - - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.7", "", { "os": "linux", "cpu": "s390x" }, "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw=="], - - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.7", "", { "os": "linux", "cpu": "x64" }, "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA=="], - - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w=="], + "@oxc-project/runtime": ["@oxc-project/runtime@0.122.0", "", {}, "sha512-vevyz3bNjevQFCV2Yg5o6Sp9BSoiYiJVymMrzA3S1ZGj4J8ak4YiywhFyQMueQ3UNlJU6HZOZYDy70TUc99aHw=="], - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.7", "", { "os": "none", "cpu": "x64" }, "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw=="], + "@oxc-project/types": ["@oxc-project/types@0.122.0", "", {}, "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA=="], - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.7", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A=="], + "@oxfmt/binding-android-arm-eabi": ["@oxfmt/binding-android-arm-eabi@0.43.0", "", { "os": "android", "cpu": "arm" }, "sha512-CgU2s+/9hHZgo0IxVxrbMPrMj+tJ6VM3mD7Mr/4oiz4FNTISLoCvRmB5nk4wAAle045RtRjd86m673jwPyb1OQ=="], - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.7", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg=="], + "@oxfmt/binding-android-arm64": ["@oxfmt/binding-android-arm64@0.43.0", "", { "os": "android", "cpu": "arm64" }, "sha512-T9OfRwjA/EdYxAqbvR7TtqLv5nIrwPXuCtTwOHtS7aR9uXyn74ZYgzgTo6/ZwvTq9DY4W+DsV09hB2EXgn9EbA=="], - "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw=="], + "@oxfmt/binding-darwin-arm64": ["@oxfmt/binding-darwin-arm64@0.43.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-o3i49ZUSJWANzXMAAVY1wnqb65hn4JVzwlRQ5qfcwhRzIA8lGVaud31Q3by5ALHPrksp5QEaKCQF9aAS3TXpZA=="], - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.7", "", { "os": "sunos", "cpu": "x64" }, "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA=="], + "@oxfmt/binding-darwin-x64": ["@oxfmt/binding-darwin-x64@0.43.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-vWECzzCFkb0kK6jaHjbtC5sC3adiNWtqawFCxhpvsWlzVeKmv5bNvkB4nux+o4JKWTpHCM57NDK/MeXt44txmA=="], - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA=="], + "@oxfmt/binding-freebsd-x64": ["@oxfmt/binding-freebsd-x64@0.43.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-rgz8JpkKiI/umOf7fl9gwKyQasC8bs5SYHy6g7e4SunfLBY3+8ATcD5caIg8KLGEtKFm5ujKaH8EfjcmnhzTLg=="], - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.7", "", { "os": "win32", "cpu": "ia32" }, "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw=="], + "@oxfmt/binding-linux-arm-gnueabihf": ["@oxfmt/binding-linux-arm-gnueabihf@0.43.0", "", { "os": "linux", "cpu": "arm" }, "sha512-nWYnF3vIFzT4OM1qL/HSf1Yuj96aBuKWSaObXHSWliwAk2rcj7AWd6Lf7jowEBQMo4wCZVnueIGw/7C4u0KTBQ=="], - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="], + "@oxfmt/binding-linux-arm-musleabihf": ["@oxfmt/binding-linux-arm-musleabihf@0.43.0", "", { "os": "linux", "cpu": "arm" }, "sha512-sFg+NWJbLfupYTF4WELHAPSnLPOn1jiDZ33Z1jfDnTaA+cC3iB35x0FMMZTFdFOz3icRIArncwCcemJFGXu6TQ=="], - "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + "@oxfmt/binding-linux-arm64-gnu": ["@oxfmt/binding-linux-arm64-gnu@0.43.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-MelWqv68tX6wZEILDrTc9yewiGXe7im62+5x0bNXlCYFOZdA+VnYiJfAihbROsZ5fm90p9C3haFrqjj43XnlAA=="], - "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + "@oxfmt/binding-linux-arm64-musl": ["@oxfmt/binding-linux-arm64-musl@0.43.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-ROaWfYh+6BSJ1Arwy5ujijTlwnZetxDxzBpDc1oBR4d7rfrPBqzeyjd5WOudowzQUgyavl2wEpzn1hw3jWcqLA=="], - "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + "@oxfmt/binding-linux-ppc64-gnu": ["@oxfmt/binding-linux-ppc64-gnu@0.43.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-PJRs/uNxmFipJJ8+SyKHh7Y7VZIKQicqrrBzvfyM5CtKi8D7yZKTwUOZV3ffxmiC2e7l1SDJpkBEOyue5NAFsg=="], - "@microsoft/api-extractor": ["@microsoft/api-extractor@7.58.0", "", { "dependencies": { "@microsoft/api-extractor-model": "7.33.5", "@microsoft/tsdoc": "~0.16.0", "@microsoft/tsdoc-config": "~0.18.1", "@rushstack/node-core-library": "5.21.0", "@rushstack/rig-package": "0.7.2", "@rushstack/terminal": "0.22.4", "@rushstack/ts-command-line": "5.3.4", "diff": "~8.0.2", "lodash": "~4.17.23", "minimatch": "10.2.3", "resolve": "~1.22.1", "semver": "~7.5.4", "source-map": "~0.6.1", "typescript": "5.9.3" }, "bin": { "api-extractor": "bin/api-extractor" } }, "sha512-XcHlDylX5GblbGbs1eBoexjVyvdJMioTPuBCgCorE2rqijzTYxi6eudXyez3xACRxtH9aDtahoL9fYM4XTvQmg=="], + "@oxfmt/binding-linux-riscv64-gnu": ["@oxfmt/binding-linux-riscv64-gnu@0.43.0", "", { "os": "linux", "cpu": "none" }, "sha512-j6biGAgzIhj+EtHXlbNumvwG7XqOIdiU4KgIWRXAEj/iUbHKukKW8eXa4MIwpQwW1YkxovduKtzEAPnjlnAhVQ=="], - "@microsoft/api-extractor-model": ["@microsoft/api-extractor-model@7.33.5", "", { "dependencies": { "@microsoft/tsdoc": "~0.16.0", "@microsoft/tsdoc-config": "~0.18.1", "@rushstack/node-core-library": "5.21.0" } }, "sha512-Xh4dXuusndVQqVz4nEN9xOp0DyzsKxeD2FFJkSPg4arAjDSKPcy6cAc7CaeBPA7kF2wV1fuDlo2p/bNMpVr8yg=="], + "@oxfmt/binding-linux-riscv64-musl": ["@oxfmt/binding-linux-riscv64-musl@0.43.0", "", { "os": "linux", "cpu": "none" }, "sha512-RYWxAcslKxvy7yri24Xm9cmD0RiANaiEPs007EFG6l9h1ChM69Q5SOzACaCoz4Z9dEplnhhneeBaTWMEdpgIbA=="], - "@microsoft/tsdoc": ["@microsoft/tsdoc@0.16.0", "", {}, "sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA=="], + "@oxfmt/binding-linux-s390x-gnu": ["@oxfmt/binding-linux-s390x-gnu@0.43.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-DT6Q8zfQQy3jxpezAsBACEHNUUixKSYTwdXeXojNHe4DQOoxjPdjr3Szu6BRNjxLykZM/xMNmp9ElOIyDppwtw=="], - "@microsoft/tsdoc-config": ["@microsoft/tsdoc-config@0.18.1", "", { "dependencies": { "@microsoft/tsdoc": "0.16.0", "ajv": "~8.18.0", "jju": "~1.4.0", "resolve": "~1.22.2" } }, "sha512-9brPoVdfN9k9g0dcWkFeA7IH9bbcttzDJlXvkf8b2OBzd5MueR1V2wkKBL0abn0otvmkHJC6aapBOTJDDeMCZg=="], + "@oxfmt/binding-linux-x64-gnu": ["@oxfmt/binding-linux-x64-gnu@0.43.0", "", { "os": "linux", "cpu": "x64" }, "sha512-R8Yk7iYcuZORXmCfFZClqbDxRZgZ9/HEidUuBNdoX8Ptx07cMePnMVJ/woB84lFIDjh2ROHVaOP40Ds3rBXFqg=="], - "@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="], + "@oxfmt/binding-linux-x64-musl": ["@oxfmt/binding-linux-x64-musl@0.43.0", "", { "os": "linux", "cpu": "x64" }, "sha512-F2YYqyvnQNvi320RWZNAvsaWEHwmW3k4OwNJ1hZxRKXupY63expbBaNp6jAgvYs7y/g546vuQnGHQuCBhslhLQ=="], - "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.1", "", { "os": "android", "cpu": "arm" }, "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA=="], + "@oxfmt/binding-openharmony-arm64": ["@oxfmt/binding-openharmony-arm64@0.43.0", "", { "os": "none", "cpu": "arm64" }, "sha512-OE6TdietLXV3F6c7pNIhx/9YC1/2YFwjU9DPc/fbjxIX19hNIaP1rS0cFjCGJlGX+cVJwIKWe8Mos+LdQ1yAJw=="], - "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.1", "", { "os": "android", "cpu": "arm64" }, "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA=="], + "@oxfmt/binding-win32-arm64-msvc": ["@oxfmt/binding-win32-arm64-msvc@0.43.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-0nWK6a7pGkbdoypfVicmV9k/N1FwjPZENoqhlTU+5HhZnAhpIO3za30nEE33u6l6tuy9OVfpdXUqxUgZ+4lbZw=="], - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.60.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw=="], + "@oxfmt/binding-win32-ia32-msvc": ["@oxfmt/binding-win32-ia32-msvc@0.43.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-9aokTR4Ft+tRdvgN/pKzSkVy2ksc4/dCpDm9L/xFrbIw0yhLtASLbvoG/5WOTUh/BRPPnfGTsWznEqv0dlOmhA=="], - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.60.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew=="], + "@oxfmt/binding-win32-x64-msvc": ["@oxfmt/binding-win32-x64-msvc@0.43.0", "", { "os": "win32", "cpu": "x64" }, "sha512-4bPgdQux2ZLWn3bf2TTXXMHcJB4lenmuxrLqygPmvCJ104Yqzj1UctxSRzR31TiJ4MLaG22RK8dUsVpJtrCz5g=="], - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.60.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w=="], + "@oxlint-tsgolint/darwin-arm64": ["@oxlint-tsgolint/darwin-arm64@0.18.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-CxSd15ZwHn70UJFTXVvy76bZ9zwI097cVyjvUFmYRJwvkQF3VnrTf2oe1gomUacErksvtqLgn9OKvZhLMYwvog=="], - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.60.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g=="], + "@oxlint-tsgolint/darwin-x64": ["@oxlint-tsgolint/darwin-x64@0.18.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-LE7VW/T/VcKhl3Z1ev5BusrxdlQ3DWweSeOB+qpBeur2h8+vCWq+M7tCO29C7lveBDfx1+rNwj4aiUVlA+Qs+g=="], - "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.60.1", "", { "os": "linux", "cpu": "arm" }, "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g=="], + "@oxlint-tsgolint/linux-arm64": ["@oxlint-tsgolint/linux-arm64@0.18.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-2AG8YIXVJJbnM0rcsJmzzWOjZXBu5REwowgUpbHZueF7OYM3wR7Xu8pXEpAojEHAtYYZ3X4rpPoetomkJx7kCw=="], - "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.60.1", "", { "os": "linux", "cpu": "arm" }, "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg=="], + "@oxlint-tsgolint/linux-x64": ["@oxlint-tsgolint/linux-x64@0.18.1", "", { "os": "linux", "cpu": "x64" }, "sha512-f8vDYPEdiwpA2JaDEkadTXfuqIgweQ8zcL4SX75EN2kkW2oAynjN7cd8m86uXDgB0JrcyOywbRtwnXdiIzXn2A=="], - "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.60.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ=="], + "@oxlint-tsgolint/win32-arm64": ["@oxlint-tsgolint/win32-arm64@0.18.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-fBdML05KMDAL9ebWeoHIzkyI86Eq6r9YH5UDRuXJ9vAIo1EnKo0ti7hLUxNdc2dy2FF/T4k98p5wkkXvLyXqfA=="], - "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.60.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA=="], + "@oxlint-tsgolint/win32-x64": ["@oxlint-tsgolint/win32-x64@0.18.1", "", { "os": "win32", "cpu": "x64" }, "sha512-cYZMhNrsq9ZZ3OUWHyawqiS+c8HfieYG0zuZP2LbEuWWPfdZM/22iAlo608J+27G1s9RXQhvgX6VekwWbXbD7A=="], - "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ=="], + "@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.58.0", "", { "os": "android", "cpu": "arm" }, "sha512-1T7UN3SsWWxpWyWGn1cT3ASNJOo+pI3eUkmEl7HgtowapcV8kslYpFQcYn431VuxghXakPNlbjRwhqmR37PFOg=="], - "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw=="], + "@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.58.0", "", { "os": "android", "cpu": "arm64" }, "sha512-GryzujxuiRv2YFF7bRy8mKcxlbuAN+euVUtGJt9KKbLT8JBUIosamVhcthLh+VEr6KE6cjeVMAQxKAzJcoN7dg=="], - "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.60.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw=="], + "@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.58.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-7/bRSJIwl4GxeZL9rPZ11anNTyUO9epZrfEJH/ZMla3+/gbQ6xZixh9nOhsZ0QwsTW7/5J2A/fHbD1udC5DQQA=="], - "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.60.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg=="], + "@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.58.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-EqdtJSiHweS2vfILNrpyJ6HUwpEq2g7+4Zx1FPi4hu3Hu7tC3znF6ufbXO8Ub2LD4mGgznjI7kSdku9NDD1Mkg=="], - "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg=="], + "@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.58.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-VQt5TH4M42mY20F545G637RKxV/yjwVtKk2vfXuazfReSIiuvWBnv+FVSvIV5fKVTJNjt3GSJibh6JecbhGdBw=="], - "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg=="], + "@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.58.0", "", { "os": "linux", "cpu": "arm" }, "sha512-fBYcj4ucwpAtjJT3oeBdFBYKvNyjRSK+cyuvBOTQjh0jvKp4yeA4S/D0IsCHus/VPaNG5L48qQkh+Vjy3HL2/Q=="], - "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.60.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ=="], + "@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.58.0", "", { "os": "linux", "cpu": "arm" }, "sha512-0BeuFfwlUHlJ1xpEdSD1YO3vByEFGPg36uLjK1JgFaxFb4W6w17F8ET8sz5cheZ4+x5f2xzdnRrrWv83E3Yd8g=="], - "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.60.1", "", { "os": "linux", "cpu": "x64" }, "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg=="], + "@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.58.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-TXlZgnPTlxrQzxG9ZXU7BNwx1Ilrr17P3GwZY0If2EzrinqRH3zXPc3HrRcBJgcsoZNMuNL5YivtkJYgp467UQ=="], - "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.60.1", "", { "os": "linux", "cpu": "x64" }, "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w=="], + "@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.58.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-zSoYRo5dxHLcUx93Stl2hW3hSNjPt99O70eRVWt5A1zwJ+FPjeCCANCD2a9R4JbHsdcl11TIQOjyigcRVOH2mw=="], - "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.60.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw=="], + "@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.58.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NQ0U/lqxH2/VxBYeAIvMNUK1y0a1bJ3ZicqkF2c6wfakbEciP9jvIE4yNzCFpZaqeIeRYaV7AVGqEO1yrfVPjA=="], - "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.60.1", "", { "os": "none", "cpu": "arm64" }, "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA=="], + "@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.58.0", "", { "os": "linux", "cpu": "none" }, "sha512-X9J+kr3gIC9FT8GuZt0ekzpNUtkBVzMVU4KiKDSlocyQuEgi3gBbXYN8UkQiV77FTusLDPsovjo95YedHr+3yg=="], - "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.60.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g=="], + "@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.58.0", "", { "os": "linux", "cpu": "none" }, "sha512-CDze3pi1OO3Wvb/QsXjmLEY4XPKGM6kIo82ssNOgmcl1IdndF9VSGAE38YLhADWmOac7fjqhBw82LozuUVxD0Q=="], - "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.60.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg=="], + "@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.58.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-b/89glbxFaEAcA6Uf1FvCNecBJEgcUTsV1quzrqXM/o4R1M4u+2KCVuyGCayN2UpsRWtGGLb+Ver0tBBpxaPog=="], - "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.60.1", "", { "os": "win32", "cpu": "x64" }, "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg=="], + "@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.58.0", "", { "os": "linux", "cpu": "x64" }, "sha512-0/yYpkq9VJFCEcuRlrViGj8pJUFFvNS4EkEREaN7CB1EcLXJIaVSSa5eCihwBGXtOZxhnblWgxks9juRdNQI7w=="], - "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.60.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ=="], + "@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.58.0", "", { "os": "linux", "cpu": "x64" }, "sha512-hr6FNvmcAXiH+JxSvaJ4SJ1HofkdqEElXICW9sm3/Rd5eC3t7kzvmLyRAB3NngKO2wzXRCAm4Z/mGWfrsS4X8w=="], - "@rushstack/node-core-library": ["@rushstack/node-core-library@5.21.0", "", { "dependencies": { "ajv": "~8.18.0", "ajv-draft-04": "~1.0.0", "ajv-formats": "~3.0.1", "fs-extra": "~11.3.0", "import-lazy": "~4.0.0", "jju": "~1.4.0", "resolve": "~1.22.1", "semver": "~7.5.4" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-LFzN+1lyWROit/P8Md6yxAth7lLYKn37oCKJHirEE2TQB25NDUM7bALf0ar+JAtwFfRCH+D+DGOA7DAzIi2r+g=="], + "@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.58.0", "", { "os": "none", "cpu": "arm64" }, "sha512-R+O368VXgRql1K6Xar+FEo7NEwfo13EibPMoTv3sesYQedRXd6m30Dh/7lZMxnrQVFfeo4EOfYIP4FpcgWQNHg=="], - "@rushstack/problem-matcher": ["@rushstack/problem-matcher@0.2.1", "", { "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-gulfhBs6n+I5b7DvjKRfhMGyUejtSgOHTclF/eONr8hcgF1APEDjhxIsfdUYYMzC3rvLwGluqLjbwCFZ8nxrog=="], + "@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.58.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-Q0FZiAY/3c4YRj4z3h9K1PgaByrifrfbBoODSeX7gy97UtB7pySPUQfC2B/GbxWU6k7CzQrRy5gME10PltLAFQ=="], - "@rushstack/rig-package": ["@rushstack/rig-package@0.7.2", "", { "dependencies": { "resolve": "~1.22.1", "strip-json-comments": "~3.1.1" } }, "sha512-9XbFWuqMYcHUso4mnETfhGVUSaADBRj6HUAAEYk50nMPn8WRICmBuCphycQGNB3duIR6EEZX3Xj3SYc2XiP+9A=="], + "@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.58.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-Y8FKBABrSPp9H0QkRLHDHOSUgM/309a3IvOVgPcVxYcX70wxJrk608CuTg7w+C6vEd724X5wJoNkBcGYfH7nNQ=="], - "@rushstack/terminal": ["@rushstack/terminal@0.22.4", "", { "dependencies": { "@rushstack/node-core-library": "5.21.0", "@rushstack/problem-matcher": "0.2.1", "supports-color": "~8.1.1" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-fhtLjnXCc/4WleVbVl6aoc7jcWnU6yqjS1S8WoaNREG3ycu/viZ9R/9QM7Y/b4CDvcXoiDyMNIay7JMwBptM3g=="], + "@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.58.0", "", { "os": "win32", "cpu": "x64" }, "sha512-bCn5rbiz5My+Bj7M09sDcnqW0QJyINRVxdZ65x1/Y2tGrMwherwK/lpk+HRQCKvXa8pcaQdF5KY5j54VGZLwNg=="], - "@rushstack/ts-command-line": ["@rushstack/ts-command-line@5.3.4", "", { "dependencies": { "@rushstack/terminal": "0.22.4", "@types/argparse": "1.0.38", "argparse": "~1.0.9", "string-argv": "~0.3.1" } }, "sha512-MLkVKVEN6/2clKTrjN2B2KqKCuPxRwnNsWY7a+FCAq2EMdkj10cM8YgiBSMeGFfzM0mDMzargpHNnNzaBi9Whg=="], + "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="], "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - "@types/argparse": ["@types/argparse@1.0.38", "", {}, "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA=="], - "@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="], "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], - "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], - "@types/node": ["@types/node@14.18.63", "", {}, "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ=="], "@types/whatwg-mimetype": ["@types/whatwg-mimetype@3.0.2", "", {}, "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA=="], "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], - "@vitest/coverage-v8": ["@vitest/coverage-v8@4.1.2", "", { "dependencies": { "@bcoe/v8-coverage": "^1.0.2", "@vitest/utils": "4.1.2", "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-reports": "^3.2.0", "magicast": "^0.5.2", "obug": "^2.1.1", "std-env": "^4.0.0-rc.1", "tinyrainbow": "^3.1.0" }, "peerDependencies": { "@vitest/browser": "4.1.2", "vitest": "4.1.2" }, "optionalPeers": ["@vitest/browser"] }, "sha512-sPK//PHO+kAkScb8XITeB1bf7fsk85Km7+rt4eeuRR3VS1/crD47cmV5wicisJmjNdfeokTZwjMk4Mj2d58Mgg=="], - - "@vitest/expect": ["@vitest/expect@4.1.2", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.2", "@vitest/utils": "4.1.2", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ=="], - - "@vitest/mocker": ["@vitest/mocker@4.1.2", "", { "dependencies": { "@vitest/spy": "4.1.2", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q=="], - - "@vitest/pretty-format": ["@vitest/pretty-format@4.1.2", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA=="], - - "@vitest/runner": ["@vitest/runner@4.1.2", "", { "dependencies": { "@vitest/utils": "4.1.2", "pathe": "^2.0.3" } }, "sha512-Gr+FQan34CdiYAwpGJmQG8PgkyFVmARK8/xSijia3eTFgVfpcpztWLuP6FttGNfPLJhaZVP/euvujeNYar36OQ=="], - - "@vitest/snapshot": ["@vitest/snapshot@4.1.2", "", { "dependencies": { "@vitest/pretty-format": "4.1.2", "@vitest/utils": "4.1.2", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-g7yfUmxYS4mNxk31qbOYsSt2F4m1E02LFqO53Xpzg3zKMhLAPZAjjfyl9e6z7HrW6LvUdTwAQR3HHfLjpko16A=="], - - "@vitest/spy": ["@vitest/spy@4.1.2", "", {}, "sha512-DU4fBnbVCJGNBwVA6xSToNXrkZNSiw59H8tcuUspVMsBDBST4nfvsPsEHDHGtWRRnqBERBQu7TrTKskmjqTXKA=="], - - "@vitest/utils": ["@vitest/utils@4.1.2", "", { "dependencies": { "@vitest/pretty-format": "4.1.2", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ=="], - - "@volar/language-core": ["@volar/language-core@2.4.28", "", { "dependencies": { "@volar/source-map": "2.4.28" } }, "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ=="], - - "@volar/source-map": ["@volar/source-map@2.4.28", "", {}, "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ=="], + "@voidzero-dev/vite-plus-core": ["@voidzero-dev/vite-plus-core@0.1.15", "", { "dependencies": { "@oxc-project/runtime": "=0.122.0", "@oxc-project/types": "=0.122.0", "lightningcss": "^1.30.2", "postcss": "^8.5.6" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@arethetypeswrong/core": "^0.18.1", "@tsdown/css": "0.21.7", "@tsdown/exe": "0.21.7", "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", "publint": "^0.3.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "typescript": "^5.0.0 || ^6.0.0", "unplugin-unused": "^0.5.0", "yaml": "^2.4.2" }, "optionalPeers": ["@arethetypeswrong/core", "@tsdown/css", "@tsdown/exe", "@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "publint", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "typescript", "unplugin-unused", "yaml"] }, "sha512-0qAbqwcvQwiC8xGKSSuFtsjJUEM4LZzpXF7dffRazghGEQ8HH8NAvVryp/PiMSFwreJlV3rujwL4amKjnwCHpg=="], - "@volar/typescript": ["@volar/typescript@2.4.28", "", { "dependencies": { "@volar/language-core": "2.4.28", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw=="], + "@voidzero-dev/vite-plus-darwin-arm64": ["@voidzero-dev/vite-plus-darwin-arm64@0.1.15", "", { "os": "darwin", "cpu": "arm64" }, "sha512-arFq8phXg96rQ5J+FYvkBYdEGxIhP1ePAXlUeQY2hV8hJPzse+CdxusWxcjfpTgvFi+dpsKzE4KSNS22PyBo7w=="], - "@vue/compiler-core": ["@vue/compiler-core@3.5.31", "", { "dependencies": { "@babel/parser": "^7.29.2", "@vue/shared": "3.5.31", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-k/ueL14aNIEy5Onf0OVzR8kiqF/WThgLdFhxwa4e/KF/0qe38IwIdofoSWBTvvxQOesaz6riAFAUaYjoF9fLLQ=="], + "@voidzero-dev/vite-plus-darwin-x64": ["@voidzero-dev/vite-plus-darwin-x64@0.1.15", "", { "os": "darwin", "cpu": "x64" }, "sha512-2eY+gTEIZvLH33nQmcL2tKlf+iHfClaqaSMYIlUpTp/CN+xqh4Ir4y2vN1XGEuFDIW0FshSZTg3ulPtduneEDA=="], - "@vue/compiler-dom": ["@vue/compiler-dom@3.5.31", "", { "dependencies": { "@vue/compiler-core": "3.5.31", "@vue/shared": "3.5.31" } }, "sha512-BMY/ozS/xxjYqRFL+tKdRpATJYDTTgWSo0+AJvJNg4ig+Hgb0dOsHPXvloHQ5hmlivUqw1Yt2pPIqp4e0v1GUw=="], + "@voidzero-dev/vite-plus-linux-arm64-gnu": ["@voidzero-dev/vite-plus-linux-arm64-gnu@0.1.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-jJgz84pp61oHeXAYIUXKsVwQsMQ7NHK0+dBe6v1Q+Z034xXsyBrxi/JASSeVmCpAd6CN+xzOCsfMyn3whVTTxQ=="], - "@vue/compiler-vue2": ["@vue/compiler-vue2@2.7.16", "", { "dependencies": { "de-indent": "^1.0.2", "he": "^1.2.0" } }, "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A=="], + "@voidzero-dev/vite-plus-linux-arm64-musl": ["@voidzero-dev/vite-plus-linux-arm64-musl@0.1.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-F0Wig+We0ERhGecf3fDIwM/kfqT0vP2htH0vKUnV/inHIVbPc1MsrjcExX1eJ6KFSp5YTfchRN8HGecqtsudPA=="], - "@vue/language-core": ["@vue/language-core@2.2.0", "", { "dependencies": { "@volar/language-core": "~2.4.11", "@vue/compiler-dom": "^3.5.0", "@vue/compiler-vue2": "^2.7.16", "@vue/shared": "^3.5.0", "alien-signals": "^0.4.9", "minimatch": "^9.0.3", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw=="], + "@voidzero-dev/vite-plus-linux-x64-gnu": ["@voidzero-dev/vite-plus-linux-x64-gnu@0.1.15", "", { "os": "linux", "cpu": "x64" }, "sha512-aT5Yr2GphvRjoc2URmELDqjWwhe5VPvyy15Tzum+jPhEjY4I/lPXxKXEROjQe3TIv6MmFSHCe3oNCSaFdUE1pA=="], - "@vue/shared": ["@vue/shared@3.5.31", "", {}, "sha512-nBxuiuS9Lj5bPkPbWogPUnjxxWpkRniX7e5UBQDWl6Fsf4roq9wwV+cR7ezQ4zXswNvPIlsdj1slcLB7XCsRAw=="], + "@voidzero-dev/vite-plus-linux-x64-musl": ["@voidzero-dev/vite-plus-linux-x64-musl@0.1.15", "", { "os": "linux", "cpu": "x64" }, "sha512-Q6qMBMdVp5v84YVzFvMUpzVIHLfJuwZQR/KUtAOn/hzpfNITigKR2GrZZDgQvszFW+0CPhDFcK3kqLkxlJCdFg=="], - "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + "@voidzero-dev/vite-plus-test": ["@voidzero-dev/vite-plus-test@0.1.15", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@voidzero-dev/vite-plus-core": "0.1.15", "es-module-lexer": "^1.7.0", "obug": "^2.1.1", "pixelmatch": "^7.1.0", "pngjs": "^7.0.0", "sirv": "^3.0.2", "std-env": "^4.0.0", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "ws": "^8.18.3" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/ui": "4.1.2", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/ui", "happy-dom", "jsdom"] }, "sha512-jxMUEX6PDpzMUz+KOVOoB8HiODMf5mWjH19pof0k9l/RZT4iLDyVXB+p9PoWjbVrEMMGzq9BTOVob7wfOZeZEA=="], - "ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="], + "@voidzero-dev/vite-plus-win32-arm64-msvc": ["@voidzero-dev/vite-plus-win32-arm64-msvc@0.1.15", "", { "os": "win32", "cpu": "arm64" }, "sha512-EePrs+NIUy3gE60qaXPXzj8mw+JAXEBfGKsfweYBgNK6jo9ZXZto5ViKTuQsVVuWLVaELZSjoudbkzXB8wnJoQ=="], - "ajv-draft-04": ["ajv-draft-04@1.0.0", "", { "peerDependencies": { "ajv": "^8.5.0" }, "optionalPeers": ["ajv"] }, "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw=="], - - "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], - - "alien-signals": ["alien-signals@0.4.14", "", {}, "sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q=="], - - "argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], + "@voidzero-dev/vite-plus-win32-x64-msvc": ["@voidzero-dev/vite-plus-win32-x64-msvc@0.1.15", "", { "os": "win32", "cpu": "x64" }, "sha512-vfYfwOG/5a/WUtgGrbUCatRkc5x0Rq/9GDlCzQQIAFGDB5BfyIjGbdCOqamQWOh+yQbeOHwvgAhqjZ7Dv1oo/w=="], "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], - "ast-v8-to-istanbul": ["ast-v8-to-istanbul@1.0.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.31", "estree-walker": "^3.0.3", "js-tokens": "^10.0.0" } }, "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg=="], - - "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - - "brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="], - "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], - "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], + "cac": ["cac@7.0.0", "", {}, "sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ=="], "colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="], - "compare-versions": ["compare-versions@6.1.1", "", {}, "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg=="], + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], - "confbox": ["confbox@0.2.4", "", {}, "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ=="], - - "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], - - "de-indent": ["de-indent@1.0.2", "", {}, "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg=="], - - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - - "diff": ["diff@8.0.4", "", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="], + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], "entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], - "es-module-lexer": ["es-module-lexer@2.0.0", "", {}, "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw=="], - - "esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="], - - "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], - - "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], - - "exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="], - - "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], - - "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], + "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], - "fs-extra": ["fs-extra@11.3.4", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA=="], - "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], - "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], - "github-slugger": ["github-slugger@1.5.0", "", {}, "sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw=="], - "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], - "happy-dom": ["happy-dom@20.8.9", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.18.3" } }, "sha512-Tz23LR9T9jOGVZm2x1EPdXqwA37G/owYMxRwU0E4miurAtFsPMQ1d2Jc2okUaSjZqAFz2oEn3FLXC5a0a+siyA=="], - "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], - - "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], - - "he": ["he@1.2.0", "", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="], - - "html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="], + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - "import-lazy": ["import-lazy@4.0.0", "", {}, "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw=="], + "jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], - "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], + "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], - "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="], + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], - "istanbul-lib-report": ["istanbul-lib-report@3.0.1", "", { "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw=="], + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], - "istanbul-reports": ["istanbul-reports@3.2.0", "", { "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA=="], + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], - "jju": ["jju@1.4.0", "", {}, "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA=="], + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], - "js-tokens": ["js-tokens@10.0.0", "", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="], + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], - "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], - "jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], - "kolorist": ["kolorist@1.8.0", "", {}, "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ=="], + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], - "local-pkg": ["local-pkg@1.1.2", "", { "dependencies": { "mlly": "^1.7.4", "pkg-types": "^2.3.0", "quansync": "^0.2.11" } }, "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A=="], + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], - "lodash": ["lodash@4.17.23", "", {}, "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="], + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], - "lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], - - "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], - - "magicast": ["magicast@0.5.2", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "source-map-js": "^1.2.1" } }, "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ=="], - - "make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="], - - "minimatch": ["minimatch@10.2.3", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-Rwi3pnapEqirPSbWbrZaa6N3nmqq4Xer/2XooiOKyV3q12ML06f7MOuc5DVH8ONZIFhwIYQ3yzPH4nt7iWHaTg=="], + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], - "mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="], - - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - - "muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="], + "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], - "path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="], + "oxfmt": ["oxfmt@0.43.0", "", { "dependencies": { "tinypool": "2.1.0" }, "optionalDependencies": { "@oxfmt/binding-android-arm-eabi": "0.43.0", "@oxfmt/binding-android-arm64": "0.43.0", "@oxfmt/binding-darwin-arm64": "0.43.0", "@oxfmt/binding-darwin-x64": "0.43.0", "@oxfmt/binding-freebsd-x64": "0.43.0", "@oxfmt/binding-linux-arm-gnueabihf": "0.43.0", "@oxfmt/binding-linux-arm-musleabihf": "0.43.0", "@oxfmt/binding-linux-arm64-gnu": "0.43.0", "@oxfmt/binding-linux-arm64-musl": "0.43.0", "@oxfmt/binding-linux-ppc64-gnu": "0.43.0", "@oxfmt/binding-linux-riscv64-gnu": "0.43.0", "@oxfmt/binding-linux-riscv64-musl": "0.43.0", "@oxfmt/binding-linux-s390x-gnu": "0.43.0", "@oxfmt/binding-linux-x64-gnu": "0.43.0", "@oxfmt/binding-linux-x64-musl": "0.43.0", "@oxfmt/binding-openharmony-arm64": "0.43.0", "@oxfmt/binding-win32-arm64-msvc": "0.43.0", "@oxfmt/binding-win32-ia32-msvc": "0.43.0", "@oxfmt/binding-win32-x64-msvc": "0.43.0" }, "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-KTYNG5ISfHSdmeZ25Xzb3qgz9EmQvkaGAxgBY/p38+ZiAet3uZeu7FnMwcSQJg152Qwl0wnYAxDc+Z/H6cvrwA=="], - "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], + "oxlint": ["oxlint@1.58.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.58.0", "@oxlint/binding-android-arm64": "1.58.0", "@oxlint/binding-darwin-arm64": "1.58.0", "@oxlint/binding-darwin-x64": "1.58.0", "@oxlint/binding-freebsd-x64": "1.58.0", "@oxlint/binding-linux-arm-gnueabihf": "1.58.0", "@oxlint/binding-linux-arm-musleabihf": "1.58.0", "@oxlint/binding-linux-arm64-gnu": "1.58.0", "@oxlint/binding-linux-arm64-musl": "1.58.0", "@oxlint/binding-linux-ppc64-gnu": "1.58.0", "@oxlint/binding-linux-riscv64-gnu": "1.58.0", "@oxlint/binding-linux-riscv64-musl": "1.58.0", "@oxlint/binding-linux-s390x-gnu": "1.58.0", "@oxlint/binding-linux-x64-gnu": "1.58.0", "@oxlint/binding-linux-x64-musl": "1.58.0", "@oxlint/binding-openharmony-arm64": "1.58.0", "@oxlint/binding-win32-arm64-msvc": "1.58.0", "@oxlint/binding-win32-ia32-msvc": "1.58.0", "@oxlint/binding-win32-x64-msvc": "1.58.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.18.0" }, "optionalPeers": ["oxlint-tsgolint"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-t4s9leczDMqlvOSjnbCQe7gtoLkWgBGZ7sBdCJ9EOj5IXFSG/X7OAzK4yuH4iW+4cAYe8kLFbC8tuYMwWZm+Cg=="], - "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + "oxlint-tsgolint": ["oxlint-tsgolint@0.18.1", "", { "optionalDependencies": { "@oxlint-tsgolint/darwin-arm64": "0.18.1", "@oxlint-tsgolint/darwin-x64": "0.18.1", "@oxlint-tsgolint/linux-arm64": "0.18.1", "@oxlint-tsgolint/linux-x64": "0.18.1", "@oxlint-tsgolint/win32-arm64": "0.18.1", "@oxlint-tsgolint/win32-x64": "0.18.1" }, "bin": { "tsgolint": "bin/tsgolint.js" } }, "sha512-Hgb0wMfuXBYL0ddY+1hAG8IIfC40ADwPnBuUaC6ENAuCtTF4dHwsy7mCYtQ2e7LoGvfoSJRY0+kqQRiembJ/jQ=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], - "pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="], - - "postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], - - "quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="], - - "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + "pixelmatch": ["pixelmatch@7.1.0", "", { "dependencies": { "pngjs": "^7.0.0" }, "bin": { "pixelmatch": "bin/pixelmatch" } }, "sha512-1wrVzJ2STrpmONHKBy228LM1b84msXDUoAzVEl0R8Mz4Ce6EPr+IVtxm8+yvrqLYMHswREkjYFaMxnyGnaY3Ng=="], - "resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], + "pngjs": ["pngjs@7.0.0", "", {}, "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow=="], - "rollup": ["rollup@4.60.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.1", "@rollup/rollup-android-arm64": "4.60.1", "@rollup/rollup-darwin-arm64": "4.60.1", "@rollup/rollup-darwin-x64": "4.60.1", "@rollup/rollup-freebsd-arm64": "4.60.1", "@rollup/rollup-freebsd-x64": "4.60.1", "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", "@rollup/rollup-linux-arm-musleabihf": "4.60.1", "@rollup/rollup-linux-arm64-gnu": "4.60.1", "@rollup/rollup-linux-arm64-musl": "4.60.1", "@rollup/rollup-linux-loong64-gnu": "4.60.1", "@rollup/rollup-linux-loong64-musl": "4.60.1", "@rollup/rollup-linux-ppc64-gnu": "4.60.1", "@rollup/rollup-linux-ppc64-musl": "4.60.1", "@rollup/rollup-linux-riscv64-gnu": "4.60.1", "@rollup/rollup-linux-riscv64-musl": "4.60.1", "@rollup/rollup-linux-s390x-gnu": "4.60.1", "@rollup/rollup-linux-x64-gnu": "4.60.1", "@rollup/rollup-linux-x64-musl": "4.60.1", "@rollup/rollup-openbsd-x64": "4.60.1", "@rollup/rollup-openharmony-arm64": "4.60.1", "@rollup/rollup-win32-arm64-msvc": "4.60.1", "@rollup/rollup-win32-ia32-msvc": "4.60.1", "@rollup/rollup-win32-x64-gnu": "4.60.1", "@rollup/rollup-win32-x64-msvc": "4.60.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w=="], + "postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], - "semver": ["semver@7.5.4", "", { "dependencies": { "lru-cache": "^6.0.0" }, "bin": { "semver": "bin/semver.js" } }, "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA=="], + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], - "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], - "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + "sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], - "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="], - - "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], - "std-env": ["std-env@4.0.0", "", {}, "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ=="], - "string-argv": ["string-argv@0.3.2", "", {}, "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q=="], - - "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], - - "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], - - "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], - "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], "tinyexec": ["tinyexec@1.0.4", "", {}, "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw=="], "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], - "tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="], + "tinypool": ["tinypool@2.1.0", "", {}, "sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw=="], + + "totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="], "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - "ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="], - "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], - "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], - - "vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="], - - "vite-plugin-dts": ["vite-plugin-dts@4.5.4", "", { "dependencies": { "@microsoft/api-extractor": "^7.50.1", "@rollup/pluginutils": "^5.1.4", "@volar/typescript": "^2.4.11", "@vue/language-core": "2.2.0", "compare-versions": "^6.1.1", "debug": "^4.4.0", "kolorist": "^1.8.0", "local-pkg": "^1.0.0", "magic-string": "^0.30.17" }, "peerDependencies": { "typescript": "*", "vite": "*" }, "optionalPeers": ["vite"] }, "sha512-d4sOM8M/8z7vRXHHq/ebbblfaxENjogAAekcfcDCCwAyvGqnPrc7f4NZbvItS+g4WTgerW0xDwSz5qz11JT3vg=="], + "vite": ["@voidzero-dev/vite-plus-core@0.1.15", "", { "dependencies": { "@oxc-project/runtime": "=0.122.0", "@oxc-project/types": "=0.122.0", "lightningcss": "^1.30.2", "postcss": "^8.5.6" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@arethetypeswrong/core": "^0.18.1", "@tsdown/css": "0.21.7", "@tsdown/exe": "0.21.7", "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", "publint": "^0.3.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "typescript": "^5.0.0 || ^6.0.0", "unplugin-unused": "^0.5.0", "yaml": "^2.4.2" }, "optionalPeers": ["@arethetypeswrong/core", "@tsdown/css", "@tsdown/exe", "@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "publint", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "typescript", "unplugin-unused", "yaml"] }, "sha512-0qAbqwcvQwiC8xGKSSuFtsjJUEM4LZzpXF7dffRazghGEQ8HH8NAvVryp/PiMSFwreJlV3rujwL4amKjnwCHpg=="], - "vitest": ["vitest@4.1.2", "", { "dependencies": { "@vitest/expect": "4.1.2", "@vitest/mocker": "4.1.2", "@vitest/pretty-format": "4.1.2", "@vitest/runner": "4.1.2", "@vitest/snapshot": "4.1.2", "@vitest/spy": "4.1.2", "@vitest/utils": "4.1.2", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.2", "@vitest/browser-preview": "4.1.2", "@vitest/browser-webdriverio": "4.1.2", "@vitest/ui": "4.1.2", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg=="], - - "vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="], + "vite-plus": ["vite-plus@0.1.15", "", { "dependencies": { "@oxc-project/types": "=0.122.0", "@voidzero-dev/vite-plus-core": "0.1.15", "@voidzero-dev/vite-plus-test": "0.1.15", "cac": "^7.0.0", "cross-spawn": "^7.0.5", "jsonc-parser": "^3.3.1", "oxfmt": "=0.43.0", "oxlint": "=1.58.0", "oxlint-tsgolint": "=0.18.1", "picocolors": "^1.1.1" }, "optionalDependencies": { "@voidzero-dev/vite-plus-darwin-arm64": "0.1.15", "@voidzero-dev/vite-plus-darwin-x64": "0.1.15", "@voidzero-dev/vite-plus-linux-arm64-gnu": "0.1.15", "@voidzero-dev/vite-plus-linux-arm64-musl": "0.1.15", "@voidzero-dev/vite-plus-linux-x64-gnu": "0.1.15", "@voidzero-dev/vite-plus-linux-x64-musl": "0.1.15", "@voidzero-dev/vite-plus-win32-arm64-msvc": "0.1.15", "@voidzero-dev/vite-plus-win32-x64-msvc": "0.1.15" }, "bin": { "vp": "bin/vp", "oxfmt": "bin/oxfmt", "oxlint": "bin/oxlint" } }, "sha512-PBUvTq4D4BJcuusCA3mrSQmXcGVdPX9CIPpS7Y6+T+LbDsrmAZ+ITl9FzuE6zXvpT6Nht9cpHtwOLJw7m3adog=="], "whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="], - "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="], - "yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], - "@capacitor/docgen/typescript": ["typescript@4.2.4", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg=="], - "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], - - "@rushstack/terminal/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], - "@types/ws/@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], - "@vue/compiler-core/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], - - "@vue/language-core/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], - "bun-types/@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], "happy-dom/@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], - - "make-dir/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], - - "mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], - - "@vue/language-core/minimatch/brace-expansion": ["brace-expansion@2.0.3", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA=="], - - "mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], - - "@vue/language-core/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], } } diff --git a/package.json b/package.json index 8db9bed..998820a 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,10 @@ "format:check": "vp fmt --check", "lint": "vp check", "lint:fix": "vp check --fix", - "test": "vp test", - "verify": "vp build && vp test run && vp lint", + "test": "vitest", + "test:run": "vitest run", + "test:coverage": "vitest run --coverage", + "verify": "vp build && vitest run && vp lint", "docgen": "docgen --api OneSignalCapacitorPlugin --output-readme README.md --output-json dist/docs.json" }, "peerDependencies": { @@ -39,12 +41,11 @@ "@capacitor/core": "^7.0.0", "@capacitor/docgen": "^0.2.2", "@types/bun": "latest", - "@vitest/coverage-v8": "^4.0.2", "happy-dom": "^20.0.8", "typescript": "^5.9.3", - "vite": "^7.1.12", - "vite-plugin-dts": "^4.5.4", - "vitest": "^4.0.2" + "vite": "npm:@voidzero-dev/vite-plus-core@latest", + "vite-plus": "0.1.15" + }, "capacitor": { "ios": { @@ -72,7 +73,9 @@ "onesignal" ], "license": "MIT", - "publishConfig": { - "access": "public" - } -} + "overrides": { + "vite": "npm:@voidzero-dev/vite-plus-core@latest", + "vitest": "npm:@voidzero-dev/vite-plus-test@latest" + }, + "packageManager": "bun@1.3.11" +} \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index af316ec..df7c758 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,23 +1,18 @@ -import { defineConfig } from 'vitest/config'; -import dts from 'vite-plugin-dts'; +import { defineConfig } from 'vite-plus'; export default defineConfig({ - plugins: [dts({ exclude: ['**/*.test.ts', 'mocks/**'], rollupTypes: true })], - build: { - outDir: 'dist', - emptyOutDir: true, - lib: { - entry: 'src/index.ts', - formats: ['es'], - fileName: 'index', - }, - rollupOptions: { - external: ['@capacitor/core'], - output: { - exports: 'named', - }, + fmt: { + singleQuote: true, + sortImports: { + enabled: true, }, }, + pack: { + entry: 'src/index.ts', + dts: true, + format: 'es', + outExtensions: () => ({ js: '.js', dts: '.d.ts' }), + }, test: { clearMocks: true, environment: 'happy-dom', From c8c8ab24c5f2adb3acafd69a89b946df5591ef3a Mon Sep 17 00:00:00 2001 From: Fadi George Date: Thu, 2 Apr 2026 16:03:35 -0700 Subject: [PATCH 05/13] refactor(ios): inline OneSignalCapacitor wrapper class --- .gitignore | 21 ++++++++++-- .../OneSignalCapacitor.swift | 34 ------------------- .../OneSignalCapacitorPlugin.swift | 17 +++++----- 3 files changed, 27 insertions(+), 45 deletions(-) delete mode 100644 ios/Sources/OneSignalCapacitorPlugin/OneSignalCapacitor.swift diff --git a/.gitignore b/.gitignore index 1aa4e91..9942827 100644 --- a/.gitignore +++ b/.gitignore @@ -5,13 +5,28 @@ node_modules dist/ coverage/ +# macOS .DS_Store + +# IDE .idea +*.iml -# Gradle -.gradle/ -build/ +# iOS +Pods/ +Podfile.lock +Package.resolved +DerivedData/ +xcuserdata/ +/.build +.swiftpm/ # Android +.gradle/ +build/ +local.properties **/capacitor.build.gradle *.orig +*.apk +*.dex +*.class diff --git a/ios/Sources/OneSignalCapacitorPlugin/OneSignalCapacitor.swift b/ios/Sources/OneSignalCapacitorPlugin/OneSignalCapacitor.swift deleted file mode 100644 index b3f44f7..0000000 --- a/ios/Sources/OneSignalCapacitorPlugin/OneSignalCapacitor.swift +++ /dev/null @@ -1,34 +0,0 @@ -import Foundation -import OneSignalFramework - -@objc public class OneSignalCapacitor: NSObject { - @objc public func initialize(_ appId: String) { - OneSignalWrapper.sdkType = "capacitor" - OneSignalWrapper.sdkVersion = "010000" - OneSignal.initialize(appId, withLaunchOptions: nil) - } - - @objc public func login(_ externalId: String) { - OneSignal.login(externalId) - } - - @objc public func logout() { - OneSignal.logout() - } - - @objc public func setConsentRequired(_ required: Bool) { - OneSignal.setConsentRequired(required) - } - - @objc public func setConsentGiven(_ granted: Bool) { - OneSignal.setConsentGiven(granted) - } - - @objc public func setLogLevel(_ logLevel: Int) { - OneSignal.Debug.setLogLevel(ONE_S_LOG_LEVEL(rawValue: UInt32(logLevel))!) - } - - @objc public func setAlertLevel(_ logLevel: Int) { - OneSignal.Debug.setAlertLevel(ONE_S_LOG_LEVEL(rawValue: UInt32(logLevel))!) - } -} diff --git a/ios/Sources/OneSignalCapacitorPlugin/OneSignalCapacitorPlugin.swift b/ios/Sources/OneSignalCapacitorPlugin/OneSignalCapacitorPlugin.swift index d8b7884..7597c89 100644 --- a/ios/Sources/OneSignalCapacitorPlugin/OneSignalCapacitorPlugin.swift +++ b/ios/Sources/OneSignalCapacitorPlugin/OneSignalCapacitorPlugin.swift @@ -71,7 +71,6 @@ public class OneSignalCapacitorPlugin: CAPPlugin, CAPBridgedPlugin, CAPPluginMethod(name: "startDefaultLiveActivity", returnType: CAPPluginReturnPromise), ] - private let implementation = OneSignalCapacitor() private var notificationWillDisplayCache = [String: OSNotificationWillDisplayEvent]() private var preventDefaultCache = [String: OSNotificationWillDisplayEvent]() private var pendingClickEvent: OSNotificationClickEvent? @@ -83,7 +82,9 @@ public class OneSignalCapacitorPlugin: CAPPlugin, CAPBridgedPlugin, call.reject("appId is required") return } - implementation.initialize(appId) + OneSignalWrapper.sdkType = "capacitor" + OneSignalWrapper.sdkVersion = "010000" + OneSignal.initialize(appId, withLaunchOptions: nil) OneSignal.Notifications.addPermissionObserver(self) OneSignal.Notifications.addForegroundLifecycleListener(self) OneSignal.Notifications.addClickListener(self) @@ -104,24 +105,24 @@ public class OneSignalCapacitorPlugin: CAPPlugin, CAPBridgedPlugin, call.reject("externalId is required") return } - implementation.login(externalId) + OneSignal.login(externalId) call.resolve() } @objc func logout(_ call: CAPPluginCall) { - implementation.logout() + OneSignal.logout() call.resolve() } @objc func setConsentRequired(_ call: CAPPluginCall) { let required = call.getBool("required") ?? false - implementation.setConsentRequired(required) + OneSignal.setConsentRequired(required) call.resolve() } @objc func setConsentGiven(_ call: CAPPluginCall) { let granted = call.getBool("granted") ?? false - implementation.setConsentGiven(granted) + OneSignal.setConsentGiven(granted) call.resolve() } @@ -129,13 +130,13 @@ public class OneSignalCapacitorPlugin: CAPPlugin, CAPBridgedPlugin, @objc func setLogLevel(_ call: CAPPluginCall) { let level = call.getInt("logLevel") ?? 0 - implementation.setLogLevel(level) + OneSignal.Debug.setLogLevel(ONE_S_LOG_LEVEL(rawValue: UInt32(level))!) call.resolve() } @objc func setAlertLevel(_ call: CAPPluginCall) { let level = call.getInt("logLevel") ?? 0 - implementation.setAlertLevel(level) + OneSignal.Debug.setAlertLevel(ONE_S_LOG_LEVEL(rawValue: UInt32(level))!) call.resolve() } From 85fd1191c4255c771e5aacd6579ba6d5873d0c36 Mon Sep 17 00:00:00 2001 From: Fadi George Date: Thu, 2 Apr 2026 16:07:12 -0700 Subject: [PATCH 06/13] build: add pre-commit hooks and lint config --- .vite-hooks/pre-commit | 1 + vite.config.ts | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100755 .vite-hooks/pre-commit diff --git a/.vite-hooks/pre-commit b/.vite-hooks/pre-commit new file mode 100755 index 0000000..85fb65b --- /dev/null +++ b/.vite-hooks/pre-commit @@ -0,0 +1 @@ +vp staged diff --git a/vite.config.ts b/vite.config.ts index df7c758..ede3090 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,12 +1,18 @@ import { defineConfig } from 'vite-plus'; export default defineConfig({ + staged: { + '*': 'vp check --fix', + }, fmt: { singleQuote: true, sortImports: { enabled: true, }, }, + lint: { + options: { typeAware: true, typeCheck: true }, + }, pack: { entry: 'src/index.ts', dts: true, From 533b4f014530447e38a8dc3f24b089009867af77 Mon Sep 17 00:00:00 2001 From: Fadi George Date: Thu, 2 Apr 2026 16:12:04 -0700 Subject: [PATCH 07/13] style: format code and reorganize package.json --- README.md | 1 + mocks/capacitor.ts | 4 +- mocks/data.ts | 4 +- package.json | 67 +++++++++++++-------------- src/DebugNamespace.test.ts | 32 ++++++------- src/InAppMessagesNamespace.test.ts | 47 +++++++------------ src/InAppMessagesNamespace.ts | 62 ++++++------------------- src/LiveActivitiesNamespace.test.ts | 14 ++---- src/LocationNamespace.test.ts | 17 +++---- src/NotificationReceivedEvent.test.ts | 4 +- src/NotificationReceivedEvent.ts | 4 +- src/NotificationsNamespace.test.ts | 56 ++++++++-------------- src/NotificationsNamespace.ts | 55 ++++++---------------- src/OSNotification.ts | 9 +--- src/OneSignalPlugin.ts | 2 +- src/PushSubscriptionNamespace.test.ts | 23 ++------- src/PushSubscriptionNamespace.ts | 42 ++++++----------- src/UserNamespace.test.ts | 37 ++++----------- src/UserNamespace.ts | 19 ++------ src/definitions.ts | 42 ++++------------- src/index.ts | 8 +--- src/types/NotificationClicked.ts | 5 +- 22 files changed, 179 insertions(+), 375 deletions(-) diff --git a/README.md b/README.md index 94fdb1d..c00767a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ # OneSignal-Capacitor-SDK + For setting up push notifications & live activities for Capacitor projects diff --git a/mocks/capacitor.ts b/mocks/capacitor.ts index 41e4a2c..fc9bf7a 100644 --- a/mocks/capacitor.ts +++ b/mocks/capacitor.ts @@ -37,9 +37,7 @@ export function createMockPlugin(): MockPlugin { permissionNative: vi.fn().mockResolvedValue({ permission: 0 }), requestPermission: vi.fn().mockResolvedValue({ permission: false }), canRequestPermission: vi.fn().mockResolvedValue({ canRequest: true }), - registerForProvisionalAuthorization: vi - .fn() - .mockResolvedValue({ accepted: false }), + registerForProvisionalAuthorization: vi.fn().mockResolvedValue({ accepted: false }), clearAllNotifications: vi.fn().mockResolvedValue(undefined), removeNotification: vi.fn().mockResolvedValue(undefined), removeGroupedNotifications: vi.fn().mockResolvedValue(undefined), diff --git a/mocks/data.ts b/mocks/data.ts index 2428100..1014c17 100644 --- a/mocks/data.ts +++ b/mocks/data.ts @@ -2,9 +2,7 @@ import { OSNotification, type ReceivedEvent } from '../src/OSNotification'; import type { NotificationClickEvent } from '../src/types/NotificationClicked'; import { deepMerge } from './deepmerge'; -export const mockNotification = ( - props: Partial = {}, -): OSNotification => { +export const mockNotification = (props: Partial = {}): OSNotification => { return new OSNotification( deepMerge( { diff --git a/package.json b/package.json index 998820a..50293ae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,28 @@ { - "version": "1.0.0", "name": "onesignal-capacitor-plugin", + "version": "1.0.0", + "description": "OneSignal is a high volume Push Notification service for mobile apps. This is the pure Capacitor plugin for OneSignal, providing push notifications, in-app messaging, and more.", + "keywords": [ + "apns", + "capacitor", + "fcm", + "native", + "notification", + "onesignal", + "plugin", + "push", + "push notification", + "push notifications" + ], + "homepage": "https://github.com/OneSignal/OneSignal-Capacitor-SDK#readme", + "bugs": { + "url": "https://github.com/OneSignal/OneSignal-Capacitor-SDK/issues" + }, + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/OneSignal/OneSignal-Capacitor-SDK.git" + }, "files": [ "dist", "ios", @@ -8,10 +30,6 @@ "Package.swift", "OneSignalCapacitorPlugin.podspec" ], - "repository": { - "type": "git", - "url": "git+https://github.com/OneSignal/OneSignal-Capacitor-SDK.git" - }, "type": "module", "main": "./dist/index.js", "module": "./dist/index.js", @@ -34,9 +52,6 @@ "verify": "vp build && vitest run && vp lint", "docgen": "docgen --api OneSignalCapacitorPlugin --output-readme README.md --output-json dist/docs.json" }, - "peerDependencies": { - "@capacitor/core": "^7.0.0" - }, "devDependencies": { "@capacitor/core": "^7.0.0", "@capacitor/docgen": "^0.2.2", @@ -45,8 +60,15 @@ "typescript": "^5.9.3", "vite": "npm:@voidzero-dev/vite-plus-core@latest", "vite-plus": "0.1.15" - }, + "peerDependencies": { + "@capacitor/core": "^7.0.0" + }, + "overrides": { + "vite": "npm:@voidzero-dev/vite-plus-core@latest", + "vitest": "npm:@voidzero-dev/vite-plus-test@latest" + }, + "packageManager": "bun@1.3.11", "capacitor": { "ios": { "src": "ios" @@ -54,28 +76,5 @@ "android": { "src": "android" } - }, - "bugs": { - "url": "https://github.com/OneSignal/OneSignal-Capacitor-SDK/issues" - }, - "description": "OneSignal is a high volume Push Notification service for mobile apps. This is the pure Capacitor plugin for OneSignal, providing push notifications, in-app messaging, and more.", - "homepage": "https://github.com/OneSignal/OneSignal-Capacitor-SDK#readme", - "keywords": [ - "capacitor", - "plugin", - "native", - "push", - "notification", - "push notification", - "push notifications", - "apns", - "fcm", - "onesignal" - ], - "license": "MIT", - "overrides": { - "vite": "npm:@voidzero-dev/vite-plus-core@latest", - "vitest": "npm:@voidzero-dev/vite-plus-test@latest" - }, - "packageManager": "bun@1.3.11" -} \ No newline at end of file + } +} diff --git a/src/DebugNamespace.test.ts b/src/DebugNamespace.test.ts index bf1b517..c0b5c01 100644 --- a/src/DebugNamespace.test.ts +++ b/src/DebugNamespace.test.ts @@ -24,16 +24,13 @@ describe('Debug', () => { [LogLevel.Info, 4], [LogLevel.Debug, 5], [LogLevel.Verbose, 6], - ])( - 'should call plugin for setLogLevel with %s', - (logLevel, logLevelValue) => { - debug.setLogLevel(logLevel); + ])('should call plugin for setLogLevel with %s', (logLevel, logLevelValue) => { + debug.setLogLevel(logLevel); - expect(mockPlugin.setLogLevel).toHaveBeenCalledWith({ - logLevel: logLevelValue, - }); - }, - ); + expect(mockPlugin.setLogLevel).toHaveBeenCalledWith({ + logLevel: logLevelValue, + }); + }); test.each([ [LogLevel.None, 0], @@ -43,14 +40,11 @@ describe('Debug', () => { [LogLevel.Info, 4], [LogLevel.Debug, 5], [LogLevel.Verbose, 6], - ])( - 'should call plugin for setAlertLevel with %s', - (logLevel, logLevelValue) => { - debug.setAlertLevel(logLevel); - - expect(mockPlugin.setAlertLevel).toHaveBeenCalledWith({ - logLevel: logLevelValue, - }); - }, - ); + ])('should call plugin for setAlertLevel with %s', (logLevel, logLevelValue) => { + debug.setAlertLevel(logLevel); + + expect(mockPlugin.setAlertLevel).toHaveBeenCalledWith({ + logLevel: logLevelValue, + }); + }); }); diff --git a/src/InAppMessagesNamespace.test.ts b/src/InAppMessagesNamespace.test.ts index ae777df..6c0181c 100644 --- a/src/InAppMessagesNamespace.test.ts +++ b/src/InAppMessagesNamespace.test.ts @@ -49,22 +49,16 @@ describe('InAppMessages', () => { ['didDisplay', 'inAppMessageDidDisplay', messageData.didDisplay], ['willDismiss', 'inAppMessageWillDismiss', messageData.willDismiss], ['didDismiss', 'inAppMessageDidDismiss', messageData.didDismiss], - ] as const)( - 'should call addListener for %s event', - (eventType, listenerName, data) => { - const mockListener = vi.fn(); - inAppMessages.addEventListener(eventType, mockListener); - - expect(mockPlugin.addListener).toHaveBeenCalledWith( - listenerName, - expect.any(Function), - ); - - const callback = mockPlugin.addListener.mock.calls[0][1]; - callback(data); - expect(mockListener).toHaveBeenCalledWith(data); - }, - ); + ] as const)('should call addListener for %s event', (eventType, listenerName, data) => { + const mockListener = vi.fn(); + inAppMessages.addEventListener(eventType, mockListener); + + expect(mockPlugin.addListener).toHaveBeenCalledWith(listenerName, expect.any(Function)); + + const callback = mockPlugin.addListener.mock.calls[0][1]; + callback(data); + expect(mockListener).toHaveBeenCalledWith(data); + }); test('should not add listener for unknown event type', () => { const mockListener = vi.fn(); @@ -153,9 +147,7 @@ describe('InAppMessages', () => { }); test('should handle non-array input gracefully', () => { - const consoleSpy = vi - .spyOn(console, 'error') - .mockImplementation(() => {}); + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); inAppMessages.removeTriggers('not-an-array' as any); @@ -176,16 +168,13 @@ describe('InAppMessages', () => { }); describe('setPaused', () => { - test.each([[true], [false]])( - 'should call plugin for setPaused with %s', - (pauseValue) => { - inAppMessages.setPaused(pauseValue); - - expect(mockPlugin.setPaused).toHaveBeenCalledWith({ - pause: pauseValue, - }); - }, - ); + test.each([[true], [false]])('should call plugin for setPaused with %s', (pauseValue) => { + inAppMessages.setPaused(pauseValue); + + expect(mockPlugin.setPaused).toHaveBeenCalledWith({ + pause: pauseValue, + }); + }); }); describe('getPaused', () => { diff --git a/src/InAppMessagesNamespace.ts b/src/InAppMessagesNamespace.ts index 7f0eced..d74d613 100644 --- a/src/InAppMessagesNamespace.ts +++ b/src/InAppMessagesNamespace.ts @@ -12,30 +12,17 @@ import type { export default class InAppMessages { private _plugin: OneSignalCapacitorPlugin; - private _inAppMessageClickListeners: (( - action: InAppMessageClickEvent, - ) => void)[] = []; - private _willDisplayInAppMessageListeners: (( - event: InAppMessageWillDisplayEvent, - ) => void)[] = []; - private _didDisplayInAppMessageListeners: (( - event: InAppMessageDidDisplayEvent, - ) => void)[] = []; - private _willDismissInAppMessageListeners: (( - event: InAppMessageWillDismissEvent, - ) => void)[] = []; - private _didDismissInAppMessageListeners: (( - event: InAppMessageDidDismissEvent, - ) => void)[] = []; + private _inAppMessageClickListeners: ((action: InAppMessageClickEvent) => void)[] = []; + private _willDisplayInAppMessageListeners: ((event: InAppMessageWillDisplayEvent) => void)[] = []; + private _didDisplayInAppMessageListeners: ((event: InAppMessageDidDisplayEvent) => void)[] = []; + private _willDismissInAppMessageListeners: ((event: InAppMessageWillDismissEvent) => void)[] = []; + private _didDismissInAppMessageListeners: ((event: InAppMessageDidDismissEvent) => void)[] = []; constructor(plugin: OneSignalCapacitorPlugin) { this._plugin = plugin; } - private _processFunctionList( - array: ((event: T) => void)[], - param: T, - ): void { + private _processFunctionList(array: ((event: T) => void)[], param: T): void { for (let i = 0; i < array.length; i++) { array[i](param); } @@ -52,15 +39,10 @@ export default class InAppMessages { listener: (event: InAppMessageEventTypeMap[K]) => void, ): void { if (event === 'click') { - this._inAppMessageClickListeners.push( - listener as (event: InAppMessageClickEvent) => void, - ); - void this._plugin.addListener( - 'inAppMessageClick', - (json: InAppMessageClickEvent) => { - this._processFunctionList(this._inAppMessageClickListeners, json); - }, - ); + this._inAppMessageClickListeners.push(listener as (event: InAppMessageClickEvent) => void); + void this._plugin.addListener('inAppMessageClick', (json: InAppMessageClickEvent) => { + this._processFunctionList(this._inAppMessageClickListeners, json); + }); } else if (event === 'willDisplay') { this._willDisplayInAppMessageListeners.push( listener as (event: InAppMessageWillDisplayEvent) => void, @@ -68,10 +50,7 @@ export default class InAppMessages { void this._plugin.addListener( 'inAppMessageWillDisplay', (event: InAppMessageWillDisplayEvent) => { - this._processFunctionList( - this._willDisplayInAppMessageListeners, - event, - ); + this._processFunctionList(this._willDisplayInAppMessageListeners, event); }, ); } else if (event === 'didDisplay') { @@ -81,10 +60,7 @@ export default class InAppMessages { void this._plugin.addListener( 'inAppMessageDidDisplay', (event: InAppMessageDidDisplayEvent) => { - this._processFunctionList( - this._didDisplayInAppMessageListeners, - event, - ); + this._processFunctionList(this._didDisplayInAppMessageListeners, event); }, ); } else if (event === 'willDismiss') { @@ -94,10 +70,7 @@ export default class InAppMessages { void this._plugin.addListener( 'inAppMessageWillDismiss', (event: InAppMessageWillDismissEvent) => { - this._processFunctionList( - this._willDismissInAppMessageListeners, - event, - ); + this._processFunctionList(this._willDismissInAppMessageListeners, event); }, ); } else if (event === 'didDismiss') { @@ -107,10 +80,7 @@ export default class InAppMessages { void this._plugin.addListener( 'inAppMessageDidDismiss', (event: InAppMessageDidDismissEvent) => { - this._processFunctionList( - this._didDismissInAppMessageListeners, - event, - ); + this._processFunctionList(this._didDismissInAppMessageListeners, event); }, ); } @@ -180,9 +150,7 @@ export default class InAppMessages { */ removeTriggers(keys: string[]): void { if (!Array.isArray(keys)) { - console.error( - 'OneSignal: removeTriggers: argument must be of type Array', - ); + console.error('OneSignal: removeTriggers: argument must be of type Array'); } void this._plugin.removeTriggers({ keys }); diff --git a/src/LiveActivitiesNamespace.test.ts b/src/LiveActivitiesNamespace.test.ts index a3af62f..645f4be 100644 --- a/src/LiveActivitiesNamespace.test.ts +++ b/src/LiveActivitiesNamespace.test.ts @@ -1,7 +1,7 @@ import { describe, test, expect, vi, beforeEach } from 'vitest'; -import { SUB_TOKEN } from '../mocks/constants'; import { createMockPlugin } from '../mocks/capacitor'; +import { SUB_TOKEN } from '../mocks/constants'; import LiveActivities from './LiveActivitiesNamespace'; import type { LiveActivitySetupOptions } from './types/LiveActivities'; @@ -44,9 +44,7 @@ describe('LiveActivities', () => { test('should call onFailure callback on error', async () => { const onFailure = vi.fn(); - mockPlugin.enterLiveActivity.mockRejectedValue( - new Error('Enter failed'), - ); + mockPlugin.enterLiveActivity.mockRejectedValue(new Error('Enter failed')); liveActivities.enter(ACTIVITY_ID, SUB_TOKEN, undefined, onFailure); @@ -113,9 +111,7 @@ describe('LiveActivities', () => { test('should call plugin for setupDefault without options', () => { liveActivities.setupDefault(); - expect(mockPlugin.setupDefaultLiveActivity).toHaveBeenCalledWith( - undefined, - ); + expect(mockPlugin.setupDefaultLiveActivity).toHaveBeenCalledWith(undefined); }); test('should call plugin for setupDefault with options', () => { @@ -126,9 +122,7 @@ describe('LiveActivities', () => { liveActivities.setupDefault(options); - expect(mockPlugin.setupDefaultLiveActivity).toHaveBeenCalledWith( - options, - ); + expect(mockPlugin.setupDefaultLiveActivity).toHaveBeenCalledWith(options); }); }); diff --git a/src/LocationNamespace.test.ts b/src/LocationNamespace.test.ts index 7f6b527..f1d1c8d 100644 --- a/src/LocationNamespace.test.ts +++ b/src/LocationNamespace.test.ts @@ -25,16 +25,13 @@ describe('Location', () => { }); describe('setShared', () => { - test.each([[true], [false]])( - 'should call plugin for setShared with %s', - (sharedValue) => { - location.setShared(sharedValue); - - expect(mockPlugin.setLocationShared).toHaveBeenCalledWith({ - shared: sharedValue, - }); - }, - ); + test.each([[true], [false]])('should call plugin for setShared with %s', (sharedValue) => { + location.setShared(sharedValue); + + expect(mockPlugin.setLocationShared).toHaveBeenCalledWith({ + shared: sharedValue, + }); + }); }); describe('isShared', () => { diff --git a/src/NotificationReceivedEvent.test.ts b/src/NotificationReceivedEvent.test.ts index ec5737e..b1dcfca 100644 --- a/src/NotificationReceivedEvent.test.ts +++ b/src/NotificationReceivedEvent.test.ts @@ -65,9 +65,7 @@ describe('NotificationWillDisplayEvent', () => { const notification2 = notificationEvent.getNotification(); expect(notification1).toBeInstanceOf(OSNotification); - expect(notification1.notificationId).toBe( - notificationData.notificationId, - ); + expect(notification1.notificationId).toBe(notificationData.notificationId); expect(notification1).toBe(notification2); }); diff --git a/src/NotificationReceivedEvent.ts b/src/NotificationReceivedEvent.ts index 4a12a77..525cdb7 100644 --- a/src/NotificationReceivedEvent.ts +++ b/src/NotificationReceivedEvent.ts @@ -3,9 +3,7 @@ import { OSNotification } from './OSNotification'; let _pluginRef: OneSignalCapacitorPlugin | undefined; -export function _setNotificationEventPlugin( - plugin: OneSignalCapacitorPlugin, -): void { +export function _setNotificationEventPlugin(plugin: OneSignalCapacitorPlugin): void { _pluginRef = plugin; } diff --git a/src/NotificationsNamespace.test.ts b/src/NotificationsNamespace.test.ts index 0bbf71e..86e7de6 100644 --- a/src/NotificationsNamespace.test.ts +++ b/src/NotificationsNamespace.test.ts @@ -2,11 +2,9 @@ import { describe, test, expect, vi, beforeEach } from 'vitest'; import { createMockPlugin } from '../mocks/capacitor'; import { mockNotification, mockNotificationClickEvent } from '../mocks/data'; -import { NotificationWillDisplayEvent } from './NotificationReceivedEvent'; import * as helpers from './helpers'; -import Notifications, { - OSNotificationPermission, -} from './NotificationsNamespace'; +import { NotificationWillDisplayEvent } from './NotificationReceivedEvent'; +import Notifications, { OSNotificationPermission } from './NotificationsNamespace'; describe('Notifications', () => { let mockPlugin: ReturnType; @@ -43,9 +41,7 @@ describe('Notifications', () => { const mockError = new Error('Permission check failed'); mockPlugin.getPermission.mockRejectedValue(mockError); - await expect(notifications.getPermissionAsync()).rejects.toThrow( - mockError.message, - ); + await expect(notifications.getPermissionAsync()).rejects.toThrow(mockError.message); }); }); @@ -75,9 +71,7 @@ describe('Notifications', () => { const mockError = new Error('Permission check failed'); mockPlugin.permissionNative.mockRejectedValue(mockError); - await expect(notifications.permissionNative()).rejects.toThrow( - mockError.message, - ); + await expect(notifications.permissionNative()).rejects.toThrow(mockError.message); }); }); @@ -128,9 +122,7 @@ describe('Notifications', () => { const mockError = new Error('Permission check failed'); mockPlugin.canRequestPermission.mockRejectedValue(mockError); - await expect(notifications.canRequestPermission()).rejects.toThrow( - mockError.message, - ); + await expect(notifications.canRequestPermission()).rejects.toThrow(mockError.message); }); }); @@ -138,9 +130,7 @@ describe('Notifications', () => { test('should call plugin', () => { notifications.registerForProvisionalAuthorization(); - expect( - mockPlugin.registerForProvisionalAuthorization, - ).toHaveBeenCalled(); + expect(mockPlugin.registerForProvisionalAuthorization).toHaveBeenCalled(); }); test('should call handler with result', async () => { @@ -199,9 +189,7 @@ describe('Notifications', () => { const callback = mockPlugin.addListener.mock.calls[0][1]; callback(notificationData); - expect(mockListener).toHaveBeenCalledWith( - expect.any(NotificationWillDisplayEvent), - ); + expect(mockListener).toHaveBeenCalledWith(expect.any(NotificationWillDisplayEvent)); expect(mockPlugin.proceedWithWillDisplay).toHaveBeenCalledWith({ notificationId: notificationData.notificationId, }); @@ -211,10 +199,7 @@ describe('Notifications', () => { const mockListener = vi.fn(); notifications.addEventListener('permissionChange', mockListener); - expect(mockPlugin.addListener).toHaveBeenCalledWith( - 'permissionChange', - expect.any(Function), - ); + expect(mockPlugin.addListener).toHaveBeenCalledWith('permissionChange', expect.any(Function)); }); test('should call all permissionChange listeners when event fires', () => { @@ -240,19 +225,18 @@ describe('Notifications', () => { }); describe('removeEventListener', () => { - test.each([ - ['click'], - ['foregroundWillDisplay'], - ['permissionChange'], - ] as const)('should remove %s event listener', (eventType) => { - const mockListener = vi.fn(); - notifications.addEventListener(eventType, mockListener); - notifications.removeEventListener(eventType, mockListener); - - const callback = mockPlugin.addListener.mock.calls[0][1]; - callback('some-data'); - expect(mockListener).not.toHaveBeenCalled(); - }); + test.each([['click'], ['foregroundWillDisplay'], ['permissionChange']] as const)( + 'should remove %s event listener', + (eventType) => { + const mockListener = vi.fn(); + notifications.addEventListener(eventType, mockListener); + notifications.removeEventListener(eventType, mockListener); + + const callback = mockPlugin.addListener.mock.calls[0][1]; + callback('some-data'); + expect(mockListener).not.toHaveBeenCalled(); + }, + ); test('should not remove listener for unknown event type', () => { vi.spyOn(helpers, 'removeListener').mockImplementation(() => {}); diff --git a/src/NotificationsNamespace.ts b/src/NotificationsNamespace.ts index d08aa73..4f9d8a2 100644 --- a/src/NotificationsNamespace.ts +++ b/src/NotificationsNamespace.ts @@ -22,21 +22,14 @@ export type OSNotificationPermission = export default class Notifications { private _plugin: OneSignalCapacitorPlugin; private _permissionObserverList: ((event: boolean) => void)[] = []; - private _notificationClickedListeners: (( - event: NotificationClickEvent, - ) => void)[] = []; - private _notificationWillDisplayListeners: (( - event: NotificationWillDisplayEvent, - ) => void)[] = []; + private _notificationClickedListeners: ((event: NotificationClickEvent) => void)[] = []; + private _notificationWillDisplayListeners: ((event: NotificationWillDisplayEvent) => void)[] = []; constructor(plugin: OneSignalCapacitorPlugin) { this._plugin = plugin; } - private _processFunctionList( - array: ((event: T) => void)[], - param: T, - ): void { + private _processFunctionList(array: ((event: T) => void)[], param: T): void { for (let i = 0; i < array.length; i++) { array[i](param); } @@ -115,14 +108,10 @@ export default class Notifications { * @param {(response: boolean)=>void} handler * @returns void */ - registerForProvisionalAuthorization( - handler?: (response: boolean) => void, - ): void { - void this._plugin - .registerForProvisionalAuthorization() - .then((result) => { - handler?.(result.accepted); - }); + registerForProvisionalAuthorization(handler?: (response: boolean) => void): void { + void this._plugin.registerForProvisionalAuthorization().then((result) => { + handler?.(result.accepted); + }); } /** @@ -136,15 +125,10 @@ export default class Notifications { listener: (event: NotificationEventTypeMap[K]) => void, ): void { if (event === 'click') { - this._notificationClickedListeners.push( - listener as (event: NotificationClickEvent) => void, - ); - void this._plugin.addListener( - 'notificationClick', - (json: NotificationClickEvent) => { - this._processFunctionList(this._notificationClickedListeners, json); - }, - ); + this._notificationClickedListeners.push(listener as (event: NotificationClickEvent) => void); + void this._plugin.addListener('notificationClick', (json: NotificationClickEvent) => { + this._processFunctionList(this._notificationClickedListeners, json); + }); } else if (event === 'foregroundWillDisplay') { this._notificationWillDisplayListeners.push( listener as (event: NotificationWillDisplayEvent) => void, @@ -162,15 +146,9 @@ export default class Notifications { ); } else if (event === 'permissionChange') { this._permissionObserverList.push(listener as (event: boolean) => void); - void this._plugin.addListener( - 'permissionChange', - (state: { permission: boolean }) => { - this._processFunctionList( - this._permissionObserverList, - state.permission, - ); - }, - ); + void this._plugin.addListener('permissionChange', (state: { permission: boolean }) => { + this._processFunctionList(this._permissionObserverList, state.permission); + }); } } @@ -195,10 +173,7 @@ export default class Notifications { listener as (event: NotificationWillDisplayEvent) => void, ); } else if (event === 'permissionChange') { - removeListener( - this._permissionObserverList, - listener as (event: boolean) => void, - ); + removeListener(this._permissionObserverList, listener as (event: boolean) => void); } } diff --git a/src/OSNotification.ts b/src/OSNotification.ts index 8fc1950..00bf046 100644 --- a/src/OSNotification.ts +++ b/src/OSNotification.ts @@ -6,9 +6,7 @@ export type ReceivedEvent = Omit & { let _pluginRef: OneSignalCapacitorPlugin | undefined; -export function _setOSNotificationPlugin( - plugin: OneSignalCapacitorPlugin, -): void { +export function _setOSNotificationPlugin(plugin: OneSignalCapacitorPlugin): void { _pluginRef = plugin; } @@ -105,10 +103,7 @@ export class OSNotification { if (receivedEvent.androidNotificationId) { this.androidNotificationId = receivedEvent.androidNotificationId; } - if ( - receivedEvent.groupedNotifications && - receivedEvent.groupedNotifications.length - ) { + if (receivedEvent.groupedNotifications && receivedEvent.groupedNotifications.length) { this.groupedNotifications = receivedEvent.groupedNotifications; } diff --git a/src/OneSignalPlugin.ts b/src/OneSignalPlugin.ts index 1502c42..a4222e3 100644 --- a/src/OneSignalPlugin.ts +++ b/src/OneSignalPlugin.ts @@ -1,5 +1,5 @@ -import type { OneSignalCapacitorPlugin } from './definitions'; import Debug from './DebugNamespace'; +import type { OneSignalCapacitorPlugin } from './definitions'; import InAppMessages from './InAppMessagesNamespace'; import LiveActivities from './LiveActivitiesNamespace'; import Location from './LocationNamespace'; diff --git a/src/PushSubscriptionNamespace.test.ts b/src/PushSubscriptionNamespace.test.ts index fc121a5..f48fc56 100644 --- a/src/PushSubscriptionNamespace.test.ts +++ b/src/PushSubscriptionNamespace.test.ts @@ -1,15 +1,8 @@ import { describe, test, expect, vi, beforeEach } from 'vitest'; -import { - PREV_SUB_ID, - PREV_SUB_TOKEN, - SUB_ID, - SUB_TOKEN, -} from '../mocks/constants'; import { createMockPlugin } from '../mocks/capacitor'; -import PushSubscription, { - type PushSubscriptionChangedState, -} from './PushSubscriptionNamespace'; +import { PREV_SUB_ID, PREV_SUB_TOKEN, SUB_ID, SUB_TOKEN } from '../mocks/constants'; +import PushSubscription, { type PushSubscriptionChangedState } from './PushSubscriptionNamespace'; const SUB_CHANGED_STATE: PushSubscriptionChangedState = { current: { id: SUB_ID, token: SUB_TOKEN, optedIn: true }, @@ -114,9 +107,7 @@ describe('PushSubscription', () => { const mockError = new Error('Failed to get subscription ID'); mockPlugin.getPushSubscriptionId.mockRejectedValue(mockError); - await expect(pushSubscription.getIdAsync()).rejects.toThrow( - mockError.message, - ); + await expect(pushSubscription.getIdAsync()).rejects.toThrow(mockError.message); }); }); @@ -140,9 +131,7 @@ describe('PushSubscription', () => { const mockError = new Error('Failed to get push token'); mockPlugin.getPushSubscriptionToken.mockRejectedValue(mockError); - await expect(pushSubscription.getTokenAsync()).rejects.toThrow( - mockError.message, - ); + await expect(pushSubscription.getTokenAsync()).rejects.toThrow(mockError.message); }); }); @@ -165,9 +154,7 @@ describe('PushSubscription', () => { const mockError = new Error('Failed to get opted in status'); mockPlugin.getPushSubscriptionOptedIn.mockRejectedValue(mockError); - await expect(pushSubscription.getOptedInAsync()).rejects.toThrow( - mockError.message, - ); + await expect(pushSubscription.getOptedInAsync()).rejects.toThrow(mockError.message); }); }); diff --git a/src/PushSubscriptionNamespace.ts b/src/PushSubscriptionNamespace.ts index 1695a49..491bc56 100644 --- a/src/PushSubscriptionNamespace.ts +++ b/src/PushSubscriptionNamespace.ts @@ -18,9 +18,7 @@ export default class PushSubscription { private _token?: string | null; private _optedIn?: boolean; - private _subscriptionObserverList: (( - event: PushSubscriptionChangedState, - ) => void)[] = []; + private _subscriptionObserverList: ((event: PushSubscriptionChangedState) => void)[] = []; constructor(plugin: OneSignalCapacitorPlugin) { this._plugin = plugin; @@ -40,23 +38,17 @@ export default class PushSubscription { * This internal method is kept to support the deprecated methods {@link id}, {@link token}, {@link optedIn}. */ _setPropertiesAndObserver(): void { - void this._plugin - .getPushSubscriptionId() - .then((result) => { - this._id = result.id; - }); - - void this._plugin - .getPushSubscriptionToken() - .then((result) => { - this._token = result.token; - }); - - void this._plugin - .getPushSubscriptionOptedIn() - .then((result) => { - this._optedIn = result.optedIn; - }); + void this._plugin.getPushSubscriptionId().then((result) => { + this._id = result.id; + }); + + void this._plugin.getPushSubscriptionToken().then((result) => { + this._token = result.token; + }); + + void this._plugin.getPushSubscriptionOptedIn().then((result) => { + this._optedIn = result.optedIn; + }); this.addEventListener('change', (subscriptionChange) => { this._id = subscriptionChange.current.id; @@ -130,10 +122,7 @@ export default class PushSubscription { * @param {(event: PushSubscriptionChangedState)=>void} listener * @returns void */ - addEventListener( - _event: 'change', - listener: (event: PushSubscriptionChangedState) => void, - ) { + addEventListener(_event: 'change', listener: (event: PushSubscriptionChangedState) => void) { this._subscriptionObserverList.push(listener); void this._plugin.addListener( 'pushSubscriptionChange', @@ -148,10 +137,7 @@ export default class PushSubscription { * @param {(event: PushSubscriptionChangedState)=>void} listener * @returns void */ - removeEventListener( - _event: 'change', - listener: (event: PushSubscriptionChangedState) => void, - ) { + removeEventListener(_event: 'change', listener: (event: PushSubscriptionChangedState) => void) { removeListener(this._subscriptionObserverList, listener); } diff --git a/src/UserNamespace.test.ts b/src/UserNamespace.test.ts index 2842212..af97c73 100644 --- a/src/UserNamespace.test.ts +++ b/src/UserNamespace.test.ts @@ -210,10 +210,7 @@ describe('User', () => { const mockListener = vi.fn(); user.addEventListener('change', mockListener); - expect(mockPlugin.addListener).toHaveBeenCalledWith( - 'userStateChange', - expect.any(Function), - ); + expect(mockPlugin.addListener).toHaveBeenCalledWith('userStateChange', expect.any(Function)); }); test('should call all listeners when user state changes', () => { @@ -277,13 +274,9 @@ describe('User', () => { }); test('should reject Promise when plugin fails', async () => { - mockPlugin.getOnesignalId.mockRejectedValue( - new Error('Failed to get onesignal id'), - ); + mockPlugin.getOnesignalId.mockRejectedValue(new Error('Failed to get onesignal id')); - await expect(user.getOnesignalId()).rejects.toThrow( - 'Failed to get onesignal id', - ); + await expect(user.getOnesignalId()).rejects.toThrow('Failed to get onesignal id'); }); }); @@ -303,13 +296,9 @@ describe('User', () => { }); test('should reject Promise when plugin fails', async () => { - mockPlugin.getExternalId.mockRejectedValue( - new Error('Failed to get external id'), - ); + mockPlugin.getExternalId.mockRejectedValue(new Error('Failed to get external id')); - await expect(user.getExternalId()).rejects.toThrow( - 'Failed to get external id', - ); + await expect(user.getExternalId()).rejects.toThrow('Failed to get external id'); }); }); @@ -336,30 +325,22 @@ describe('User', () => { const eventName = 'purchase'; const circularObj: Record = { name: 'test' }; circularObj.self = circularObj; - const consoleSpy = vi - .spyOn(console, 'error') - .mockImplementation(() => {}); + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); user.trackEvent(eventName, circularObj); - expect(consoleSpy).toHaveBeenCalledWith( - 'Properties must be a JSON-serializable object', - ); + expect(consoleSpy).toHaveBeenCalledWith('Properties must be a JSON-serializable object'); expect(mockPlugin.trackEvent).not.toHaveBeenCalled(); consoleSpy.mockRestore(); }); test('should not call plugin when properties is an array', () => { const eventName = 'purchase'; - const consoleSpy = vi - .spyOn(console, 'error') - .mockImplementation(() => {}); + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); user.trackEvent(eventName, ['item1', 'item2'] as unknown as object); - expect(consoleSpy).toHaveBeenCalledWith( - 'Properties must be a JSON-serializable object', - ); + expect(consoleSpy).toHaveBeenCalledWith('Properties must be a JSON-serializable object'); expect(mockPlugin.trackEvent).not.toHaveBeenCalled(); consoleSpy.mockRestore(); }); diff --git a/src/UserNamespace.ts b/src/UserNamespace.ts index 9388009..11e268e 100644 --- a/src/UserNamespace.ts +++ b/src/UserNamespace.ts @@ -172,17 +172,11 @@ export default class User { * @param {(event: UserChangedState)=>void} listener * @returns void */ - addEventListener( - _event: 'change', - listener: (event: UserChangedState) => void, - ) { + addEventListener(_event: 'change', listener: (event: UserChangedState) => void) { this._userStateObserverList.push(listener); - void this._plugin.addListener( - 'userStateChange', - (state: UserChangedState) => { - this._processFunctionList(this._userStateObserverList, state); - }, - ); + void this._plugin.addListener('userStateChange', (state: UserChangedState) => { + this._processFunctionList(this._userStateObserverList, state); + }); } /** @@ -190,10 +184,7 @@ export default class User { * @param {(event: UserChangedState)=>void} listener * @returns void */ - removeEventListener( - _event: 'change', - listener: (event: UserChangedState) => void, - ) { + removeEventListener(_event: 'change', listener: (event: UserChangedState) => void) { removeListener(this._userStateObserverList, listener); } diff --git a/src/definitions.ts b/src/definitions.ts index 67feb7b..5c590cc 100644 --- a/src/definitions.ts +++ b/src/definitions.ts @@ -1,4 +1,5 @@ import type { Plugin } from '@capacitor/core'; + import type { OSNotificationPermission } from './NotificationsNamespace'; export interface OneSignalCapacitorPlugin extends Plugin { @@ -26,10 +27,7 @@ export interface OneSignalCapacitorPlugin extends Plugin { getTags(): Promise<{ tags: Record }>; getOnesignalId(): Promise<{ onesignalId: string | null }>; getExternalId(): Promise<{ externalId: string | null }>; - trackEvent(options: { - name: string; - properties?: Record; - }): Promise; + trackEvent(options: { name: string; properties?: Record }): Promise; // Push Subscription getPushSubscriptionId(): Promise<{ id: string | null }>; @@ -41,27 +39,18 @@ export interface OneSignalCapacitorPlugin extends Plugin { // Notifications getPermission(): Promise<{ permission: boolean }>; permissionNative(): Promise<{ permission: OSNotificationPermission }>; - requestPermission(options: { - fallbackToSettings: boolean; - }): Promise<{ permission: boolean }>; + requestPermission(options: { fallbackToSettings: boolean }): Promise<{ permission: boolean }>; canRequestPermission(): Promise<{ canRequest: boolean }>; registerForProvisionalAuthorization(): Promise<{ accepted: boolean }>; clearAllNotifications(): Promise; removeNotification(options: { id: number }): Promise; removeGroupedNotifications(options: { id: string }): Promise; - preventDefault(options: { - notificationId: string; - discard: boolean; - }): Promise; - proceedWithWillDisplay(options: { - notificationId: string; - }): Promise; + preventDefault(options: { notificationId: string; discard: boolean }): Promise; + proceedWithWillDisplay(options: { notificationId: string }): Promise; displayNotification(options: { notificationId: string }): Promise; // In-App Messages - addTriggers(options: { - triggers: Record; - }): Promise; + addTriggers(options: { triggers: Record }): Promise; removeTriggers(options: { keys: string[] }): Promise; clearTriggers(): Promise; setPaused(options: { pause: boolean }): Promise; @@ -70,10 +59,7 @@ export interface OneSignalCapacitorPlugin extends Plugin { // Session / Outcomes addOutcome(options: { name: string }): Promise; addUniqueOutcome(options: { name: string }): Promise; - addOutcomeWithValue(options: { - name: string; - value: number; - }): Promise; + addOutcomeWithValue(options: { name: string; value: number }): Promise; // Location requestLocationPermission(): Promise; @@ -81,18 +67,10 @@ export interface OneSignalCapacitorPlugin extends Plugin { isLocationShared(): Promise<{ shared: boolean }>; // Live Activities - enterLiveActivity(options: { - activityId: string; - token: string; - }): Promise; + enterLiveActivity(options: { activityId: string; token: string }): Promise; exitLiveActivity(options: { activityId: string }): Promise; - setPushToStartToken(options: { - activityType: string; - token: string; - }): Promise; - removePushToStartToken(options: { - activityType: string; - }): Promise; + setPushToStartToken(options: { activityType: string; token: string }): Promise; + removePushToStartToken(options: { activityType: string }): Promise; setupDefaultLiveActivity(options?: { enablePushToStart: boolean; enablePushToUpdate: boolean; diff --git a/src/index.ts b/src/index.ts index bf732c0..d4b4dc8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,8 +3,7 @@ import { registerPlugin } from '@capacitor/core'; import type { OneSignalCapacitorPlugin } from './definitions'; import { OneSignalPlugin } from './OneSignalPlugin'; -const OneSignalCapacitor = - registerPlugin('OneSignalCapacitor'); +const OneSignalCapacitor = registerPlugin('OneSignalCapacitor'); const OneSignal = new OneSignalPlugin(OneSignalCapacitor); @@ -19,10 +18,7 @@ export type { PushSubscriptionState, } from './PushSubscriptionNamespace'; -export type { - NotificationClickEvent, - NotificationClickResult, -} from './types/NotificationClicked'; +export type { NotificationClickEvent, NotificationClickResult } from './types/NotificationClicked'; export type { InAppMessageActionUrlType, diff --git a/src/types/NotificationClicked.ts b/src/types/NotificationClicked.ts index fc89607..ba5dbed 100644 --- a/src/types/NotificationClicked.ts +++ b/src/types/NotificationClicked.ts @@ -1,10 +1,7 @@ import type { NotificationWillDisplayEvent } from '../NotificationReceivedEvent'; import type { OSNotification } from '../OSNotification'; -export type NotificationEventName = - | 'click' - | 'foregroundWillDisplay' - | 'permissionChange'; +export type NotificationEventName = 'click' | 'foregroundWillDisplay' | 'permissionChange'; export type NotificationEventTypeMap = { click: NotificationClickEvent; From 2fbb81114f0c12cf538ec971f6a6790e05a353cf Mon Sep 17 00:00:00 2001 From: Fadi George Date: Thu, 2 Apr 2026 16:27:08 -0700 Subject: [PATCH 08/13] build(lint): disable unbound-method rule for tests --- vite.config.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/vite.config.ts b/vite.config.ts index ede3090..1d7cb71 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -12,6 +12,14 @@ export default defineConfig({ }, lint: { options: { typeAware: true, typeCheck: true }, + overrides: [ + { + files: ['**/*.test.ts', '**/*.test.tsx'], + rules: { + 'typescript/unbound-method': 'off', + }, + }, + ], }, pack: { entry: 'src/index.ts', From dc81eff46396339422c816b9fb66c1e03385dcd9 Mon Sep 17 00:00:00 2001 From: Fadi George Date: Thu, 2 Apr 2026 16:32:40 -0700 Subject: [PATCH 09/13] build: enable script sorting in package.json --- package.json | 6 +++--- vite.config.ts | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 50293ae..370d0b7 100644 --- a/package.json +++ b/package.json @@ -42,15 +42,15 @@ }, "scripts": { "build": "vp pack", + "docgen": "docgen --api OneSignalCapacitorPlugin --output-readme README.md --output-json dist/docs.json", "format": "vp fmt", "format:check": "vp fmt --check", "lint": "vp check", "lint:fix": "vp check --fix", "test": "vitest", - "test:run": "vitest run", "test:coverage": "vitest run --coverage", - "verify": "vp build && vitest run && vp lint", - "docgen": "docgen --api OneSignalCapacitorPlugin --output-readme README.md --output-json dist/docs.json" + "test:run": "vitest run", + "verify": "vp build && vitest run && vp lint" }, "devDependencies": { "@capacitor/core": "^7.0.0", diff --git a/vite.config.ts b/vite.config.ts index 1d7cb71..5a580bf 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -9,6 +9,9 @@ export default defineConfig({ sortImports: { enabled: true, }, + sortPackageJson: { + sortScripts: true, + }, }, lint: { options: { typeAware: true, typeCheck: true }, From 89dd206ab83efab6218b63b9131d788d4eae59ea Mon Sep 17 00:00:00 2001 From: Fadi George Date: Thu, 2 Apr 2026 16:40:43 -0700 Subject: [PATCH 10/13] test: add vitest coverage support --- bun.lock | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 7 +++---- 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/bun.lock b/bun.lock index cf3bb68..978cb06 100644 --- a/bun.lock +++ b/bun.lock @@ -8,6 +8,7 @@ "@capacitor/core": "^7.0.0", "@capacitor/docgen": "^0.2.2", "@types/bun": "latest", + "@vitest/coverage-v8": "^4.1.2", "happy-dom": "^20.0.8", "typescript": "^5.9.3", "vite": "npm:@voidzero-dev/vite-plus-core@latest", @@ -23,10 +24,26 @@ "vitest": "npm:@voidzero-dev/vite-plus-test@latest", }, "packages": { + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + + "@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="], + + "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "@bcoe/v8-coverage": ["@bcoe/v8-coverage@1.0.2", "", {}, "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA=="], + "@capacitor/core": ["@capacitor/core@7.6.1", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-nsNouCMxgYenyemy20sZwZYMtFi93LSZVWm2KqHTYIPIDgwx24+PzwHIdRQBZdK7hpvD5jQEhWuo/QyLLnAyBQ=="], "@capacitor/docgen": ["@capacitor/docgen@0.2.2", "", { "dependencies": { "@types/node": "^14.18.0", "colorette": "^2.0.16", "github-slugger": "^1.4.0", "minimist": "^1.2.5", "typescript": "~4.2.4" }, "bin": { "docgen": "bin/docgen" } }, "sha512-617jB5DlKjUljxfoANORWeqffsHqHekckH48oslKWFGzfCHYIAEPfJfMa5M4mVSzn2ifSvWB5C3st4augwhl5w=="], + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + "@oxc-project/runtime": ["@oxc-project/runtime@0.122.0", "", {}, "sha512-vevyz3bNjevQFCV2Yg5o6Sp9BSoiYiJVymMrzA3S1ZGj4J8ak4YiywhFyQMueQ3UNlJU6HZOZYDy70TUc99aHw=="], "@oxc-project/types": ["@oxc-project/types@0.122.0", "", {}, "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA=="], @@ -129,12 +146,20 @@ "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + "@types/node": ["@types/node@14.18.63", "", {}, "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ=="], "@types/whatwg-mimetype": ["@types/whatwg-mimetype@3.0.2", "", {}, "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA=="], "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], + "@vitest/coverage-v8": ["@vitest/coverage-v8@4.1.2", "", { "dependencies": { "@bcoe/v8-coverage": "^1.0.2", "@vitest/utils": "4.1.2", "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-reports": "^3.2.0", "magicast": "^0.5.2", "obug": "^2.1.1", "std-env": "^4.0.0-rc.1", "tinyrainbow": "^3.1.0" }, "peerDependencies": { "@vitest/browser": "4.1.2", "vitest": "4.1.2" }, "optionalPeers": ["@vitest/browser"] }, "sha512-sPK//PHO+kAkScb8XITeB1bf7fsk85Km7+rt4eeuRR3VS1/crD47cmV5wicisJmjNdfeokTZwjMk4Mj2d58Mgg=="], + + "@vitest/pretty-format": ["@vitest/pretty-format@4.1.2", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA=="], + + "@vitest/utils": ["@vitest/utils@4.1.2", "", { "dependencies": { "@vitest/pretty-format": "4.1.2", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ=="], + "@voidzero-dev/vite-plus-core": ["@voidzero-dev/vite-plus-core@0.1.15", "", { "dependencies": { "@oxc-project/runtime": "=0.122.0", "@oxc-project/types": "=0.122.0", "lightningcss": "^1.30.2", "postcss": "^8.5.6" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@arethetypeswrong/core": "^0.18.1", "@tsdown/css": "0.21.7", "@tsdown/exe": "0.21.7", "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", "publint": "^0.3.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "typescript": "^5.0.0 || ^6.0.0", "unplugin-unused": "^0.5.0", "yaml": "^2.4.2" }, "optionalPeers": ["@arethetypeswrong/core", "@tsdown/css", "@tsdown/exe", "@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "publint", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "typescript", "unplugin-unused", "yaml"] }, "sha512-0qAbqwcvQwiC8xGKSSuFtsjJUEM4LZzpXF7dffRazghGEQ8HH8NAvVryp/PiMSFwreJlV3rujwL4amKjnwCHpg=="], "@voidzero-dev/vite-plus-darwin-arm64": ["@voidzero-dev/vite-plus-darwin-arm64@0.1.15", "", { "os": "darwin", "cpu": "arm64" }, "sha512-arFq8phXg96rQ5J+FYvkBYdEGxIhP1ePAXlUeQY2hV8hJPzse+CdxusWxcjfpTgvFi+dpsKzE4KSNS22PyBo7w=="], @@ -157,12 +182,16 @@ "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + "ast-v8-to-istanbul": ["ast-v8-to-istanbul@1.0.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.31", "estree-walker": "^3.0.3", "js-tokens": "^10.0.0" } }, "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg=="], + "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], "cac": ["cac@7.0.0", "", {}, "sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ=="], "colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="], + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], @@ -171,6 +200,8 @@ "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], @@ -179,8 +210,20 @@ "happy-dom": ["happy-dom@20.8.9", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.18.3" } }, "sha512-Tz23LR9T9jOGVZm2x1EPdXqwA37G/owYMxRwU0E4miurAtFsPMQ1d2Jc2okUaSjZqAFz2oEn3FLXC5a0a+siyA=="], + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="], + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="], + + "istanbul-lib-report": ["istanbul-lib-report@3.0.1", "", { "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw=="], + + "istanbul-reports": ["istanbul-reports@3.2.0", "", { "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA=="], + + "js-tokens": ["js-tokens@10.0.0", "", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="], + "jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], @@ -207,6 +250,10 @@ "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], + "magicast": ["magicast@0.5.2", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "source-map-js": "^1.2.1" } }, "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ=="], + + "make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="], + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], @@ -233,6 +280,8 @@ "postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], + "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], @@ -243,6 +292,8 @@ "std-env": ["std-env@4.0.0", "", {}, "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ=="], + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], "tinyexec": ["tinyexec@1.0.4", "", {}, "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw=="], @@ -251,6 +302,8 @@ "tinypool": ["tinypool@2.1.0", "", {}, "sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw=="], + "tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="], + "totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="], "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], @@ -263,6 +316,8 @@ "vite-plus": ["vite-plus@0.1.15", "", { "dependencies": { "@oxc-project/types": "=0.122.0", "@voidzero-dev/vite-plus-core": "0.1.15", "@voidzero-dev/vite-plus-test": "0.1.15", "cac": "^7.0.0", "cross-spawn": "^7.0.5", "jsonc-parser": "^3.3.1", "oxfmt": "=0.43.0", "oxlint": "=1.58.0", "oxlint-tsgolint": "=0.18.1", "picocolors": "^1.1.1" }, "optionalDependencies": { "@voidzero-dev/vite-plus-darwin-arm64": "0.1.15", "@voidzero-dev/vite-plus-darwin-x64": "0.1.15", "@voidzero-dev/vite-plus-linux-arm64-gnu": "0.1.15", "@voidzero-dev/vite-plus-linux-arm64-musl": "0.1.15", "@voidzero-dev/vite-plus-linux-x64-gnu": "0.1.15", "@voidzero-dev/vite-plus-linux-x64-musl": "0.1.15", "@voidzero-dev/vite-plus-win32-arm64-msvc": "0.1.15", "@voidzero-dev/vite-plus-win32-x64-msvc": "0.1.15" }, "bin": { "vp": "bin/vp", "oxfmt": "bin/oxfmt", "oxlint": "bin/oxlint" } }, "sha512-PBUvTq4D4BJcuusCA3mrSQmXcGVdPX9CIPpS7Y6+T+LbDsrmAZ+ITl9FzuE6zXvpT6Nht9cpHtwOLJw7m3adog=="], + "vitest": ["@voidzero-dev/vite-plus-test@0.1.15", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@voidzero-dev/vite-plus-core": "0.1.15", "es-module-lexer": "^1.7.0", "obug": "^2.1.1", "pixelmatch": "^7.1.0", "pngjs": "^7.0.0", "sirv": "^3.0.2", "std-env": "^4.0.0", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "ws": "^8.18.3" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/ui": "4.1.2", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/ui", "happy-dom", "jsdom"] }, "sha512-jxMUEX6PDpzMUz+KOVOoB8HiODMf5mWjH19pof0k9l/RZT4iLDyVXB+p9PoWjbVrEMMGzq9BTOVob7wfOZeZEA=="], + "whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="], "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], diff --git a/package.json b/package.json index 370d0b7..c96ab62 100644 --- a/package.json +++ b/package.json @@ -47,15 +47,14 @@ "format:check": "vp fmt --check", "lint": "vp check", "lint:fix": "vp check --fix", - "test": "vitest", - "test:coverage": "vitest run --coverage", - "test:run": "vitest run", - "verify": "vp build && vitest run && vp lint" + "test": "vp test", + "verify": "vp build && vp test run && vp lint" }, "devDependencies": { "@capacitor/core": "^7.0.0", "@capacitor/docgen": "^0.2.2", "@types/bun": "latest", + "@vitest/coverage-v8": "^4.1.2", "happy-dom": "^20.0.8", "typescript": "^5.9.3", "vite": "npm:@voidzero-dev/vite-plus-core@latest", From e1415d0081dea709db4049fbb3bba9fcde5f7a80 Mon Sep 17 00:00:00 2001 From: Fadi George Date: Thu, 16 Apr 2026 13:29:46 -0700 Subject: [PATCH 11/13] chore: convert android build script to Kotlin DSL Rename `android/build.gradle` to `android/build.gradle.kts` and port the build configuration to Kotlin DSL for better type safety and IDE support. Update the release workflow to reference the new filename. Made-with: Cursor --- .github/workflows/create-release-pr.yml | 8 +-- android/build.gradle | 56 ----------------- android/build.gradle.kts | 84 +++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 60 deletions(-) delete mode 100644 android/build.gradle create mode 100644 android/build.gradle.kts diff --git a/.github/workflows/create-release-pr.yml b/.github/workflows/create-release-pr.yml index 4500b05..b55bfd7 100644 --- a/.github/workflows/create-release-pr.yml +++ b/.github/workflows/create-release-pr.yml @@ -92,7 +92,7 @@ jobs: # Get versions from target branch (not the release branch) CURRENT_VERSION=$(git show origin/${{ inputs.target_branch }}:package.json | jq -r .version) - ANDROID_VERSION=$(git show origin/${{ inputs.target_branch }}:android/build.gradle | grep "com.onesignal:OneSignal:" | sed -E "s/.*OneSignal:([0-9.]+).*/\1/") + ANDROID_VERSION=$(git show origin/${{ inputs.target_branch }}:android/build.gradle.kts | grep "com.onesignal:OneSignal:" | sed -E "s/.*OneSignal:([0-9.]+).*/\1/") IOS_VERSION=$(git show origin/${{ inputs.target_branch }}:OneSignalCapacitorPlugin.podspec | grep "OneSignalXCFramework" | sed -E "s/.*'([0-9.]+)'.*/\1/") echo "capacitor_from=$CURRENT_VERSION" >> $GITHUB_OUTPUT @@ -114,9 +114,9 @@ jobs: exit 1 fi - # Update Android SDK version in build.gradle (handles both api '...' and api('...') syntax) - sed -i '' -E "s/(com\.onesignal:OneSignal:)[0-9.]+/\1$VERSION/" android/build.gradle - echo "✓ Updated android/build.gradle with Android SDK ${VERSION}" + # Update Android SDK version in build.gradle.kts + sed -i '' -E "s/(com\.onesignal:OneSignal:)[0-9.]+/\1$VERSION/" android/build.gradle.kts + echo "✓ Updated android/build.gradle.kts with Android SDK ${VERSION}" # Only commit if there are changes git add -A diff --git a/android/build.gradle b/android/build.gradle deleted file mode 100644 index 08abf8d..0000000 --- a/android/build.gradle +++ /dev/null @@ -1,56 +0,0 @@ -ext { - junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2' - androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.0' -} - -buildscript { - ext.kotlin_version = project.hasProperty("kotlin_version") ? rootProject.ext.kotlin_version : '1.9.25' - repositories { - google() - mavenCentral() - } - dependencies { - classpath 'com.android.tools.build:gradle:8.7.3' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' - -android { - namespace "com.onesignal.capacitor" - compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35 - defaultConfig { - minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23 - targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35 - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 - } - kotlinOptions { - jvmTarget = '17' - } -} - -repositories { - google() - mavenCentral() -} - -dependencies { - implementation project(':capacitor-android') - implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion" - implementation 'com.onesignal:OneSignal:5.7.6' - testImplementation "junit:junit:$junitVersion" - androidTestImplementation "androidx.test.ext:junit:1.2.1" - androidTestImplementation "androidx.test.espresso:espresso-core:3.6.1" -} diff --git a/android/build.gradle.kts b/android/build.gradle.kts new file mode 100644 index 0000000..e305176 --- /dev/null +++ b/android/build.gradle.kts @@ -0,0 +1,84 @@ +val junitVersion: String = if (project.hasProperty("junitVersion")) { + rootProject.extra["junitVersion"] as String +} else { + "4.13.2" +} + +val androidxAppCompatVersion: String = if (project.hasProperty("androidxAppCompatVersion")) { + rootProject.extra["androidxAppCompatVersion"] as String +} else { + "1.7.0" +} + +extra["junitVersion"] = junitVersion +extra["androidxAppCompatVersion"] = androidxAppCompatVersion + +buildscript { + val kotlinVersion: String = if (project.hasProperty("kotlin_version")) { + rootProject.extra["kotlin_version"] as String + } else { + "1.9.25" + } + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.android.tools.build:gradle:8.7.3") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") + } +} + +apply(plugin = "com.android.library") +apply(plugin = "kotlin-android") + +configure { + namespace = "com.onesignal.capacitor" + compileSdk = if (project.hasProperty("compileSdkVersion")) { + rootProject.extra["compileSdkVersion"] as Int + } else { + 35 + } + defaultConfig { + minSdk = if (project.hasProperty("minSdkVersion")) { + rootProject.extra["minSdkVersion"] as Int + } else { + 23 + } + @Suppress("DEPRECATION") + targetSdk = if (project.hasProperty("targetSdkVersion")) { + rootProject.extra["targetSdkVersion"] as Int + } else { + 35 + } + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + buildTypes { + getByName("release") { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } +} + +tasks.withType().configureEach { + kotlinOptions.jvmTarget = "17" +} + +repositories { + google() + mavenCentral() +} + +dependencies { + "implementation"(project(":capacitor-android")) + "implementation"("androidx.appcompat:appcompat:$androidxAppCompatVersion") + "implementation"("com.onesignal:OneSignal:5.7.6") + "testImplementation"("junit:junit:$junitVersion") + "androidTestImplementation"("androidx.test.ext:junit:1.2.1") + "androidTestImplementation"("androidx.test.espresso:espresso-core:3.6.1") +} From 9fefecb53d4ed2303f58a10f39cdf8ee28c3579a Mon Sep 17 00:00:00 2001 From: Fadi George Date: Thu, 16 Apr 2026 14:04:19 -0700 Subject: [PATCH 12/13] refactor: return promises from void methods --- src/InAppMessagesNamespace.test.ts | 28 ++++----- src/InAppMessagesNamespace.ts | 30 +++++----- src/LiveActivitiesNamespace.test.ts | 20 +++---- src/LiveActivitiesNamespace.ts | 16 ++--- src/LocationNamespace.test.ts | 4 +- src/LocationNamespace.ts | 6 +- src/NotificationsNamespace.test.ts | 12 ++-- src/NotificationsNamespace.ts | 18 +++--- src/OneSignalPlugin.ts | 18 +++--- src/PushSubscriptionNamespace.test.ts | 8 +-- src/PushSubscriptionNamespace.ts | 12 ++-- src/SessionNamespace.test.ts | 12 ++-- src/SessionNamespace.ts | 18 +++--- src/UserNamespace.test.ts | 84 +++++++++++++------------- src/UserNamespace.ts | 86 +++++++++++++-------------- src/index.test.ts | 10 ++-- 16 files changed, 191 insertions(+), 191 deletions(-) diff --git a/src/InAppMessagesNamespace.test.ts b/src/InAppMessagesNamespace.test.ts index 6c0181c..9ac2bb5 100644 --- a/src/InAppMessagesNamespace.test.ts +++ b/src/InAppMessagesNamespace.test.ts @@ -99,8 +99,8 @@ describe('InAppMessages', () => { }); describe('addTrigger', () => { - test('should call plugin for addTrigger', () => { - inAppMessages.addTrigger('key', 'value'); + test('should call plugin for addTrigger', async () => { + await inAppMessages.addTrigger('key', 'value'); expect(mockPlugin.addTriggers).toHaveBeenCalledWith({ triggers: { key: 'value' }, @@ -109,18 +109,18 @@ describe('InAppMessages', () => { }); describe('addTriggers', () => { - test('should call plugin for addTriggers with string values', () => { + test('should call plugin for addTriggers with string values', async () => { const triggers = { key1: 'value1', key2: 'value2' }; - inAppMessages.addTriggers(triggers); + await inAppMessages.addTriggers(triggers); expect(mockPlugin.addTriggers).toHaveBeenCalledWith({ triggers }); }); - test('should convert non-string values to JSON strings', () => { + test('should convert non-string values to JSON strings', async () => { const triggers = { key1: 'value1', key2: 123, key3: true }; // @ts-expect-error - testing non-string values - inAppMessages.addTriggers(triggers); + await inAppMessages.addTriggers(triggers); expect(mockPlugin.addTriggers).toHaveBeenCalledWith({ triggers: { key1: 'value1', key2: '123', key3: 'true' }, @@ -129,8 +129,8 @@ describe('InAppMessages', () => { }); describe('removeTrigger', () => { - test('should call plugin for removeTrigger', () => { - inAppMessages.removeTrigger('key'); + test('should call plugin for removeTrigger', async () => { + await inAppMessages.removeTrigger('key'); expect(mockPlugin.removeTriggers).toHaveBeenCalledWith({ keys: ['key'], @@ -139,17 +139,17 @@ describe('InAppMessages', () => { }); describe('removeTriggers', () => { - test('should call plugin for removeTriggers with valid array', () => { + test('should call plugin for removeTriggers with valid array', async () => { const keys = ['key1', 'key2']; - inAppMessages.removeTriggers(keys); + await inAppMessages.removeTriggers(keys); expect(mockPlugin.removeTriggers).toHaveBeenCalledWith({ keys }); }); - test('should handle non-array input gracefully', () => { + test('should handle non-array input gracefully', async () => { const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); - inAppMessages.removeTriggers('not-an-array' as any); + await inAppMessages.removeTriggers('not-an-array' as any); expect(consoleSpy).toHaveBeenCalledWith( 'OneSignal: removeTriggers: argument must be of type Array', @@ -160,8 +160,8 @@ describe('InAppMessages', () => { }); describe('clearTriggers', () => { - test('should call plugin for clearTriggers', () => { - inAppMessages.clearTriggers(); + test('should call plugin for clearTriggers', async () => { + await inAppMessages.clearTriggers(); expect(mockPlugin.clearTriggers).toHaveBeenCalled(); }); diff --git a/src/InAppMessagesNamespace.ts b/src/InAppMessagesNamespace.ts index d74d613..485f60a 100644 --- a/src/InAppMessagesNamespace.ts +++ b/src/InAppMessagesNamespace.ts @@ -113,55 +113,55 @@ export default class InAppMessages { * Add a trigger for the current user. Triggers are currently explicitly used to determine whether a specific IAM should be displayed to the user. * @param {string} key * @param {string} value - * @returns void + * @returns Promise */ - addTrigger(key: string, value: string): void { - this.addTriggers({ [key]: value }); + addTrigger(key: string, value: string): Promise { + return this.addTriggers({ [key]: value }); } /** * Add multiple triggers for the current user. * @param {[key: string]: string} triggers - * @returns void + * @returns Promise */ - addTriggers(triggers: { [key: string]: string }): void { + addTriggers(triggers: { [key: string]: string }): Promise { Object.keys(triggers).forEach(function (key) { if (typeof triggers[key] !== 'string') { triggers[key] = JSON.stringify(triggers[key]); } }); - void this._plugin.addTriggers({ triggers }); + return this._plugin.addTriggers({ triggers }); } /** * Remove the trigger with the provided key from the current user. * @param {string} key - * @returns void + * @returns Promise */ - removeTrigger(key: string): void { - this.removeTriggers([key]); + removeTrigger(key: string): Promise { + return this.removeTriggers([key]); } /** * Remove multiple triggers from the current user. * @param {string[]} keys - * @returns void + * @returns Promise */ - removeTriggers(keys: string[]): void { + removeTriggers(keys: string[]): Promise { if (!Array.isArray(keys)) { console.error('OneSignal: removeTriggers: argument must be of type Array'); } - void this._plugin.removeTriggers({ keys }); + return this._plugin.removeTriggers({ keys }); } /** * Clear all triggers from the current user. - * @returns void + * @returns Promise */ - clearTriggers(): void { - void this._plugin.clearTriggers(); + clearTriggers(): Promise { + return this._plugin.clearTriggers(); } /** diff --git a/src/LiveActivitiesNamespace.test.ts b/src/LiveActivitiesNamespace.test.ts index 645f4be..9f4cfd6 100644 --- a/src/LiveActivitiesNamespace.test.ts +++ b/src/LiveActivitiesNamespace.test.ts @@ -87,8 +87,8 @@ describe('LiveActivities', () => { }); describe('setPushToStartToken', () => { - test('should call plugin for setPushToStartToken', () => { - liveActivities.setPushToStartToken(ACTIVITY_TYPE, SUB_TOKEN); + test('should call plugin for setPushToStartToken', async () => { + await liveActivities.setPushToStartToken(ACTIVITY_TYPE, SUB_TOKEN); expect(mockPlugin.setPushToStartToken).toHaveBeenCalledWith({ activityType: ACTIVITY_TYPE, @@ -98,8 +98,8 @@ describe('LiveActivities', () => { }); describe('removePushToStartToken', () => { - test('should call plugin for removePushToStartToken', () => { - liveActivities.removePushToStartToken(ACTIVITY_TYPE); + test('should call plugin for removePushToStartToken', async () => { + await liveActivities.removePushToStartToken(ACTIVITY_TYPE); expect(mockPlugin.removePushToStartToken).toHaveBeenCalledWith({ activityType: ACTIVITY_TYPE, @@ -108,30 +108,30 @@ describe('LiveActivities', () => { }); describe('setupDefault', () => { - test('should call plugin for setupDefault without options', () => { - liveActivities.setupDefault(); + test('should call plugin for setupDefault without options', async () => { + await liveActivities.setupDefault(); expect(mockPlugin.setupDefaultLiveActivity).toHaveBeenCalledWith(undefined); }); - test('should call plugin for setupDefault with options', () => { + test('should call plugin for setupDefault with options', async () => { const options: LiveActivitySetupOptions = { enablePushToStart: true, enablePushToUpdate: false, }; - liveActivities.setupDefault(options); + await liveActivities.setupDefault(options); expect(mockPlugin.setupDefaultLiveActivity).toHaveBeenCalledWith(options); }); }); describe('startDefault', () => { - test('should call plugin for startDefault', () => { + test('should call plugin for startDefault', async () => { const attributes = { key1: 'value1', key2: 'value2' }; const content = { title: 'Test Title', message: 'Test Message' }; - liveActivities.startDefault(ACTIVITY_ID, attributes, content); + await liveActivities.startDefault(ACTIVITY_ID, attributes, content); expect(mockPlugin.startDefaultLiveActivity).toHaveBeenCalledWith({ activityId: ACTIVITY_ID, diff --git a/src/LiveActivitiesNamespace.ts b/src/LiveActivitiesNamespace.ts index c4ca170..82a6acc 100644 --- a/src/LiveActivitiesNamespace.ts +++ b/src/LiveActivitiesNamespace.ts @@ -60,8 +60,8 @@ export default class LiveActivities { * @param {string} activityType * @param {string} token */ - setPushToStartToken(activityType: string, token: string) { - void this._plugin.setPushToStartToken({ activityType, token }); + setPushToStartToken(activityType: string, token: string): Promise { + return this._plugin.setPushToStartToken({ activityType, token }); } /** @@ -69,8 +69,8 @@ export default class LiveActivities { * for the `activityType`. Only applies to iOS. * @param {string} activityType */ - removePushToStartToken(activityType: string) { - void this._plugin.removePushToStartToken({ activityType }); + removePushToStartToken(activityType: string): Promise { + return this._plugin.removePushToStartToken({ activityType }); } /** @@ -78,8 +78,8 @@ export default class LiveActivities { * Only applies to iOS. * @param {LiveActivitySetupOptions} options */ - setupDefault(options?: LiveActivitySetupOptions) { - void this._plugin.setupDefaultLiveActivity(options); + setupDefault(options?: LiveActivitySetupOptions): Promise { + return this._plugin.setupDefaultLiveActivity(options); } /** @@ -93,8 +93,8 @@ export default class LiveActivities { activityId: string, attributes: Record, content: Record, - ) { - void this._plugin.startDefaultLiveActivity({ + ): Promise { + return this._plugin.startDefaultLiveActivity({ activityId, attributes, content, diff --git a/src/LocationNamespace.test.ts b/src/LocationNamespace.test.ts index f1d1c8d..832cbe9 100644 --- a/src/LocationNamespace.test.ts +++ b/src/LocationNamespace.test.ts @@ -17,8 +17,8 @@ describe('Location', () => { }); describe('requestPermission', () => { - test('should call plugin for requestPermission', () => { - location.requestPermission(); + test('should call plugin for requestPermission', async () => { + await location.requestPermission(); expect(mockPlugin.requestLocationPermission).toHaveBeenCalled(); }); diff --git a/src/LocationNamespace.ts b/src/LocationNamespace.ts index e4f113c..542d519 100644 --- a/src/LocationNamespace.ts +++ b/src/LocationNamespace.ts @@ -9,10 +9,10 @@ export default class Location { /** * Prompts the user for location permissions to allow geotagging from the OneSignal dashboard. - * @returns void + * @returns Promise */ - requestPermission(): void { - void this._plugin.requestLocationPermission(); + requestPermission(): Promise { + return this._plugin.requestLocationPermission(); } /** diff --git a/src/NotificationsNamespace.test.ts b/src/NotificationsNamespace.test.ts index 86e7de6..392a3c0 100644 --- a/src/NotificationsNamespace.test.ts +++ b/src/NotificationsNamespace.test.ts @@ -249,17 +249,17 @@ describe('Notifications', () => { }); describe('clearAll', () => { - test('should call plugin for clearAll', () => { - notifications.clearAll(); + test('should call plugin for clearAll', async () => { + await notifications.clearAll(); expect(mockPlugin.clearAllNotifications).toHaveBeenCalled(); }); }); describe('removeNotification', () => { - test('should call plugin for removeNotification', () => { + test('should call plugin for removeNotification', async () => { const notificationId = 123; - notifications.removeNotification(notificationId); + await notifications.removeNotification(notificationId); expect(mockPlugin.removeNotification).toHaveBeenCalledWith({ id: notificationId, @@ -268,9 +268,9 @@ describe('Notifications', () => { }); describe('removeGroupedNotifications', () => { - test('should call plugin for removeGroupedNotifications', () => { + test('should call plugin for removeGroupedNotifications', async () => { const groupId = 'test-group-id'; - notifications.removeGroupedNotifications(groupId); + await notifications.removeGroupedNotifications(groupId); expect(mockPlugin.removeGroupedNotifications).toHaveBeenCalledWith({ id: groupId, diff --git a/src/NotificationsNamespace.ts b/src/NotificationsNamespace.ts index 4f9d8a2..591801b 100644 --- a/src/NotificationsNamespace.ts +++ b/src/NotificationsNamespace.ts @@ -179,29 +179,29 @@ export default class Notifications { /** * Removes all OneSignal notifications. - * @returns void + * @returns Promise */ - clearAll(): void { - void this._plugin.clearAllNotifications(); + clearAll(): Promise { + return this._plugin.clearAllNotifications(); } /** * Android only. * Cancels a single OneSignal notification based on its Android notification integer ID. * @param {number} id - notification id to cancel - * @returns void + * @returns Promise */ - removeNotification(id: number): void { - void this._plugin.removeNotification({ id }); + removeNotification(id: number): Promise { + return this._plugin.removeNotification({ id }); } /** * Android only. * Cancels a group of OneSignal notifications with the provided group key. * @param {string} id - notification group id to cancel - * @returns void + * @returns Promise */ - removeGroupedNotifications(id: string): void { - void this._plugin.removeGroupedNotifications({ id }); + removeGroupedNotifications(id: string): Promise { + return this._plugin.removeGroupedNotifications({ id }); } } diff --git a/src/OneSignalPlugin.ts b/src/OneSignalPlugin.ts index a4222e3..a4fe231 100644 --- a/src/OneSignalPlugin.ts +++ b/src/OneSignalPlugin.ts @@ -38,12 +38,12 @@ export class OneSignalPlugin { /** * Initializes the OneSignal SDK. This should be called during startup of the application. * @param {string} appId - * @returns void + * @returns Promise */ - initialize(appId: string): void { + initialize(appId: string): Promise { this._appID = appId; - void this._plugin.initialize({ appId: this._appID }).then(() => { + return this._plugin.initialize({ appId: this._appID }).then(() => { this.User.pushSubscription._setPropertiesAndObserver(); this.Notifications._setPropertyAndObserver(); }); @@ -52,18 +52,18 @@ export class OneSignalPlugin { /** * Login to OneSignal under the user identified by the [externalId] provided. The act of logging a user into the OneSignal SDK will switch the [user] context to that specific user. * @param {string} externalId - * @returns void + * @returns Promise */ - login(externalId: string): void { - void this._plugin.login({ externalId }); + login(externalId: string): Promise { + return this._plugin.login({ externalId }); } /** * Logout the user previously logged in via [login]. The [user] property now references a new device-scoped user. - * @returns void + * @returns Promise */ - logout(): void { - void this._plugin.logout(); + logout(): Promise { + return this._plugin.logout(); } /** diff --git a/src/PushSubscriptionNamespace.test.ts b/src/PushSubscriptionNamespace.test.ts index f48fc56..92be1d7 100644 --- a/src/PushSubscriptionNamespace.test.ts +++ b/src/PushSubscriptionNamespace.test.ts @@ -210,15 +210,15 @@ describe('PushSubscription', () => { }); describe('optIn', () => { - test('should call plugin', () => { - pushSubscription.optIn(); + test('should call plugin', async () => { + await pushSubscription.optIn(); expect(mockPlugin.optInPushSubscription).toHaveBeenCalled(); }); }); describe('optOut', () => { - test('should call plugin', () => { - pushSubscription.optOut(); + test('should call plugin', async () => { + await pushSubscription.optOut(); expect(mockPlugin.optOutPushSubscription).toHaveBeenCalled(); }); }); diff --git a/src/PushSubscriptionNamespace.ts b/src/PushSubscriptionNamespace.ts index 491bc56..83c9638 100644 --- a/src/PushSubscriptionNamespace.ts +++ b/src/PushSubscriptionNamespace.ts @@ -143,17 +143,17 @@ export default class PushSubscription { /** * Call this method to receive push notifications on the device or to resume receiving of push notifications after calling optOut. If needed, this method will prompt the user for push notifications permission. - * @returns void + * @returns Promise */ - optIn(): void { - void this._plugin.optInPushSubscription(); + optIn(): Promise { + return this._plugin.optInPushSubscription(); } /** * If at any point you want the user to stop receiving push notifications on the current device (regardless of system-level permission status), you can call this method to opt out. - * @returns void + * @returns Promise */ - optOut(): void { - void this._plugin.optOutPushSubscription(); + optOut(): Promise { + return this._plugin.optOutPushSubscription(); } } diff --git a/src/SessionNamespace.test.ts b/src/SessionNamespace.test.ts index 13d917e..a995668 100644 --- a/src/SessionNamespace.test.ts +++ b/src/SessionNamespace.test.ts @@ -17,10 +17,10 @@ describe('Session', () => { }); describe('addOutcome', () => { - test('should call plugin with correct parameters', () => { + test('should call plugin with correct parameters', async () => { const outcomeName = 'test_outcome'; - session.addOutcome(outcomeName); + await session.addOutcome(outcomeName); expect(mockPlugin.addOutcome).toHaveBeenCalledWith({ name: outcomeName, @@ -29,10 +29,10 @@ describe('Session', () => { }); describe('addUniqueOutcome', () => { - test('should call plugin with correct parameters', () => { + test('should call plugin with correct parameters', async () => { const outcomeName = 'unique_test_outcome'; - session.addUniqueOutcome(outcomeName); + await session.addUniqueOutcome(outcomeName); expect(mockPlugin.addUniqueOutcome).toHaveBeenCalledWith({ name: outcomeName, @@ -41,11 +41,11 @@ describe('Session', () => { }); describe('addOutcomeWithValue', () => { - test('should call plugin with correct parameters', () => { + test('should call plugin with correct parameters', async () => { const outcomeName = 'purchase_value'; const outcomeValue = 99.99; - session.addOutcomeWithValue(outcomeName, outcomeValue); + await session.addOutcomeWithValue(outcomeName, outcomeValue); expect(mockPlugin.addOutcomeWithValue).toHaveBeenCalledWith({ name: outcomeName, diff --git a/src/SessionNamespace.ts b/src/SessionNamespace.ts index 3bca96f..bb2b16a 100644 --- a/src/SessionNamespace.ts +++ b/src/SessionNamespace.ts @@ -10,28 +10,28 @@ export default class Session { /** * Add an outcome with the provided name, captured against the current session. * @param {string} name - * @returns void + * @returns Promise */ - addOutcome(name: string): void { - void this._plugin.addOutcome({ name }); + addOutcome(name: string): Promise { + return this._plugin.addOutcome({ name }); } /** * Add a unique outcome with the provided name, captured against the current session. * @param {string} name - * @returns void + * @returns Promise */ - addUniqueOutcome(name: string): void { - void this._plugin.addUniqueOutcome({ name }); + addUniqueOutcome(name: string): Promise { + return this._plugin.addUniqueOutcome({ name }); } /** * Add an outcome with the provided name and value, captured against the current session. * @param {string} name * @param {number} value - * @returns void + * @returns Promise */ - addOutcomeWithValue(name: string, value: number): void { - void this._plugin.addOutcomeWithValue({ name, value }); + addOutcomeWithValue(name: string, value: number): Promise { + return this._plugin.addOutcomeWithValue({ name, value }); } } diff --git a/src/UserNamespace.test.ts b/src/UserNamespace.test.ts index af97c73..bd81efa 100644 --- a/src/UserNamespace.test.ts +++ b/src/UserNamespace.test.ts @@ -32,18 +32,18 @@ describe('User', () => { }); describe('setLanguage', () => { - test('should call plugin with correct parameters', () => { + test('should call plugin with correct parameters', async () => { const language = 'en'; - user.setLanguage(language); + await user.setLanguage(language); expect(mockPlugin.setLanguage).toHaveBeenCalledWith({ language }); }); }); describe('addAlias', () => { - test('should call plugin with correct parameters', () => { + test('should call plugin with correct parameters', async () => { const id = '12345'; - user.addAlias(LABEL, id); + await user.addAlias(LABEL, id); expect(mockPlugin.addAliases).toHaveBeenCalledWith({ aliases: { [LABEL]: id }, @@ -52,24 +52,24 @@ describe('User', () => { }); describe('addAliases', () => { - test('should call plugin with correct parameters', () => { + test('should call plugin with correct parameters', async () => { const aliases = { [LABEL]: '12345', custom_id: 'abc-123' }; - user.addAliases(aliases); + await user.addAliases(aliases); expect(mockPlugin.addAliases).toHaveBeenCalledWith({ aliases }); }); - test('should handle empty aliases object', () => { + test('should handle empty aliases object', async () => { const aliases = {}; - user.addAliases(aliases); + await user.addAliases(aliases); expect(mockPlugin.addAliases).toHaveBeenCalledWith({ aliases }); }); }); describe('removeAlias', () => { - test('should call plugin with correct parameters', () => { - user.removeAlias(LABEL); + test('should call plugin with correct parameters', async () => { + await user.removeAlias(LABEL); expect(mockPlugin.removeAliases).toHaveBeenCalledWith({ labels: [LABEL], @@ -78,40 +78,40 @@ describe('User', () => { }); describe('removeAliases', () => { - test('should call plugin with correct parameters', () => { + test('should call plugin with correct parameters', async () => { const labels = [LABEL, 'custom_id', 'external_id']; - user.removeAliases(labels); + await user.removeAliases(labels); expect(mockPlugin.removeAliases).toHaveBeenCalledWith({ labels }); }); - test('should handle empty array', () => { + test('should handle empty array', async () => { const labels: string[] = []; - user.removeAliases(labels); + await user.removeAliases(labels); expect(mockPlugin.removeAliases).toHaveBeenCalledWith({ labels }); }); }); describe('addEmail', () => { - test('should call plugin with correct parameters', () => { - user.addEmail(EMAIL); + test('should call plugin with correct parameters', async () => { + await user.addEmail(EMAIL); expect(mockPlugin.addEmail).toHaveBeenCalledWith({ email: EMAIL }); }); }); describe('removeEmail', () => { - test('should call plugin with correct parameters', () => { - user.removeEmail(EMAIL); + test('should call plugin with correct parameters', async () => { + await user.removeEmail(EMAIL); expect(mockPlugin.removeEmail).toHaveBeenCalledWith({ email: EMAIL }); }); }); describe('addSms', () => { - test('should call plugin with correct parameters', () => { - user.addSms(SMS_NUMBER); + test('should call plugin with correct parameters', async () => { + await user.addSms(SMS_NUMBER); expect(mockPlugin.addSms).toHaveBeenCalledWith({ smsNumber: SMS_NUMBER, @@ -120,8 +120,8 @@ describe('User', () => { }); describe('removeSms', () => { - test('should call plugin with correct parameters', () => { - user.removeSms(SMS_NUMBER); + test('should call plugin with correct parameters', async () => { + await user.removeSms(SMS_NUMBER); expect(mockPlugin.removeSms).toHaveBeenCalledWith({ smsNumber: SMS_NUMBER, @@ -130,10 +130,10 @@ describe('User', () => { }); describe('addTag', () => { - test('should call plugin with correct parameters', () => { + test('should call plugin with correct parameters', async () => { const key = 'level'; const value = 'premium'; - user.addTag(key, value); + await user.addTag(key, value); expect(mockPlugin.addTags).toHaveBeenCalledWith({ tags: { [key]: value }, @@ -142,16 +142,16 @@ describe('User', () => { }); describe('addTags', () => { - test('should call plugin with correct parameters', () => { + test('should call plugin with correct parameters', async () => { const tags = { level: 'premium', status: 'active' }; - user.addTags(tags); + await user.addTags(tags); expect(mockPlugin.addTags).toHaveBeenCalledWith({ tags }); }); - test('should convert non-string values to JSON strings', () => { + test('should convert non-string values to JSON strings', async () => { const tags = { count: 42, active: true, data: { nested: 'value' } }; - user.addTags(tags); + await user.addTags(tags); expect(mockPlugin.addTags).toHaveBeenCalledWith({ tags: { count: '42', active: 'true', data: '{"nested":"value"}' }, @@ -160,25 +160,25 @@ describe('User', () => { }); describe('removeTag', () => { - test('should call plugin with correct parameters', () => { + test('should call plugin with correct parameters', async () => { const key = 'level'; - user.removeTag(key); + await user.removeTag(key); expect(mockPlugin.removeTags).toHaveBeenCalledWith({ keys: [key] }); }); }); describe('removeTags', () => { - test('should call plugin with correct parameters', () => { + test('should call plugin with correct parameters', async () => { const keys = ['level', 'status', 'premium']; - user.removeTags(keys); + await user.removeTags(keys); expect(mockPlugin.removeTags).toHaveBeenCalledWith({ keys }); }); - test('should handle empty array', () => { + test('should handle empty array', async () => { const keys: string[] = []; - user.removeTags(keys); + await user.removeTags(keys); expect(mockPlugin.removeTags).toHaveBeenCalledWith({ keys }); }); @@ -303,17 +303,17 @@ describe('User', () => { }); describe('trackEvent', () => { - test('should call plugin with only event name', () => { + test('should call plugin with only event name', async () => { const eventName = 'purchase'; - user.trackEvent(eventName); + await user.trackEvent(eventName); expect(mockPlugin.trackEvent).toHaveBeenCalledWith({ name: eventName }); }); - test('should call plugin with event name and properties', () => { + test('should call plugin with event name and properties', async () => { const eventName = 'purchase'; const properties = { amount: 99.99, currency: 'USD' }; - user.trackEvent(eventName, properties); + await user.trackEvent(eventName, properties); expect(mockPlugin.trackEvent).toHaveBeenCalledWith({ name: eventName, @@ -321,24 +321,24 @@ describe('User', () => { }); }); - test('should not call plugin when properties are not serializable', () => { + test('should not call plugin when properties are not serializable', async () => { const eventName = 'purchase'; const circularObj: Record = { name: 'test' }; circularObj.self = circularObj; const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); - user.trackEvent(eventName, circularObj); + await user.trackEvent(eventName, circularObj); expect(consoleSpy).toHaveBeenCalledWith('Properties must be a JSON-serializable object'); expect(mockPlugin.trackEvent).not.toHaveBeenCalled(); consoleSpy.mockRestore(); }); - test('should not call plugin when properties is an array', () => { + test('should not call plugin when properties is an array', async () => { const eventName = 'purchase'; const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); - user.trackEvent(eventName, ['item1', 'item2'] as unknown as object); + await user.trackEvent(eventName, ['item1', 'item2'] as unknown as object); expect(consoleSpy).toHaveBeenCalledWith('Properties must be a JSON-serializable object'); expect(mockPlugin.trackEvent).not.toHaveBeenCalled(); diff --git a/src/UserNamespace.ts b/src/UserNamespace.ts index 11e268e..9b24972 100644 --- a/src/UserNamespace.ts +++ b/src/UserNamespace.ts @@ -34,108 +34,108 @@ export default class User { /** * Explicitly set a 2-character language code for the user. * @param {string} language - * @returns void + * @returns Promise */ - setLanguage(language: string): void { - void this._plugin.setLanguage({ language }); + setLanguage(language: string): Promise { + return this._plugin.setLanguage({ language }); } /** * Set an alias for the current user. If this alias label already exists on this user, it will be overwritten with the new alias id. * @param {string} label * @param {string} id - * @returns void + * @returns Promise */ - addAlias(label: string, id: string): void { - void this._plugin.addAliases({ aliases: { [label]: id } }); + addAlias(label: string, id: string): Promise { + return this._plugin.addAliases({ aliases: { [label]: id } }); } /** * Set aliases for the current user. If any alias already exists, it will be overwritten to the new values. * @param {object} aliases - * @returns void + * @returns Promise */ - addAliases(aliases: Record): void { - void this._plugin.addAliases({ aliases }); + addAliases(aliases: Record): Promise { + return this._plugin.addAliases({ aliases }); } /** * Remove an alias from the current user. * @param {string} label - * @returns void + * @returns Promise */ - removeAlias(label: string): void { - void this._plugin.removeAliases({ labels: [label] }); + removeAlias(label: string): Promise { + return this._plugin.removeAliases({ labels: [label] }); } /** * Remove aliases from the current user. * @param {string[]} labels - * @returns void + * @returns Promise */ - removeAliases(labels: string[]): void { - void this._plugin.removeAliases({ labels }); + removeAliases(labels: string[]): Promise { + return this._plugin.removeAliases({ labels }); } /** * Add a new email subscription to the current user. * @param {string} email - * @returns void + * @returns Promise */ - addEmail(email: string): void { - void this._plugin.addEmail({ email }); + addEmail(email: string): Promise { + return this._plugin.addEmail({ email }); } /** * Remove an email subscription from the current user. * @param {string} email - * @returns void + * @returns Promise */ - removeEmail(email: string): void { - void this._plugin.removeEmail({ email }); + removeEmail(email: string): Promise { + return this._plugin.removeEmail({ email }); } /** * Add a new SMS subscription to the current user. * @param {string} smsNumber - * @returns void + * @returns Promise */ - addSms(smsNumber: string): void { - void this._plugin.addSms({ smsNumber }); + addSms(smsNumber: string): Promise { + return this._plugin.addSms({ smsNumber }); } /** * Remove an SMS subscription from the current user. * @param {string} smsNumber - * @returns void + * @returns Promise */ - removeSms(smsNumber: string): void { - void this._plugin.removeSms({ smsNumber }); + removeSms(smsNumber: string): Promise { + return this._plugin.removeSms({ smsNumber }); } /** * Add a tag for the current user. Tags are key:value string pairs used as building blocks for targeting specific users and/or personalizing messages. * @param {string} key * @param {string} value - * @returns void + * @returns Promise */ - addTag(key: string, value: string): void { - void this._plugin.addTags({ tags: { [key]: value } }); + addTag(key: string, value: string): Promise { + return this._plugin.addTags({ tags: { [key]: value } }); } /** * Add multiple tags for the current user. Tags are key:value string pairs used as building blocks for targeting specific users and/or personalizing messages. * @param {object} tags - * @returns void + * @returns Promise */ - addTags(tags: object): void { + addTags(tags: object): Promise { const convertedTags = tags as { [key: string]: unknown }; Object.keys(tags).forEach(function (key) { if (typeof convertedTags[key] !== 'string') { convertedTags[key] = JSON.stringify(convertedTags[key]); } }); - void this._plugin.addTags({ + return this._plugin.addTags({ tags: convertedTags as Record, }); } @@ -143,19 +143,19 @@ export default class User { /** * Remove the data tag with the provided key from the current user. * @param {string} key - * @returns void + * @returns Promise */ - removeTag(key: string): void { - void this._plugin.removeTags({ keys: [key] }); + removeTag(key: string): Promise { + return this._plugin.removeTags({ keys: [key] }); } /** * Remove multiple tags with the provided keys from the current user. * @param {string[]} keys - * @returns void + * @returns Promise */ - removeTags(keys: string[]): void { - void this._plugin.removeTags({ keys }); + removeTags(keys: string[]): Promise { + return this._plugin.removeTags({ keys }); } /** @@ -210,14 +210,14 @@ export default class User { * Track a custom event with the provided name and optional properties. * @param {string} name - The name of the custom event * @param {object} [properties] - Optional properties to associate with the event - * @returns void + * @returns Promise */ - trackEvent(name: string, properties?: object): void { + trackEvent(name: string, properties?: object): Promise { if (properties !== undefined && !isObjectSerializable(properties)) { console.error('Properties must be a JSON-serializable object'); - return; + return Promise.resolve(); } - void this._plugin.trackEvent({ + return this._plugin.trackEvent({ name, properties: properties as Record | undefined, }); diff --git a/src/index.test.ts b/src/index.test.ts index 87aa293..f71a558 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -28,7 +28,7 @@ describe('OneSignalPlugin', () => { }); test('should initialize with appId', async () => { - plugin.initialize(APP_ID); + await plugin.initialize(APP_ID); expect(mockPlugin.initialize).toHaveBeenCalledWith({ appId: APP_ID }); @@ -37,15 +37,15 @@ describe('OneSignalPlugin', () => { }); }); - test('should call plugin for login', () => { + test('should call plugin for login', async () => { const externalId = 'test-user-123'; - plugin.login(externalId); + await plugin.login(externalId); expect(mockPlugin.login).toHaveBeenCalledWith({ externalId }); }); - test('should call plugin for logout', () => { - plugin.logout(); + test('should call plugin for logout', async () => { + await plugin.logout(); expect(mockPlugin.logout).toHaveBeenCalled(); }); From dc021a8bfe32c5dc5c42b7b89f50bff6dbf9149e Mon Sep 17 00:00:00 2001 From: Fadi George Date: Thu, 16 Apr 2026 14:52:00 -0700 Subject: [PATCH 13/13] build: externalize Android versions to gradle/libs.versions.toml Move AGP, Kotlin, OneSignal SDK, appcompat, junit, espresso, SDK API levels, and other Android versions into a Gradle version catalog so they live in one place and match the structure reviewers expect. The buildscript {} block and the module body read from the catalog via small inline parsers. Capacitor consumer apps still override the standard Capacitor properties (compileSdkVersion, minSdkVersion, targetSdkVersion, kotlin_version, junitVersion, androidxAppCompatVersion) via rootProject.extra, so there is no change to how consumers configure the plugin. Also bump the OneSignal Android SDK pin to 5.7.7 (latest). Made-with: Cursor --- android/build.gradle.kts | 110 +++++++++++++++++++++--------- android/gradle/libs.versions.toml | 25 +++++++ 2 files changed, 101 insertions(+), 34 deletions(-) create mode 100644 android/gradle/libs.versions.toml diff --git a/android/build.gradle.kts b/android/build.gradle.kts index e305176..441d268 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -1,56 +1,98 @@ -val junitVersion: String = if (project.hasProperty("junitVersion")) { - rootProject.extra["junitVersion"] as String -} else { - "4.13.2" -} - -val androidxAppCompatVersion: String = if (project.hasProperty("androidxAppCompatVersion")) { - rootProject.extra["androidxAppCompatVersion"] as String -} else { - "1.7.0" -} +buildscript { + val catalogFile = file("gradle/libs.versions.toml") -extra["junitVersion"] = junitVersion -extra["androidxAppCompatVersion"] = androidxAppCompatVersion + // Lightweight reader for the [versions] table of libs.versions.toml. + // Inlined here because buildscript {} is evaluated before the rest of the + // script body, and Gradle's built-in version catalog APIs aren't available + // to a Capacitor plugin consumed as a sub-project. + fun fromCatalog(key: String): String { + var inVersions = false + var result: String? = null + catalogFile.forEachLine { raw -> + if (result != null) return@forEachLine + val line = raw.substringBefore("#").trim() + when { + line.startsWith("[") && line.endsWith("]") -> inVersions = (line == "[versions]") + inVersions && "=" in line -> { + val (rawKey, rawValue) = line.split("=", limit = 2) + if (rawKey.trim() == key) { + result = rawValue.trim().trim('"') + } + } + } + } + return result ?: error("Version '$key' not found in ${catalogFile.name}") + } -buildscript { val kotlinVersion: String = if (project.hasProperty("kotlin_version")) { rootProject.extra["kotlin_version"] as String } else { - "1.9.25" + fromCatalog("kotlin") } + val androidGradlePluginVersion: String = fromCatalog("androidGradlePlugin") + repositories { google() mavenCentral() } dependencies { - classpath("com.android.tools.build:gradle:8.7.3") + classpath("com.android.tools.build:gradle:$androidGradlePluginVersion") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") } } +// Duplicates the buildscript-local reader intentionally: the buildscript block +// closes over its own scope, so this helper is reused for the module body. +fun catalogVersion(key: String): String { + val toml = file("gradle/libs.versions.toml") + var inVersions = false + var result: String? = null + toml.forEachLine { raw -> + if (result != null) return@forEachLine + val line = raw.substringBefore("#").trim() + when { + line.startsWith("[") && line.endsWith("]") -> inVersions = (line == "[versions]") + inVersions && "=" in line -> { + val (rawKey, rawValue) = line.split("=", limit = 2) + if (rawKey.trim() == key) { + result = rawValue.trim().trim('"') + } + } + } + } + return result ?: error("Version '$key' not found in ${toml.name}") +} + +fun propertyOrCatalog(propertyName: String, catalogKey: String): String = + if (project.hasProperty(propertyName)) { + rootProject.extra[propertyName] as String + } else { + catalogVersion(catalogKey) + } + +fun intPropertyOrCatalog(propertyName: String, catalogKey: String): Int = + if (project.hasProperty(propertyName)) { + rootProject.extra[propertyName] as Int + } else { + catalogVersion(catalogKey).toInt() + } + +val junitVersion: String = propertyOrCatalog("junitVersion", "junit") +val androidxAppCompatVersion: String = propertyOrCatalog("androidxAppCompatVersion", "androidxAppCompat") + +extra["junitVersion"] = junitVersion +extra["androidxAppCompatVersion"] = androidxAppCompatVersion + apply(plugin = "com.android.library") apply(plugin = "kotlin-android") configure { namespace = "com.onesignal.capacitor" - compileSdk = if (project.hasProperty("compileSdkVersion")) { - rootProject.extra["compileSdkVersion"] as Int - } else { - 35 - } + compileSdk = intPropertyOrCatalog("compileSdkVersion", "compileSdk") defaultConfig { - minSdk = if (project.hasProperty("minSdkVersion")) { - rootProject.extra["minSdkVersion"] as Int - } else { - 23 - } + minSdk = intPropertyOrCatalog("minSdkVersion", "minSdk") @Suppress("DEPRECATION") - targetSdk = if (project.hasProperty("targetSdkVersion")) { - rootProject.extra["targetSdkVersion"] as Int - } else { - 35 - } + targetSdk = intPropertyOrCatalog("targetSdkVersion", "targetSdk") testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } buildTypes { @@ -77,8 +119,8 @@ repositories { dependencies { "implementation"(project(":capacitor-android")) "implementation"("androidx.appcompat:appcompat:$androidxAppCompatVersion") - "implementation"("com.onesignal:OneSignal:5.7.6") + "implementation"("com.onesignal:OneSignal:${catalogVersion("onesignal")}") "testImplementation"("junit:junit:$junitVersion") - "androidTestImplementation"("androidx.test.ext:junit:1.2.1") - "androidTestImplementation"("androidx.test.espresso:espresso-core:3.6.1") + "androidTestImplementation"("androidx.test.ext:junit:${catalogVersion("androidxTestJunit")}") + "androidTestImplementation"("androidx.test.espresso:espresso-core:${catalogVersion("androidxEspresso")}") } diff --git a/android/gradle/libs.versions.toml b/android/gradle/libs.versions.toml new file mode 100644 index 0000000..bf45eae --- /dev/null +++ b/android/gradle/libs.versions.toml @@ -0,0 +1,25 @@ +# Version catalog for the OneSignal Capacitor Android plugin. +# Defaults below can be overridden by the consuming app via gradle +# properties or rootProject.extra (see android/build.gradle.kts). +# +# compileSdk / minSdk / targetSdk / kotlin / appcompat / junit follow +# Capacitor 7's required versions, not the OneSignal Android SDK's. + +[versions] +androidGradlePlugin = "8.7.3" +androidxAppCompat = "1.7.0" +androidxEspresso = "3.6.1" +androidxTestJunit = "1.2.1" +compileSdk = "35" +junit = "4.13.2" +kotlin = "1.9.25" +minSdk = "23" +onesignal = "5.7.7" +targetSdk = "35" + +[libraries] +androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidxAppCompat" } +androidx-test-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidxEspresso" } +androidx-test-ext-junit = { module = "androidx.test.ext:junit", version.ref = "androidxTestJunit" } +junit = { module = "junit:junit", version.ref = "junit" } +onesignal = { module = "com.onesignal:OneSignal", version.ref = "onesignal" }