diff --git a/CHANGELOG.md b/CHANGELOG.md index a5f7f20..9d25435 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Release Note +## 2.15.0 +- Update latest versions of native Android (2.31.1) and iOS (2.31.1) sdks. +- Add support for `DcsSignatureReadyEvent` and `DcsSignatureErrorEvent` events. +- Add support for `IntegrationErrorEvent` event. + ## 2.14.0 - Update latest versions of native Android (2.28.0) and iOS (2.28.0) sdks diff --git a/android/build.gradle b/android/build.gradle index e9def50..d78579e 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -64,7 +64,7 @@ android { dependencies { implementation("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - implementation("io.didomi.sdk:android:2.28.0") + implementation("io.didomi.sdk:android:2.31.1") implementation("androidx.multidex:multidex:2.0.1") implementation("com.google.code.gson:gson:2.13.2") } diff --git a/android/src/main/kotlin/io/didomi/fluttersdk/DidomiEventStreamHandler.kt b/android/src/main/kotlin/io/didomi/fluttersdk/DidomiEventStreamHandler.kt index ea6059f..ec390f0 100644 --- a/android/src/main/kotlin/io/didomi/fluttersdk/DidomiEventStreamHandler.kt +++ b/android/src/main/kotlin/io/didomi/fluttersdk/DidomiEventStreamHandler.kt @@ -1,12 +1,11 @@ package io.didomi.fluttersdk +import android.os.Handler +import android.os.Looper import io.didomi.sdk.events.* import io.didomi.sdk.functionalinterfaces.DidomiEventListener -import io.flutter.plugin.common.EventChannel -import org.json.JSONObject import io.didomi.sdk.models.CurrentUserStatus.VendorStatus -import android.os.Handler -import android.os.Looper +import io.flutter.plugin.common.EventChannel /** * Handler for SDK events @@ -14,8 +13,10 @@ import android.os.Looper class DidomiEventStreamHandler : EventChannel.StreamHandler, DidomiEventListener { private var eventSink: EventChannel.EventSink? = null + // We keep references to all the Sync Ready events being registered so we can call their respective syncAcknowledged callback from the Flutter side through a method channel. private var syncReadyEventReferences: MutableMap = mutableMapOf() + // Index used to keep track of the Sync Ready events being registered. private var syncReadyEventIndex: Int = 0 @@ -79,6 +80,7 @@ class DidomiEventStreamHandler : EventChannel.StreamHandler, DidomiEventListener sendEvent("onNoticeClickViewVendors") } + @Deprecated("SPI purposes are now displayed in preferences screen.", replaceWith = ReplaceWith("noticeClickMoreInfo")) override fun noticeClickViewSPIPurposes(event: NoticeClickViewSPIPurposesEvent) { sendEvent("noticeClickViewSPIPurposes") } @@ -123,6 +125,7 @@ class DidomiEventStreamHandler : EventChannel.StreamHandler, DidomiEventListener sendEvent("onPreferencesClickViewVendors") } + @Deprecated("SPI purposes are now displayed in preferences screen.", replaceWith = ReplaceWith("noticeClickMoreInfo")) override fun preferencesClickViewSPIPurposes(event: PreferencesClickViewSPIPurposesEvent) { sendEvent("preferencesClickViewSPIPurposes") } @@ -175,22 +178,27 @@ class DidomiEventStreamHandler : EventChannel.StreamHandler, DidomiEventListener * SPI screen events */ + @Deprecated("SPI purposes now trigger the same events as other purposes.", replaceWith = ReplaceWith("preferencesClickPurposeAgree")) override fun preferencesClickSPIPurposeAgree(event: PreferencesClickSPIPurposeAgreeEvent) { sendEvent("preferencesClickSPIPurposeAgree", mapOf("purposeId" to event.purposeId)) } + @Deprecated("SPI purposes now trigger the same events as other purposes.", replaceWith = ReplaceWith("preferencesClickPurposeDisagree")) override fun preferencesClickSPIPurposeDisagree(event: PreferencesClickSPIPurposeDisagreeEvent) { sendEvent("onPreferencesClickSPIPurposeDisagree", mapOf("purposeId" to event.purposeId)) } + @Deprecated("SPI purposes now trigger the same events as other purposes.", replaceWith = ReplaceWith("preferencesClickCategoryAgree")) override fun preferencesClickSPICategoryAgree(event: PreferencesClickSPICategoryAgreeEvent) { sendEvent("onPreferencesClickSPICategoryAgree", mapOf("categoryId" to event.categoryId)) } + @Deprecated("SPI purposes now trigger the same events as other purposes.", replaceWith = ReplaceWith("preferencesClickCategoryDisagree")) override fun preferencesClickSPICategoryDisagree(event: PreferencesClickSPICategoryDisagreeEvent) { sendEvent("onPreferencesClickSPICategoryDisagree", mapOf("categoryId" to event.categoryId)) } + @Deprecated("SPI purposes now trigger the same events as other purposes.", replaceWith = ReplaceWith("preferencesClickSaveChoices")) override fun preferencesClickSPIPurposeSaveChoices(event: PreferencesClickSPIPurposeSaveChoicesEvent) { sendEvent("preferencesClickSPIPurposeSaveChoices") } @@ -206,13 +214,17 @@ class DidomiEventStreamHandler : EventChannel.StreamHandler, DidomiEventListener override fun syncReady(event: SyncReadyEvent) { syncReadyEventIndex++ syncReadyEventReferences.put(syncReadyEventIndex, event) - sendEvent("onSyncReady", mapOf("organizationUserId" to event.organizationUserId, "statusApplied" to event.statusApplied, "syncReadyEventIndex" to syncReadyEventIndex)) + sendEvent( + "onSyncReady", + mapOf("organizationUserId" to event.organizationUserId, "statusApplied" to event.statusApplied, "syncReadyEventIndex" to syncReadyEventIndex) + ) } override fun syncUserChanged(event: SyncUserChangedEvent) { // TODO Support event } + @Deprecated("Use syncReady instead", replaceWith = ReplaceWith("syncReady()")) override fun syncDone(event: SyncDoneEvent) { sendEvent("onSyncDone", mapOf("organizationUserId" to event.organizationUserId)) } @@ -234,15 +246,23 @@ class DidomiEventStreamHandler : EventChannel.StreamHandler, DidomiEventListener } /* - * TODO: DCS events + * DCS events */ override fun dcsSignatureReady(event: DcsSignatureReadyEvent) { - //sendEvent("onDCSSignatureReady") + sendEvent("onDCSSignatureReady") } override fun dcsSignatureError(event: DcsSignatureErrorEvent) { - //sendEvent("onDCSSignatureError") + sendEvent("onDCSSignatureError") + } + + /* + * External SDKs error event + */ + + override fun integrationError(event: IntegrationErrorEvent) { + sendEvent("onIntegrationError", mapOf("integrationName" to event.integrationName, "reason" to event.reason)) } /* diff --git a/example/integration_test/text_test.dart b/example/integration_test/text_test.dart index 2e8818e..a73465d 100644 --- a/example/integration_test/text_test.dart +++ b/example/integration_test/text_test.dart @@ -16,15 +16,21 @@ void main() { // Messages const expectedConsentEn = "Native message: With your agreement, we"; const expectedConsentFr = "Native message: Avec votre consentement, nous"; - const expectedResult = "Native message: fr =>Avec votre consentement, nous et nos partenaires " - "utilisons l'espace de stockage du terminal pour stocker et accéder à des données personnelles telles que des données de géolocalisation " - "précises et d'identification par analyse du terminal. Nous traitons ces données à des fins telles que les publicités et contenus personnalisés, " - "la mesure de performance des publicités et du contenu, les données d'audience et le développement des produits. Vous pouvez à tout moment retirer " - "votre consentement ou vous opposer au traitement des données sur la base de l'intérêt légitime depuis le menu de l'application.en =>With your " - "agreement, we and our partners use device storage to store and access personal data " - "like precise geolocation data, and identification through device scanning. We process that data for purposes like personalised ads and content, " - "ad and content measurement, audience insights and product development. You can withdraw your consent or object to data processing based on " - "legitimate interest at any time from the app menu."; + const expectedResult = "Native message: fr =>Avec votre consentement, nous et nos partenaires utilisons l'espace de " + "stockage du terminal pour stocker, et accéder à, des données personnelles telles que " + "des données de géolocalisation précises et d'identification par analyse du terminal.
Nous traitons ces données à des fins telles que les publicités et contenus " + "personnalisés, la mesure de performance des publicités et du contenu, les données d'audience et le " + "développement des produits.
Vous pouvez à tout moment retirer votre consentement ou " + "vous opposer au traitement des données sur la base de l'intérêt légitime en consultant la rubrique « " + "Préférences de consentement » dans le menu de cette application.en =>With your agreement, we and our partners use device storage to store " + "and access personal data like precise geolocation data, and identification through " + "device scanning.
We process that data for purposes like personalised ads " + "and content, ad and content measurement, audience insights and product development.
You " + "can withdraw your consent or object to data processing based on legitimate interest at any time by " + "going to \"Consent Preferences\" in the menu of this app."; const expectedJavascript = "window.didomiOnReady = window.didomiOnReady ||\n[];window.didomiOnReady.push(function (Didomi)\n{Didomi.notice.hide();" "Didomi.setUserStatus({\"purposes\":{\"consent\":{\"enabled\":[],\"disabled\":[]},\"legitimate_interest\":{\"enabled\":[],\"disabled\":[]}}," "\"vendors\":{\"consent\":{\"enabled\":[],\"disabled\":[]},\"legitimate_interest\":{\"enabled\":[],\"disabled\":[]}}," diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 18cdb5c..37c0c58 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,7 +1,7 @@ PODS: - - Didomi-XCFramework (2.28.0) - - didomi_sdk (2.14.0): - - Didomi-XCFramework (= 2.28.0) + - Didomi-XCFramework (2.31.1) + - didomi_sdk (2.15.0): + - Didomi-XCFramework (= 2.31.1) - Flutter - Flutter (1.0.0) - integration_test (0.0.1): @@ -25,11 +25,11 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/integration_test/ios" SPEC CHECKSUMS: - Didomi-XCFramework: 59b83cb507bbe2c8aa0965348956e18d57f69142 - didomi_sdk: 2cbb1ef5f932757cfee7efa27883c1ed481e76d9 - Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573 + Didomi-XCFramework: 14fd40dbedc4f83f8338354b4787bf696eb4d00d + didomi_sdk: 80b1901521ebde0ccc7e56c45089f6c5e28dcd94 + Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 + integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e -PODFILE CHECKSUM: eb82984a624d653d2e3bbebcdf3d34861d5eb905 +PODFILE CHECKSUM: e2772aabdb96c4d254f2b01df983810e9a468979 COCOAPODS: 1.16.2 diff --git a/ios/Classes/DidomiEventStreamHandler.swift b/ios/Classes/DidomiEventStreamHandler.swift index cbda63b..9b4a339 100644 --- a/ios/Classes/DidomiEventStreamHandler.swift +++ b/ios/Classes/DidomiEventStreamHandler.swift @@ -9,15 +9,15 @@ import Flutter import Didomi /// Handler for SDK events -class DidomiEventStreamHandler : NSObject, FlutterStreamHandler { - +class DidomiEventStreamHandler: NSObject, FlutterStreamHandler { + private var eventSink: FlutterEventSink? // We keep references to all the Sync Ready events being registered so we can call their respective syncAcknowledged callback from the Flutter side through a method channel. private var syncReadyEventReferences: [Int: SyncReadyEvent] = [:] // Index used to keep track of the Sync Ready events being registered. private var syncReadyEventIndex: Int = 0 let eventListener = EventListener() - + override init() { super.init() @@ -163,35 +163,38 @@ class DidomiEventStreamHandler : NSObject, FlutterStreamHandler { self?.sendEvent(eventType: "onLanguageUpdateFailed", arguments: ["reason": reason]) } - // TODO: DCS events - /* + // DCS events eventListener.onDCSSignatureReady = { [weak self] event in self?.sendEvent(eventType: "onDCSSignatureReady") } eventListener.onDCSSignatureError = { [weak self] event in self?.sendEvent(eventType: "onDCSSignatureError") } - */ + + // Integrations event + eventListener.onIntegrationError = { [weak self] event in + self?.sendEvent(eventType: "onIntegrationError", arguments: ["integrationName": event.integrationName, "reason": event.reason]) + } } - + func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { eventSink = events return nil } - + func onCancel(withArguments arguments: Any?) -> FlutterError? { eventSink = nil return nil } - + func onReadyCallback() { self.sendEvent(eventType: "onReadyCallback") } - + func onErrorCallback() { self.sendEvent(eventType: "onErrorCallback") } - + func sendEvent(eventType: String, arguments: Dictionary? = nil) { let eventDictionary: NSMutableDictionary = NSMutableDictionary() eventDictionary.setValue(eventType, forKey: "type") diff --git a/ios/didomi_sdk.podspec b/ios/didomi_sdk.podspec index cba4b1c..7ad6a6a 100644 --- a/ios/didomi_sdk.podspec +++ b/ios/didomi_sdk.podspec @@ -4,15 +4,15 @@ # Pod::Spec.new do |s| s.name = 'didomi_sdk' - s.version = '2.14.0' + s.version = '2.15.0' s.summary = 'Didomi CMP Plugin.' s.homepage = 'https://github.com/didomi/flutter' s.license = { :type => 'BSD', :file => '../LICENSE' } s.author = { 'Didomi ' => 'tech@didomi.io' } - s.source = { :path => 'git@github.com:didomi/flutter.git', :tag => '2.14.0' } + s.source = { :path => 'git@github.com:didomi/flutter.git', :tag => '2.15.0' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' - s.dependency 'Didomi-XCFramework', '2.28.0' + s.dependency 'Didomi-XCFramework', '2.31.1' s.platform = :ios, '13.0' # Flutter.framework does not contain a i386 slice. diff --git a/lib/events/event_listener.dart b/lib/events/event_listener.dart index 71c2566..47a3734 100644 --- a/lib/events/event_listener.dart +++ b/lib/events/event_listener.dart @@ -1,5 +1,7 @@ import 'package:didomi_sdk/events/sync_ready_event.dart'; +import 'integration_error_event.dart'; + /// Listener to events sent by the Didomi SDK class EventListener { /* @@ -160,4 +162,21 @@ class EventListener { /// The language update is complete dynamic Function(String reason) onLanguageUpdateFailed = (reason) {}; + + /* + * DCS signature events + */ + + /// DCS signature generation encountered an error + dynamic Function() onDcsSignatureError = () {}; + + /// DCS signature is ready + dynamic Function() onDcsSignatureReady = () {}; + + /* + * External SDKs integration events + */ + + /// Integration with an external SDK encountered an error + dynamic Function(IntegrationErrorEvent event) onIntegrationError = (event) {}; } diff --git a/lib/events/events_handler.dart b/lib/events/events_handler.dart index a346a84..374ace9 100644 --- a/lib/events/events_handler.dart +++ b/lib/events/events_handler.dart @@ -4,10 +4,12 @@ import 'package:didomi_sdk/events/sync_ready_event.dart'; import 'package:flutter/services.dart'; import 'event_listener.dart'; +import 'integration_error_event.dart'; /// Handler for events emitted by native SDK class EventsHandler { static const EventChannel _eventChannel = EventChannel(eventsChannelName); + // Reference to the channel so we can call methods on the native side. MethodChannel _channel; List listeners = []; @@ -26,6 +28,8 @@ class EventsHandler { final String eventType = event["type"].toString(); switch (eventType) { + /// Ready events + case "onReady": for (var listener in listeners) { listener.onReady(); @@ -44,6 +48,8 @@ class EventsHandler { } break; + /// Error events + case "onError": final String message = event["message"].toString(); for (var listener in listeners) { @@ -63,6 +69,8 @@ class EventsHandler { } break; + /// UI events + case "onShowNotice": for (var listener in listeners) { listener.onShowNotice(); @@ -87,6 +95,8 @@ class EventsHandler { } break; + /// Notice events + case "onNoticeClickAgree": for (var listener in listeners) { listener.onNoticeClickAgree(); @@ -117,6 +127,8 @@ class EventsHandler { } break; + /// Preferences events + case "onPreferencesClickAgreeToAll": for (var listener in listeners) { listener.onPreferencesClickAgreeToAll(); @@ -225,12 +237,24 @@ class EventsHandler { } break; + /// Consent events + + case "onVendorStatusChanged": + final VendorStatus vendorStatus = VendorStatus.fromJson(event["vendorStatus"]); + final List callbacks = vendorStatusListeners[vendorStatus.id] ?? []; + for (var callback in callbacks) { + callback(vendorStatus); + } + break; + case "onConsentChanged": for (var listener in listeners) { listener.onConsentChanged(); } break; + /// Sync events + case "onSyncReady": for (var listener in listeners) { final SyncReadyEvent newEvent = SyncReadyEvent( @@ -261,6 +285,8 @@ class EventsHandler { } break; + /// Language events + case "onLanguageUpdated": final String languageCode = event["languageCode"].toString(); for (var listener in listeners) { @@ -275,11 +301,29 @@ class EventsHandler { } break; - case "onVendorStatusChanged": - final VendorStatus vendorStatus = VendorStatus.fromJson(event["vendorStatus"]); - final List callbacks = vendorStatusListeners[vendorStatus.id] ?? []; - for (var callback in callbacks) { - callback(vendorStatus); + /// DCS signature events + + case "onDcsSignatureError": + for (var listener in listeners) { + listener.onDcsSignatureError(); + } + break; + + case "onDcsSignatureReady": + for (var listener in listeners) { + listener.onDcsSignatureReady(); + } + break; + + /// External SDKs integration events + + case "onIntegrationError": + final IntegrationErrorEvent newEvent = IntegrationErrorEvent( + event["integrationName"], + event["reason"], + ); + for (var listener in listeners) { + listener.onIntegrationError(newEvent); } break; diff --git a/lib/events/integration_error_event.dart b/lib/events/integration_error_event.dart new file mode 100644 index 0000000..5ab7e06 --- /dev/null +++ b/lib/events/integration_error_event.dart @@ -0,0 +1,10 @@ +/** Error while using an external SDK integration. */ +class IntegrationErrorEvent { + // External SDK integration name + String integrationName; + + // Reason of the error + String reason; + + IntegrationErrorEvent(this.integrationName, this.reason); +} diff --git a/pubspec.yaml b/pubspec.yaml index cf3e786..eac1d65 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: didomi_sdk description: The Didomi CMP Plugin allows companies to collect, store, and leverage user consent under GDPR, CCPA, and other data privacy regulations. -version: 2.14.0 +version: 2.15.0 repository: https://github.com/didomi/flutter environment: