From 262c96e1fd168670fc251b4bf312151d6535405c Mon Sep 17 00:00:00 2001 From: Matthew Podwysocki Date: Wed, 14 Feb 2024 11:25:49 -0500 Subject: [PATCH] [notification-hubs] Adding Support for FCM V1 (#28573) ### Packages impacted by this PR - @azure/notification-hubs ### Issues associated with this PR N/A ### Describe the problem that is addressed by this PR Adds support for [Firebase Messaging V1](https://firebase.google.com/docs/cloud-messaging) following the PR from the Azure Notification Hubs Team https://github.com/Azure/azure-notificationhubs-dotnet/pull/302 ### What are the possible designs available to address the problem? If there are more than one possible design, why was the one in this PR chosen? ### Are there test cases added in this PR? _(If not, why?)_ Yes ### Provide a list of related PRs _(if any)_ https://github.com/Azure/azure-notificationhubs-dotnet/pull/302 ### Command used to generate this PR:**_(Applicable only to SDK release request PRs)_ ### Checklists - [x] Added impacted package name to the issue description - [ ] Does this PR needs any fixes in the SDK Generator?** _(If so, create an Issue in the [Autorest/typescript](https://github.com/Azure/autorest.typescript) repository and link it here)_ - [x] Added a changelog (if necessary) --- .vscode/cspell.json | 6 + .../notification-hubs/CHANGELOG.md | 11 +- .../notification-hubs/README.md | 4 +- .../notification-hubs/karma.conf.cjs | 24 +- .../notification-hubs/migrationguide.md | 1 + .../notification-hubs/package.json | 31 +- .../review/notification-hubs.api.md | 195 +++++++- .../notification-hubs/sample.env | 2 + .../createInstallation.fcmLegacy.ts | 6 +- .../samples-dev/createInstallation.fcmV1.ts | 48 ++ .../samples-dev/createInstallation.ts | 6 +- .../samples-dev/createOrUpdateRegistration.ts | 6 +- .../createRegistration.fcmLegacy.ts | 6 +- .../samples-dev/createRegistration.fcmV1.ts | 47 ++ .../samples-dev/createRegistration.ts | 6 +- .../samples-dev/deleteRegistrations.ts | 10 +- .../exportRegistrationsJob.polling.ts | 6 +- .../samples-dev/exportRegistrationsJob.ts | 6 +- .../importRegistrationsJob.poller.ts | 6 +- .../samples-dev/importRegistrationsJob.ts | 7 +- .../samples-dev/listRegistrations.ts | 6 +- .../samples-dev/listRegistrationsByChannel.ts | 6 +- .../samples-dev/listRegistrationsByTag.ts | 6 +- .../scheduledSendBroadcastNotification.ts | 6 +- .../samples-dev/scheduledSendNotification.ts | 6 +- .../samples-dev/sendBroadcastNotification.ts | 6 +- .../sendDirectNotification.fcmLegacy.ts | 6 +- .../sendDirectNotification.fcmV1.ts | 108 +++++ .../samples-dev/sendDirectNotification.ts | 6 +- .../sendDirectNotificationBatch.ts | 6 +- .../samples-dev/sendTagExpression.ts | 6 +- .../samples-dev/sendTagsList.ts | 6 +- .../samples-dev/updateInstallation.ts | 6 +- .../samples-dev/updateRegistration.ts | 6 +- .../src/api/beginSubmitNotificationHubJob.ts | 75 ++- .../src/models/installation.ts | 23 + .../src/models/notification.ts | 42 ++ .../src/models/notificationBodyBuilder.ts | 439 ++++++++++++++++++ .../src/models/registration.ts | 85 ++++ .../src/serializers/registrationSerializer.ts | 88 +++- .../unit/registrationSerializer.spec.ts | 86 ++++ .../test/public/unit/installation.spec.ts | 14 + .../test/public/unit/notification.spec.ts | 13 + 43 files changed, 1330 insertions(+), 155 deletions(-) create mode 100644 sdk/notificationhubs/notification-hubs/samples-dev/createInstallation.fcmV1.ts create mode 100644 sdk/notificationhubs/notification-hubs/samples-dev/createRegistration.fcmV1.ts create mode 100644 sdk/notificationhubs/notification-hubs/samples-dev/sendDirectNotification.fcmV1.ts diff --git a/.vscode/cspell.json b/.vscode/cspell.json index 528fa7840ba9..c61d8b7c67b3 100644 --- a/.vscode/cspell.json +++ b/.vscode/cspell.json @@ -462,6 +462,12 @@ "spreadsheetml", "presentationml" ] + }, + { + "filename": "sdk/notificationhubs/notification-hubs/review/notification-hubs.api.md", + "words": [ + "fcmv" + ] } ] } diff --git a/sdk/notificationhubs/notification-hubs/CHANGELOG.md b/sdk/notificationhubs/notification-hubs/CHANGELOG.md index 4bd0a33efe50..c5467f45acf4 100644 --- a/sdk/notificationhubs/notification-hubs/CHANGELOG.md +++ b/sdk/notificationhubs/notification-hubs/CHANGELOG.md @@ -1,14 +1,13 @@ # Release History -## 1.0.4 (Unreleased) +## 1.1.0 (Unreleased) ### Features Added -### Breaking Changes - -### Bugs Fixed - -### Other Changes +- Added Support for [Firebase Cloud Messaging (FCM)](https://firebase.google.com/docs/cloud-messaging) V1 API with the following: + - `FcmV1RegistrationDescription` and `FcmV1TemplateRegistrationDescription` for registrations with associated factory methods. + - `FcmV1Installation` for installation operations and associated factory methods. + - `FcmV1Notification` for notification send support and associated factory methods. ## 1.0.3 (2023-11-13) diff --git a/sdk/notificationhubs/notification-hubs/README.md b/sdk/notificationhubs/notification-hubs/README.md index 8fbb6a73f8a6..db42866d70cf 100644 --- a/sdk/notificationhubs/notification-hubs/README.md +++ b/sdk/notificationhubs/notification-hubs/README.md @@ -1,6 +1,6 @@ # Azure Notification Hubs SDK for JavaScript -Azure Notification Hubs provide a scaled-out push engine that enables you to send notifications to any platform (Apple, Amazon Kindle, Android, Baidu, Xiaomi, Web, Windows, etc.) from any back-end (cloud or on-premises). Notification Hubs works well for both enterprise and consumer scenarios. Here are a few example scenarios: +Azure Notification Hubs provide a scaled-out push engine that enables you to send notifications to any platform (Apple, Amazon Kindle, Firebase, Baidu, Xiaomi, Web, Windows, etc.) from any back-end (cloud or on-premises). Notification Hubs works well for both enterprise and consumer scenarios. Here are a few example scenarios: - Send breaking news notifications to millions with low latency. - Send location-based coupons to interested user segments. @@ -735,7 +735,7 @@ This module's tests are a mixture of live and unit tests, which require you to h `NOTIFICATIONHUBS_CONNECTION_STRING=connection string for your Notification Hubs instance` `NOTIFICATION_HUB_NAME=Notification Hub name` 4. `cd sdk\notificationhubs\notification-hubs` -5. `npm run test`. +5. `rushx test`. View our [tests](https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/notificationhubs/notification-hubs/test) folder for more details. diff --git a/sdk/notificationhubs/notification-hubs/karma.conf.cjs b/sdk/notificationhubs/notification-hubs/karma.conf.cjs index 90b131b0e429..faf1fb3e2f44 100644 --- a/sdk/notificationhubs/notification-hubs/karma.conf.cjs +++ b/sdk/notificationhubs/notification-hubs/karma.conf.cjs @@ -4,6 +4,9 @@ // https://github.com/karma-runner/karma-chrome-launcher process.env.CHROME_BIN = require("puppeteer").executablePath(); require("dotenv").config(); +const { relativeRecordingsPath } = require("@azure-tools/test-recorder"); + +process.env.RECORDINGS_RELATIVE_PATH = relativeRecordingsPath(); module.exports = function (config) { config.set({ @@ -46,8 +49,14 @@ module.exports = function (config) { // inject following environment values into browser testing with window.__env__ // environment values MUST be exported or set with same console running "karma start" // https://www.npmjs.com/package/karma-env-preprocessor - envPreprocessor: ["TEST_MODE", "NOTIFICATIONHUBS_CONNECTION_STRING", "NOTIFICATION_HUB_NAME"], - + envPreprocessor: [ + "TEST_MODE", + "WIDGET_SERVICE_ENDPOINT", + "AZURE_CLIENT_ID", + "AZURE_CLIENT_SECRET", + "AZURE_TENANT_ID", + "RECORDINGS_RELATIVE_PATH", + ], // test results reporter to use // possible values: 'dots', 'progress' // available reporters: https://npmjs.org/browse/keyword/karma-reporter @@ -90,12 +99,19 @@ module.exports = function (config) { // start these browsers // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher // 'ChromeHeadless', 'Chrome', 'Firefox', 'Edge', 'IE' - browsers: ["ChromeHeadless"], + browsers: ['ChromeHeadlessNoSandbox'], + + customLaunchers: { + ChromeHeadlessNoSandbox: { + base: 'ChromeHeadless', + flags: ['--no-sandbox', "--disable-web-security"] + } + }, // Continuous Integration mode // if true, Karma captures browsers, runs the tests and exits singleRun: true, - + // Concurrency level // how many browser should be started simultaneous concurrency: 1, diff --git a/sdk/notificationhubs/notification-hubs/migrationguide.md b/sdk/notificationhubs/notification-hubs/migrationguide.md index 71521b629b8c..db11fe06863e 100644 --- a/sdk/notificationhubs/notification-hubs/migrationguide.md +++ b/sdk/notificationhubs/notification-hubs/migrationguide.md @@ -40,6 +40,7 @@ We have a variety of new features in the `@azure/notification-hubs` library such - Support for additional Push Notification Providers such as - Amazon Device Message (ADM) - Baidu Cloud Push + - Firebase Cloud Messaging (FCM) V1 - Xiaomi Message Service - Web Push - Scheduled Notifications for Standard SKU and higher. diff --git a/sdk/notificationhubs/notification-hubs/package.json b/sdk/notificationhubs/notification-hubs/package.json index 25597fb5b863..cc0f13509183 100644 --- a/sdk/notificationhubs/notification-hubs/package.json +++ b/sdk/notificationhubs/notification-hubs/package.json @@ -1,6 +1,6 @@ { "name": "@azure/notification-hubs", - "version": "1.0.4", + "version": "1.1.0", "description": "Azure Notification Hubs SDK for JavaScript", "sdk-type": "client", "main": "dist/index.cjs", @@ -42,8 +42,8 @@ "build:samples": "echo Obsolete", "build:test": "npm run bundle", "build": "npm run build:test && api-extractor run --local", - "check-format": "dev-tool run vendored prettier --list-different --config ../../../.prettierrc.json --ignore-path ../../../.prettierignore \"src/**/*.ts\" \"test/**/*.ts\" \"*.{js,json}\"", - "clean": "rimraf -G dist dist-* temp types *.tgz *.log", + "check-format": "dev-tool run vendored prettier --list-different --config ../../../.prettierrc.json --ignore-path ../../../.prettierignore \"src/**/*.ts\" \"samples-dev/**/*.ts\" \"test/**/*.ts\" \"*.{js,json}\"", + "clean": "rimraf --glob dist dist-* temp types *.tgz *.log", "execute:samples": "dev-tool samples run samples-dev", "extract-api": "tsc -p . && api-extractor run --local", "format": "dev-tool run vendored prettier --write --config ../../../.prettierrc.json --ignore-path ../../../.prettierignore \"src/**/*.ts\" \"test/**/*.ts\" \"samples-dev/**/*.ts\" \"*.{js,json}\"", @@ -90,15 +90,15 @@ "@azure/eslint-plugin-azure-sdk": "^3.0.0", "@azure-tools/test-recorder": "^3.0.0", "@azure/test-utils": "^1.0.0", - "@microsoft/api-extractor": "^7.38.2", + "@microsoft/api-extractor": "^7.40.1", "@types/chai": "^4.3.1", "@types/mocha": "^10.0.0", "@types/node": "^18.0.0", "c8": "^8.0.0", "chai": "^4.3.10", - "dotenv": "^16.3.1", - "eslint": "^8.53.0", - "esm": "^3.2.18", + "dotenv": "^16.4.4", + "eslint": "^8.56.0", + "esm": "^3.2.25", "karma": "^6.4.2", "karma-chrome-launcher": "^3.1.1", "karma-coverage": "^2.2.0", @@ -113,21 +113,20 @@ "mocha": "^10.0.0", "puppeteer": "^22.0.0", "rimraf": "^5.0.5", - "ts-node": "^10.9.1", - "typescript": "~5.3.3", - "util": "^0.12.4" + "ts-node": "^10.9.2", + "typescript": "~5.3.3" }, "dependencies": { - "@azure/core-auth": "^1.5.0", - "@azure/abort-controller": "^1.1.0", - "@azure/core-lro": "^2.5.4", + "@azure/core-auth": "^1.6.0", + "@azure/abort-controller": "^2.0.0", + "@azure/core-lro": "^2.6.0", "@azure/core-paging": "^1.5.0", - "@azure/core-rest-pipeline": "^1.12.2", + "@azure/core-rest-pipeline": "^1.14.0", "@azure/core-tracing": "^1.0.1", - "@azure/core-util": "^1.6.1", + "@azure/core-util": "^1.7.0", "@azure/core-xml": "^1.3.4", "@azure/logger": "^1.0.4", - "@azure-rest/core-client": "^1.1.4", + "@azure-rest/core-client": "^1.2.0", "tslib": "^2.6.2" } } diff --git a/sdk/notificationhubs/notification-hubs/review/notification-hubs.api.md b/sdk/notificationhubs/notification-hubs/review/notification-hubs.api.md index 4392337cab91..fd301162f16e 100644 --- a/sdk/notificationhubs/notification-hubs/review/notification-hubs.api.md +++ b/sdk/notificationhubs/notification-hubs/review/notification-hubs.api.md @@ -369,9 +369,24 @@ export function createFcmLegacyRegistrationDescription(description: GcmRegistrat // @public export function createFcmLegacyTemplateRegistrationDescription(description: GcmTemplateRegistrationDescriptionCommon): GcmTemplateRegistrationDescription; +// @public +export function createFcmV1Installation(installation: DeviceTokenInstallation): FcmV1Installation; + +// @public +export function createFcmV1Notification(notification: FcmV1NotificationParams): FcmV1Notification; + +// @public +export function createFcmV1RegistrationDescription(description: FcmV1RegistrationDescriptionCommon): FcmV1RegistrationDescription; + +// @public +export function createFcmV1TemplateRegistrationDescription(description: FcmV1TemplateRegistrationDescriptionCommon): FcmV1TemplateRegistrationDescription; + // @public export function createFirebaseLegacyNotificationBody(nativeMessage: FirebaseLegacyNativeMessage): string; +// @public +export function createFirebaseV1NotificationBody(nativeMessage: FirebaseV1NativeMessage): string; + // @public export function createTagExpression(tags: string[]): string; @@ -448,6 +463,41 @@ export interface FcmLegacyNotificationParams { headers?: Record; } +// @public +export interface FcmV1Installation extends DeviceTokenInstallation { + platform: "fcmv1"; +} + +// @public +export interface FcmV1Notification extends JsonNotification { + platform: "fcmv1"; +} + +// @public +export interface FcmV1NotificationParams { + body: string | FirebaseV1NativeMessage; + headers?: Record; +} + +// @public +export interface FcmV1RegistrationDescription extends FcmV1RegistrationDescriptionCommon { + kind: "FcmV1"; +} + +// @public +export interface FcmV1RegistrationDescriptionCommon extends RegistrationDescriptionCommon { + fcmV1RegistrationId: string; +} + +// @public +export interface FcmV1TemplateRegistrationDescription extends FcmV1TemplateRegistrationDescriptionCommon { + kind: "FcmV1Template"; +} + +// @public +export interface FcmV1TemplateRegistrationDescriptionCommon extends FcmV1RegistrationDescriptionCommon, TemplateRegistrationDescription { +} + // @public export interface FirebaseLegacyAndroidNativePayload { android_channel_id?: string; @@ -508,6 +558,143 @@ export interface FirebaseLegacyWebNativePayload { title?: string; } +// @public +export interface FirebaseV1AndroidConfig { + collapse_key?: string; + data?: Record; + direct_boot_ok?: boolean; + fcm_options?: FirebaseV1AndroidFcmOptions; + notification?: FirebaseV1AndroidNotification; + priority: "normal" | "high"; + restricted_package_name?: string; + ttl?: string; +} + +// @public +export interface FirebaseV1AndroidFcmOptions { + analytics_label?: string; +} + +// @public +export interface FirebaseV1AndroidNotification { + body?: string; + body_loc_args?: string[]; + body_loc_key?: string; + channel_id?: string; + click_action?: string; + color?: string; + default_light_settings?: boolean; + default_sound?: boolean; + default_vibrate_timings?: boolean; + event_time?: string; + icon?: string; + image?: string; + light_settings?: { + color: { + red: number; + green: number; + blue: number; + alpha: number; + }; + light_on_duration: string; + light_off_duration: string; + }; + local_only?: boolean; + notification_count?: number; + // (undocumented) + notification_priority?: "PRIORITY_UNSPECIFIED" | "PRIORITY_MIN" | "PRIORITY_LOW" | "PRIORITY_DEFAULT" | "PRIORITY_HIGH" | "PRIORITY_MAX"; + sound?: string; + sticky?: boolean; + tag?: string; + ticker?: string; + title?: string; + title_loc_args?: string[]; + title_loc_key?: string; + vibrate_timings?: string[]; + visibility?: "VISIBILITY_UNSPECIFIED" | "PRIVATE" | "PUBLIC" | "SECRET"; +} + +// @public +export interface FirebaseV1ApnsConfig { + fcm_options?: FirebaseV1ApnsFcmOptions; + headers?: Record; + payload?: AppleNativeMessage; +} + +// @public +export interface FirebaseV1ApnsFcmOptions { + analytics_label: string; + image: string; +} + +// @public (undocumented) +export interface FirebaseV1FcmOptions { + analytics_label: string; +} + +// @public +export interface FirebaseV1NativeMessage { + android?: FirebaseV1AndroidConfig; + apns?: FirebaseV1ApnsConfig; + condition?: string; + data?: Record; + fcm_options?: FirebaseV1FcmOptions; + notification?: FirebaseV1NativeNotification; + token?: string; + topic?: string; + webpush?: FirebaseV1WebPushConfig; +} + +// @public +export interface FirebaseV1NativeNotification { + body?: string; + image?: string; + title?: string; +} + +// @public +export interface FirebaseV1RegistrationChannel { + fcmV1RegistrationId: string; + kind: "fcmv1"; +} + +// @public (undocumented) +export interface FirebaseV1WebPushConfig { + data?: Record; + fcm_options?: FirebaseV1WebPushFcmOptions; + headers?: Record; + notification?: FirebaseV1WebPushNotification; +} + +// @public +export interface FirebaseV1WebPushFcmOptions { + analytics_label?: string; + link?: string; +} + +// @public +export interface FirebaseV1WebPushNotification { + actions?: { + action: string; + title: string; + icon: string; + }[]; + badge?: string; + body?: string; + data?: Record; + dir?: "auto" | "ltr" | "rtl"; + icon?: string; + image?: string; + lang?: string; + renotify?: boolean; + requireInteraction?: boolean; + silent?: boolean; + tag?: string; + timestamp?: number; + title?: string; + vibrate?: number[]; +} + // @public export interface GcmRegistrationDescription extends GcmRegistrationDescriptionCommon { kind: "Gcm"; @@ -528,7 +715,7 @@ export interface GcmTemplateRegistrationDescriptionCommon extends GcmRegistratio } // @public -export type Installation = AppleInstallation | AdmInstallation | BaiduInstallation | BrowserInstallation | FcmLegacyInstallation | XiaomiInstallation | WindowsInstallation; +export type Installation = AppleInstallation | AdmInstallation | BaiduInstallation | BrowserInstallation | FcmLegacyInstallation | FcmV1Installation | XiaomiInstallation | WindowsInstallation; // @public export interface InstallationCommon { @@ -747,10 +934,10 @@ export interface PolledOperationOptions extends OperationOptions { export type PushHandle = BrowserPushChannel | string; // @public -export type RegistrationChannel = AdmRegistrationChannel | AppleRegistrationChannel | BaiduRegistrationChannel | BrowserRegistrationChannel | FirebaseLegacyRegistrationChannel | XiaomiRegistrationChannel | WindowsRegistrationChannel; +export type RegistrationChannel = AdmRegistrationChannel | AppleRegistrationChannel | BaiduRegistrationChannel | BrowserRegistrationChannel | FirebaseLegacyRegistrationChannel | FirebaseV1RegistrationChannel | XiaomiRegistrationChannel | WindowsRegistrationChannel; // @public -export type RegistrationDescription = AdmRegistrationDescription | AdmTemplateRegistrationDescription | AppleRegistrationDescription | AppleTemplateRegistrationDescription | BaiduRegistrationDescription | BaiduTemplateRegistrationDescription | BrowserRegistrationDescription | BrowserTemplateRegistrationDescription | GcmRegistrationDescription | GcmTemplateRegistrationDescription | MpnsRegistrationDescription | MpnsTemplateRegistrationDescription | XiaomiRegistrationDescription | XiaomiTemplateRegistrationDescription | WindowsRegistrationDescription | WindowsTemplateRegistrationDescription; +export type RegistrationDescription = AdmRegistrationDescription | AdmTemplateRegistrationDescription | AppleRegistrationDescription | AppleTemplateRegistrationDescription | BaiduRegistrationDescription | BaiduTemplateRegistrationDescription | BrowserRegistrationDescription | BrowserTemplateRegistrationDescription | GcmRegistrationDescription | GcmTemplateRegistrationDescription | FcmV1RegistrationDescription | FcmV1TemplateRegistrationDescription | MpnsRegistrationDescription | MpnsTemplateRegistrationDescription | XiaomiRegistrationDescription | XiaomiTemplateRegistrationDescription | WindowsRegistrationDescription | WindowsTemplateRegistrationDescription; // @public export interface RegistrationDescriptionCommon { @@ -786,7 +973,7 @@ export interface RegistrationResult { } // @public -export type RegistrationType = "Adm" | "AdmTemplate" | "Apple" | "AppleTemplate" | "Baidu" | "BaiduTemplate" | "Browser" | "BrowserTemplate" | "Gcm" | "GcmTemplate" | "Mpns" | "MpnsTemplate" | "Xiaomi" | "XiaomiTemplate" | "Windows" | "WindowsTemplate"; +export type RegistrationType = "Adm" | "AdmTemplate" | "Apple" | "AppleTemplate" | "Baidu" | "BaiduTemplate" | "Browser" | "BrowserTemplate" | "Gcm" | "GcmTemplate" | "FcmV1" | "FcmV1Template" | "Mpns" | "MpnsTemplate" | "Xiaomi" | "XiaomiTemplate" | "Windows" | "WindowsTemplate"; // @public export interface ScheduleNotificationOptions extends OperationOptions { diff --git a/sdk/notificationhubs/notification-hubs/sample.env b/sdk/notificationhubs/notification-hubs/sample.env index 931e3b61b937..ef52ded845fd 100644 --- a/sdk/notificationhubs/notification-hubs/sample.env +++ b/sdk/notificationhubs/notification-hubs/sample.env @@ -6,6 +6,8 @@ NOTIFICATION_HUB_NAME="" # Used in Direct Send Examples APNS_DEVICE_TOKEN="" +FCM_DEVICE_TOKEN="" +FCMV1_DEVICE_TOKEN="" # Used in Installation samples INSTALLATION_ID="" diff --git a/sdk/notificationhubs/notification-hubs/samples-dev/createInstallation.fcmLegacy.ts b/sdk/notificationhubs/notification-hubs/samples-dev/createInstallation.fcmLegacy.ts index b2ce02197253..8b055b90c1fe 100644 --- a/sdk/notificationhubs/notification-hubs/samples-dev/createInstallation.fcmLegacy.ts +++ b/sdk/notificationhubs/notification-hubs/samples-dev/createInstallation.fcmLegacy.ts @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. /** * This sample demonstrates how the createOrUpdateInstallation() method can be used to create or overwrite an @@ -29,7 +29,7 @@ const hubName = process.env.NOTIFICATION_HUB_NAME || ""; const DUMMY_REGISTRATION = "bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1"; const gcmRegistrationId = process.env.FCM_REGISTRATION_ID || DUMMY_REGISTRATION; -async function main() { +async function main(): Promise { const context = createClientContext(connectionString, hubName); const installation = createFcmLegacyInstallation({ diff --git a/sdk/notificationhubs/notification-hubs/samples-dev/createInstallation.fcmV1.ts b/sdk/notificationhubs/notification-hubs/samples-dev/createInstallation.fcmV1.ts new file mode 100644 index 000000000000..ef50f29277cf --- /dev/null +++ b/sdk/notificationhubs/notification-hubs/samples-dev/createInstallation.fcmV1.ts @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +/** + * This sample demonstrates how the createOrUpdateInstallation() method can be used to create or overwrite an + * installation in place. + * + * See https://docs.microsoft.com/azure/notification-hubs/notification-hubs-push-notification-registration-management + * to learn about installations. + * + * + * @summary Demonstrates how to create or overwrite an installation using Azure Notification Hubs + * @azsdk-weight 100 + */ + +import * as dotenv from "dotenv"; +import { createClientContext, createOrUpdateInstallation } from "@azure/notification-hubs/api"; +import { createFcmV1Installation } from "@azure/notification-hubs/models"; +import { randomUUID } from "@azure/core-util"; + +// Load the .env file if it exists +dotenv.config(); + +// Define connection string and hub name +const connectionString = process.env.NOTIFICATIONHUBS_CONNECTION_STRING || ""; +const hubName = process.env.NOTIFICATION_HUB_NAME || ""; + +// Define message constants +const DUMMY_REGISTRATION = "00fc13adff785122b4ad28809a3420982341241421348097878e577c991de8f0"; +const fcmV1RegistrationId = process.env.FCMV1_REGISTRATION_ID || DUMMY_REGISTRATION; + +async function main(): Promise { + const context = createClientContext(connectionString, hubName); + + const installation = createFcmV1Installation({ + installationId: randomUUID(), + pushChannel: fcmV1RegistrationId, + tags: ["likes_hockey", "likes_football"], + }); + + const result = await createOrUpdateInstallation(context, installation); + console.log(`Tracking ID: ${result.trackingId}`); + console.log(`Correlation ID: ${result.correlationId}`); +} + +main().catch((err) => { + console.log("createInstallation Sample: Error occurred: ", err); + process.exit(1); +}); diff --git a/sdk/notificationhubs/notification-hubs/samples-dev/createInstallation.ts b/sdk/notificationhubs/notification-hubs/samples-dev/createInstallation.ts index 002ef8625cc5..272b06698e61 100644 --- a/sdk/notificationhubs/notification-hubs/samples-dev/createInstallation.ts +++ b/sdk/notificationhubs/notification-hubs/samples-dev/createInstallation.ts @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. /** * This sample demonstrates how the createOrUpdateInstallation() method can be used to create or overwrite an @@ -29,7 +29,7 @@ const hubName = process.env.NOTIFICATION_HUB_NAME || ""; const DUMMY_DEVICE = "00fc13adff785122b4ad28809a3420982341241421348097878e577c991de8f0"; const deviceToken = process.env.APNS_DEVICE_TOKEN || DUMMY_DEVICE; -async function main() { +async function main(): Promise { const context = createClientContext(connectionString, hubName); const installation = createAppleInstallation({ diff --git a/sdk/notificationhubs/notification-hubs/samples-dev/createOrUpdateRegistration.ts b/sdk/notificationhubs/notification-hubs/samples-dev/createOrUpdateRegistration.ts index 99d1347a6b80..8146e0b14242 100644 --- a/sdk/notificationhubs/notification-hubs/samples-dev/createOrUpdateRegistration.ts +++ b/sdk/notificationhubs/notification-hubs/samples-dev/createOrUpdateRegistration.ts @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. /** * This sample demonstrates how the createOrUpdateRegistration() method can be used to register a device with Azure @@ -33,7 +33,7 @@ const hubName = process.env.NOTIFICATION_HUB_NAME || ""; const DUMMY_DEVICE = "00fc13adff785122b4ad28809a3420982341241421348097878e577c991de8f0"; const deviceToken = process.env.APNS_DEVICE_TOKEN || DUMMY_DEVICE; -async function main() { +async function main(): Promise { const context = createClientContext(connectionString, hubName); const registrationId = await createRegistrationId(context); diff --git a/sdk/notificationhubs/notification-hubs/samples-dev/createRegistration.fcmLegacy.ts b/sdk/notificationhubs/notification-hubs/samples-dev/createRegistration.fcmLegacy.ts index 3ee87f31d709..ea674b405d68 100644 --- a/sdk/notificationhubs/notification-hubs/samples-dev/createRegistration.fcmLegacy.ts +++ b/sdk/notificationhubs/notification-hubs/samples-dev/createRegistration.fcmLegacy.ts @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. /** * This sample demonstrates how the createRegistration() method can be used to register a device with Azure @@ -28,7 +28,7 @@ const hubName = process.env.NOTIFICATION_HUB_NAME || ""; const DUMMY_REGISTRATION = "bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1"; const gcmRegistrationId = process.env.FCM_REGISTRATION_ID || DUMMY_REGISTRATION; -async function main() { +async function main(): Promise { const context = createClientContext(connectionString, hubName); const registration = createFcmLegacyRegistrationDescription({ diff --git a/sdk/notificationhubs/notification-hubs/samples-dev/createRegistration.fcmV1.ts b/sdk/notificationhubs/notification-hubs/samples-dev/createRegistration.fcmV1.ts new file mode 100644 index 000000000000..569b96d0f68a --- /dev/null +++ b/sdk/notificationhubs/notification-hubs/samples-dev/createRegistration.fcmV1.ts @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +/** + * This sample demonstrates how the createRegistration() method can be used to register a device with Azure + * Notification Hubs using the Registration APIs. + * + * See https://docs.microsoft.com/azure/notification-hubs/notification-hubs-push-notification-registration-management + * to learn about registrations. + * + * + * @summary Demonstrates how to create a registration description using Azure Notification hubs. + * @azsdk-weight 100 + */ + +import * as dotenv from "dotenv"; +import { createClientContext, createRegistration } from "@azure/notification-hubs/api"; +import { createFcmV1RegistrationDescription } from "@azure/notification-hubs/models"; + +// Load the .env file if it exists +dotenv.config(); + +// Define connection string and hub name +const connectionString = process.env.NOTIFICATIONHUBS_CONNECTION_STRING || ""; +const hubName = process.env.NOTIFICATION_HUB_NAME || ""; + +// Define message constants +const DUMMY_REGISTRATION = "00fc13adff785122b4ad28809a3420982341241421348097878e577c991de8f0"; +const fcmV1RegistrationId = process.env.FCMV1_REGISTRATION_ID || DUMMY_REGISTRATION; + +async function main(): Promise { + const context = createClientContext(connectionString, hubName); + + const registration = createFcmV1RegistrationDescription({ + fcmV1RegistrationId, + tags: ["likes_football", "likes_hockey"], + }); + + const registrationResponse = await createRegistration(context, registration); + + console.log(`Registration ID: ${registrationResponse.registrationId}`); +} + +main().catch((err) => { + console.log("createRegistration Sample: Error occurred: ", err); + process.exit(1); +}); diff --git a/sdk/notificationhubs/notification-hubs/samples-dev/createRegistration.ts b/sdk/notificationhubs/notification-hubs/samples-dev/createRegistration.ts index 7b8ff268bf26..877252656fc1 100644 --- a/sdk/notificationhubs/notification-hubs/samples-dev/createRegistration.ts +++ b/sdk/notificationhubs/notification-hubs/samples-dev/createRegistration.ts @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. /** * This sample demonstrates how the createRegistration() method can be used to register a device with Azure @@ -28,7 +28,7 @@ const hubName = process.env.NOTIFICATION_HUB_NAME || ""; const DUMMY_DEVICE = "00fc13adff785122b4ad28809a3420982341241421348097878e577c991de8f0"; const deviceToken = process.env.APNS_DEVICE_TOKEN || DUMMY_DEVICE; -async function main() { +async function main(): Promise { const context = createClientContext(connectionString, hubName); const registration = createAppleRegistrationDescription({ diff --git a/sdk/notificationhubs/notification-hubs/samples-dev/deleteRegistrations.ts b/sdk/notificationhubs/notification-hubs/samples-dev/deleteRegistrations.ts index 790dc76b2bef..8d226d6ac34f 100644 --- a/sdk/notificationhubs/notification-hubs/samples-dev/deleteRegistrations.ts +++ b/sdk/notificationhubs/notification-hubs/samples-dev/deleteRegistrations.ts @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. /** * This sample demonstrates how to clean up a Notification Hub by removing all registrations. @@ -26,12 +26,12 @@ dotenv.config(); const connectionString = process.env.NOTIFICATIONHUBS_CONNECTION_STRING || ""; const hubName = process.env.NOTIFICATION_HUB_NAME || ""; -async function main() { +async function main(): Promise { const context = createClientContext(connectionString, hubName); // Unlimited - let allRegistrations = listRegistrations(context); - let page = 0; + const allRegistrations = listRegistrations(context); + const page = 0; for await (const pages of allRegistrations.byPage()) { console.log(`Page number ${page}`); for (const item of pages) { diff --git a/sdk/notificationhubs/notification-hubs/samples-dev/exportRegistrationsJob.polling.ts b/sdk/notificationhubs/notification-hubs/samples-dev/exportRegistrationsJob.polling.ts index 40c8d5a2b1f3..56808fa1e60b 100644 --- a/sdk/notificationhubs/notification-hubs/samples-dev/exportRegistrationsJob.polling.ts +++ b/sdk/notificationhubs/notification-hubs/samples-dev/exportRegistrationsJob.polling.ts @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. /** * This sample demonstrates how the createNotificationJob() method can be used to export registrations @@ -27,7 +27,7 @@ const hubName = process.env.NOTIFICATION_HUB_NAME || ""; // Define export job parameters const outputContainerUrl = process.env.OUTPUT_CONTAINER_URL || ""; -async function main() { +async function main(): Promise { const context = createClientContext(connectionString, hubName); let exportJob: NotificationHubJob = { diff --git a/sdk/notificationhubs/notification-hubs/samples-dev/exportRegistrationsJob.ts b/sdk/notificationhubs/notification-hubs/samples-dev/exportRegistrationsJob.ts index 4bf4cd5c2b49..7ef65ad0f5e2 100644 --- a/sdk/notificationhubs/notification-hubs/samples-dev/exportRegistrationsJob.ts +++ b/sdk/notificationhubs/notification-hubs/samples-dev/exportRegistrationsJob.ts @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. /** * This sample demonstrates how the createNotificationJob() method can be used to export registrations @@ -32,7 +32,7 @@ const hubName = process.env.NOTIFICATION_HUB_NAME || ""; // Define export job parameters const outputContainerUrl = process.env.OUTPUT_CONTAINER_URL || ""; -async function main() { +async function main(): Promise { const context = createClientContext(connectionString, hubName); let exportJob: NotificationHubJob = { diff --git a/sdk/notificationhubs/notification-hubs/samples-dev/importRegistrationsJob.poller.ts b/sdk/notificationhubs/notification-hubs/samples-dev/importRegistrationsJob.poller.ts index 7c770b8bc240..733dcc03c239 100644 --- a/sdk/notificationhubs/notification-hubs/samples-dev/importRegistrationsJob.poller.ts +++ b/sdk/notificationhubs/notification-hubs/samples-dev/importRegistrationsJob.poller.ts @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. /** * This sample demonstrates how the createNotificationJob() method can be used to import registrations @@ -28,7 +28,7 @@ const hubName = process.env.NOTIFICATION_HUB_NAME || ""; const outputContainerUrl = process.env.OUTPUT_CONTAINER_URL || ""; const importFileUrl = process.env.IMPORT_FILE_URL || ""; -async function main() { +async function main(): Promise { const context = createClientContext(connectionString, hubName); let importJob: NotificationHubJob = { diff --git a/sdk/notificationhubs/notification-hubs/samples-dev/importRegistrationsJob.ts b/sdk/notificationhubs/notification-hubs/samples-dev/importRegistrationsJob.ts index 04c9be13dca5..07942242722a 100644 --- a/sdk/notificationhubs/notification-hubs/samples-dev/importRegistrationsJob.ts +++ b/sdk/notificationhubs/notification-hubs/samples-dev/importRegistrationsJob.ts @@ -1,6 +1,5 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. /** * This sample demonstrates how the createNotificationJob() method can be used to import registrations * descriptions from an existing set of exports. @@ -33,7 +32,7 @@ const hubName = process.env.NOTIFICATION_HUB_NAME || ""; const outputContainerUrl = process.env.OUTPUT_CONTAINER_URL || ""; const importFileUrl = process.env.IMPORT_FILE_URL || ""; -async function main() { +async function main(): Promise { const context = createClientContext(connectionString, hubName); let importJob: NotificationHubJob = { diff --git a/sdk/notificationhubs/notification-hubs/samples-dev/listRegistrations.ts b/sdk/notificationhubs/notification-hubs/samples-dev/listRegistrations.ts index f52c07511133..ef6696e08995 100644 --- a/sdk/notificationhubs/notification-hubs/samples-dev/listRegistrations.ts +++ b/sdk/notificationhubs/notification-hubs/samples-dev/listRegistrations.ts @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. /** * This sample demonstrates how the listRegistrations() method can be used to find all registrations for @@ -25,7 +25,7 @@ const hubName = process.env.NOTIFICATION_HUB_NAME || ""; const TOP = 100; -async function main() { +async function main(): Promise { const context = createClientContext(connectionString, hubName); // Unlimited diff --git a/sdk/notificationhubs/notification-hubs/samples-dev/listRegistrationsByChannel.ts b/sdk/notificationhubs/notification-hubs/samples-dev/listRegistrationsByChannel.ts index 75197efc7b48..d14a9fca79e2 100644 --- a/sdk/notificationhubs/notification-hubs/samples-dev/listRegistrationsByChannel.ts +++ b/sdk/notificationhubs/notification-hubs/samples-dev/listRegistrationsByChannel.ts @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. /** * This sample demonstrates how the listRegistrations() method can be used to find all registrations for @@ -30,7 +30,7 @@ const deviceToken = process.env.APNS_DEVICE_TOKEN || DUMMY_DEVICE; const TOP = 100; -async function main() { +async function main(): Promise { const context = createClientContext(connectionString, hubName); const device: AppleRegistrationChannel = { diff --git a/sdk/notificationhubs/notification-hubs/samples-dev/listRegistrationsByTag.ts b/sdk/notificationhubs/notification-hubs/samples-dev/listRegistrationsByTag.ts index 07e689eceeb9..3d2707acc0ee 100644 --- a/sdk/notificationhubs/notification-hubs/samples-dev/listRegistrationsByTag.ts +++ b/sdk/notificationhubs/notification-hubs/samples-dev/listRegistrationsByTag.ts @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. /** * This sample demonstrates how the listRegistrationsByTag() method can be used to list all registrations with @@ -26,7 +26,7 @@ const hubName = process.env.NOTIFICATION_HUB_NAME || ""; const TOP = 100; const TAG = "likes_hockey"; -async function main() { +async function main(): Promise { const context = createClientContext(connectionString, hubName); // Unlimited diff --git a/sdk/notificationhubs/notification-hubs/samples-dev/scheduledSendBroadcastNotification.ts b/sdk/notificationhubs/notification-hubs/samples-dev/scheduledSendBroadcastNotification.ts index c6aa57da2442..99149bd420a3 100644 --- a/sdk/notificationhubs/notification-hubs/samples-dev/scheduledSendBroadcastNotification.ts +++ b/sdk/notificationhubs/notification-hubs/samples-dev/scheduledSendBroadcastNotification.ts @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. /** * This sample demonstrates how the scheduleNotification() method can be used to schedule a broadcast @@ -26,7 +26,7 @@ dotenv.config(); const connectionString = process.env.NOTIFICATIONHUBS_CONNECTION_STRING || ""; const hubName = process.env.NOTIFICATION_HUB_NAME || ""; -async function main() { +async function main(): Promise { const context = createClientContext(connectionString, hubName); const messageBody = `{ "aps" : { "alert" : "Hello" } }`; diff --git a/sdk/notificationhubs/notification-hubs/samples-dev/scheduledSendNotification.ts b/sdk/notificationhubs/notification-hubs/samples-dev/scheduledSendNotification.ts index 873c619b3f6e..a4475f0bc1aa 100644 --- a/sdk/notificationhubs/notification-hubs/samples-dev/scheduledSendNotification.ts +++ b/sdk/notificationhubs/notification-hubs/samples-dev/scheduledSendNotification.ts @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. /** * This sample demonstrates how the scheduleNotification() method can be used to schedule a tag expression @@ -26,7 +26,7 @@ dotenv.config(); const connectionString = process.env.NOTIFICATIONHUBS_CONNECTION_STRING || ""; const hubName = process.env.NOTIFICATION_HUB_NAME || ""; -async function main() { +async function main(): Promise { const context = createClientContext(connectionString, hubName); const messageBody = `{ "aps" : { "alert" : "Hello" } }`; diff --git a/sdk/notificationhubs/notification-hubs/samples-dev/sendBroadcastNotification.ts b/sdk/notificationhubs/notification-hubs/samples-dev/sendBroadcastNotification.ts index caeac8c00717..d285b0d5d85d 100644 --- a/sdk/notificationhubs/notification-hubs/samples-dev/sendBroadcastNotification.ts +++ b/sdk/notificationhubs/notification-hubs/samples-dev/sendBroadcastNotification.ts @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. /** * This sample demonstrates how the sendBroadcastNotification() method can be used to send a broadcast @@ -35,7 +35,7 @@ dotenv.config(); const connectionString = process.env.NOTIFICATIONHUBS_CONNECTION_STRING || ""; const hubName = process.env.NOTIFICATION_HUB_NAME || ""; -async function main() { +async function main(): Promise { const context = createClientContext(connectionString, hubName); const messageBody = `{ "aps" : { "alert" : "Hello" } }`; diff --git a/sdk/notificationhubs/notification-hubs/samples-dev/sendDirectNotification.fcmLegacy.ts b/sdk/notificationhubs/notification-hubs/samples-dev/sendDirectNotification.fcmLegacy.ts index ba5a9184cf95..d36e2d7fc6c3 100644 --- a/sdk/notificationhubs/notification-hubs/samples-dev/sendDirectNotification.fcmLegacy.ts +++ b/sdk/notificationhubs/notification-hubs/samples-dev/sendDirectNotification.fcmLegacy.ts @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. /** * This sample demonstrates how the sendNotification() method can be used to send a direct @@ -40,7 +40,7 @@ const hubName = process.env.NOTIFICATION_HUB_NAME || ""; const DUMMY_REGISTRATION = "bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1"; const gcmRegistrationId = process.env.FCM_REGISTRATION_ID || DUMMY_REGISTRATION; -async function main() { +async function main(): Promise { const context = createClientContext(connectionString, hubName); const messageBody = `{ diff --git a/sdk/notificationhubs/notification-hubs/samples-dev/sendDirectNotification.fcmV1.ts b/sdk/notificationhubs/notification-hubs/samples-dev/sendDirectNotification.fcmV1.ts new file mode 100644 index 000000000000..aad87a57eb41 --- /dev/null +++ b/sdk/notificationhubs/notification-hubs/samples-dev/sendDirectNotification.fcmV1.ts @@ -0,0 +1,108 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +/** + * This sample demonstrates how the sendNotification() method can be used to send a direct + * notification using Firebase Legacy HTTP. This sends a JSON message to an Firebase given registration ID and returns + * a Tracking ID which can be used for troubleshooting with the Azure Notification Hubs team. + * + * See https://docs.microsoft.com/rest/api/notificationhubs/direct-send + * to learn about Direct Send. + * + * + * @summary Demonstrates how to send direct notifications using Azure Notification Hubs + * @azsdk-weight 100 + */ + +import * as dotenv from "dotenv"; +import { + NotificationDetails, + NotificationOutcomeState, + createFcmLegacyNotification, +} from "@azure/notification-hubs/models"; +import { + NotificationHubsClientContext, + createClientContext, + getNotificationOutcomeDetails, + sendNotification, +} from "@azure/notification-hubs/api"; +import { delay } from "@azure/core-util"; +import { isRestError } from "@azure/core-rest-pipeline"; + +// Load the .env file if it exists +dotenv.config(); + +// Define connection string and hub name +const connectionString = process.env.NOTIFICATIONHUBS_CONNECTION_STRING || ""; +const hubName = process.env.NOTIFICATION_HUB_NAME || ""; + +// Define message constants +const DUMMY_REGISTRATION = "00fc13adff785122b4ad28809a3420982341241421348097878e577c991de8f0"; +const fcmV1RegistrationId = process.env.FCMV1_REGISTRATION_ID || DUMMY_REGISTRATION; + +async function main(): Promise { + const context = createClientContext(connectionString, hubName); + + const messageBody = `{ + "notification":{ + "title":"Notification Hub Test Notification", + "body":"This is a sample notification delivered by Azure Notification Hubs." + }, + "data":{ + "property1":"value1", + "property2":42 + } +}`; + + const notification = createFcmLegacyNotification({ + body: messageBody, + }); + + const result = await sendNotification(context, notification, { + deviceHandle: fcmV1RegistrationId, + }); + + console.log(`Direct send Tracking ID: ${result.trackingId}`); + console.log(`Direct send Correlation ID: ${result.correlationId}`); + + // Only available in Standard SKU and above + if (result.notificationId) { + console.log(`Direct send Notification ID: ${result.notificationId}`); + + const results = await getNotificationDetails(context, result.notificationId); + if (results) { + console.log(JSON.stringify(results, null, 2)); + } + } +} + +async function getNotificationDetails( + context: NotificationHubsClientContext, + notificationId: string, +): Promise { + let state: NotificationOutcomeState = "Enqueued"; + let count = 0; + let result: NotificationDetails | undefined; + while ((state === "Enqueued" || state === "Processing") && count++ < 10) { + try { + result = await getNotificationOutcomeDetails(context, notificationId); + state = result.state!; + } catch (e) { + // Possible to get 404 for when it doesn't exist yet. + if (isRestError(e) && e.statusCode === 404) { + continue; + } else { + throw e; + } + } + + await delay(1000); + } + + return result; +} + +main().catch((err) => { + console.log("sendDirectNotification Sample: Error occurred: ", err); + process.exit(1); +}); diff --git a/sdk/notificationhubs/notification-hubs/samples-dev/sendDirectNotification.ts b/sdk/notificationhubs/notification-hubs/samples-dev/sendDirectNotification.ts index 0efe21b8f674..9063503e5476 100644 --- a/sdk/notificationhubs/notification-hubs/samples-dev/sendDirectNotification.ts +++ b/sdk/notificationhubs/notification-hubs/samples-dev/sendDirectNotification.ts @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. /** * This sample demonstrates how the sendDirectNotification() method can be used to send a direct @@ -40,7 +40,7 @@ const hubName = process.env.NOTIFICATION_HUB_NAME || ""; const DUMMY_DEVICE = "00fc13adff785122b4ad28809a3420982341241421348097878e577c991de8f0"; const deviceHandle = process.env.APNS_DEVICE_TOKEN || DUMMY_DEVICE; -async function main() { +async function main(): Promise { const context = createClientContext(connectionString, hubName); /* Can also send a stringified JSON object diff --git a/sdk/notificationhubs/notification-hubs/samples-dev/sendDirectNotificationBatch.ts b/sdk/notificationhubs/notification-hubs/samples-dev/sendDirectNotificationBatch.ts index a2693453d823..1e6bc299e00c 100644 --- a/sdk/notificationhubs/notification-hubs/samples-dev/sendDirectNotificationBatch.ts +++ b/sdk/notificationhubs/notification-hubs/samples-dev/sendDirectNotificationBatch.ts @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. /** * This sample demonstrates how the sendDirectNotification() method can be used to send a direct batch @@ -40,7 +40,7 @@ const hubName = process.env.NOTIFICATION_HUB_NAME || ""; const DUMMY_DEVICE = "00fc13adff785122b4ad28809a3420982341241421348097878e577c991de8f0"; const deviceHandle = process.env.APNS_DEVICE_TOKENS?.split(",") || [DUMMY_DEVICE]; -async function main() { +async function main(): Promise { const context = createClientContext(connectionString, hubName); const messageBody = `{ "aps" : { "alert" : { title: "Hello", body: "Hello there SDK Review!" } } }`; diff --git a/sdk/notificationhubs/notification-hubs/samples-dev/sendTagExpression.ts b/sdk/notificationhubs/notification-hubs/samples-dev/sendTagExpression.ts index c7ed1b5a4071..f044f99a4add 100644 --- a/sdk/notificationhubs/notification-hubs/samples-dev/sendTagExpression.ts +++ b/sdk/notificationhubs/notification-hubs/samples-dev/sendTagExpression.ts @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. /** * This sample demonstrates how the sendNotification() method can be used to send a tag expression @@ -36,7 +36,7 @@ dotenv.config(); const connectionString = process.env.NOTIFICATIONHUBS_CONNECTION_STRING || ""; const hubName = process.env.NOTIFICATION_HUB_NAME || ""; -async function main() { +async function main(): Promise { const context = createClientContext(connectionString, hubName); const messageBody = `{ "aps" : { "alert" : "Hello" } }`; diff --git a/sdk/notificationhubs/notification-hubs/samples-dev/sendTagsList.ts b/sdk/notificationhubs/notification-hubs/samples-dev/sendTagsList.ts index 6e507784478c..f6f077294f9f 100644 --- a/sdk/notificationhubs/notification-hubs/samples-dev/sendTagsList.ts +++ b/sdk/notificationhubs/notification-hubs/samples-dev/sendTagsList.ts @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. /** * This sample demonstrates how the sendNotification() method can be used to send a tags list @@ -37,7 +37,7 @@ dotenv.config(); const connectionString = process.env.NOTIFICATIONHUBS_CONNECTION_STRING || ""; const hubName = process.env.NOTIFICATION_HUB_NAME || ""; -async function main() { +async function main(): Promise { const context = createClientContext(connectionString, hubName); const messageBody = `{ "aps" : { "alert" : "Hello" } }`; diff --git a/sdk/notificationhubs/notification-hubs/samples-dev/updateInstallation.ts b/sdk/notificationhubs/notification-hubs/samples-dev/updateInstallation.ts index 66f7692247a4..cacfa1a1eb9d 100644 --- a/sdk/notificationhubs/notification-hubs/samples-dev/updateInstallation.ts +++ b/sdk/notificationhubs/notification-hubs/samples-dev/updateInstallation.ts @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. /** * This sample demonstrates how the updateInstallation() method can be used to update an installation using @@ -28,7 +28,7 @@ const hubName = process.env.NOTIFICATION_HUB_NAME || ""; // Define an existing Installation ID. const installationId = process.env.INSTALLATION_ID || ""; -async function main() { +async function main(): Promise { const context = createClientContext(connectionString, hubName); const updates: JsonPatch[] = [ diff --git a/sdk/notificationhubs/notification-hubs/samples-dev/updateRegistration.ts b/sdk/notificationhubs/notification-hubs/samples-dev/updateRegistration.ts index 8efd3d0f47f0..d0f170ce6558 100644 --- a/sdk/notificationhubs/notification-hubs/samples-dev/updateRegistration.ts +++ b/sdk/notificationhubs/notification-hubs/samples-dev/updateRegistration.ts @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. /** * This sample demonstrates how the updateRegistration() method can be used to update a device registration using @@ -31,7 +31,7 @@ const hubName = process.env.NOTIFICATION_HUB_NAME || ""; // Define an existing Registration ID. const registrationId = process.env.REGISTRATION_ID || ""; -async function main() { +async function main(): Promise { const context = createClientContext(connectionString, hubName); const registration = await getRegistration(context, registrationId); diff --git a/sdk/notificationhubs/notification-hubs/src/api/beginSubmitNotificationHubJob.ts b/sdk/notificationhubs/notification-hubs/src/api/beginSubmitNotificationHubJob.ts index fd3458898a96..9efc4b9926d3 100644 --- a/sdk/notificationhubs/notification-hubs/src/api/beginSubmitNotificationHubJob.ts +++ b/sdk/notificationhubs/notification-hubs/src/api/beginSubmitNotificationHubJob.ts @@ -1,11 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { AbortController, AbortSignalLike } from "@azure/abort-controller"; +import { AbortSignalLike } from "@azure/abort-controller"; import { CancelOnProgress, OperationState, SimplePollerLike } from "@azure/core-lro"; import { NotificationHubJob, NotificationHubJobPoller } from "../models/notificationHubJob.js"; import { NotificationHubsClientContext } from "./index.js"; import { PolledOperationOptions } from "../models/options.js"; +import { delay } from "@azure/core-util"; import { getNotificationHubJob } from "./getNotificationHubJob.js"; import { submitNotificationHubJob } from "./submitNotificationHubJob.js"; @@ -71,33 +72,38 @@ export async function beginSubmitNotificationHubJob( pollUntilDone(pollOptions?: { abortSignal?: AbortSignalLike }): Promise { return (resultPromise ??= (async () => { const { abortSignal: inputAbortSignal } = pollOptions || {}; - const { signal: abortSignal } = inputAbortSignal - ? new AbortController([inputAbortSignal, abortController.signal]) - : abortController; - if (!poller.isDone()) { - await poller.poll({ abortSignal }); - while (!poller.isDone()) { - const delay = sleep(currentPollIntervalInMs, abortSignal); - cancelJob = () => abortController.abort(); - await delay; + // In the future we can use AbortSignal.any() instead + function abortListener(): void { + abortController.abort(); + } + const abortSignal = abortController.signal; + if (inputAbortSignal?.aborted) { + abortController.abort(); + } else if (!abortSignal.aborted) { + inputAbortSignal?.addEventListener("abort", abortListener, { once: true }); + } + + try { + if (!poller.isDone()) { await poller.poll({ abortSignal }); + while (!poller.isDone()) { + await delay(currentPollIntervalInMs, { abortSignal }); + await poller.poll({ abortSignal }); + } } + } finally { + inputAbortSignal?.removeEventListener("abort", abortListener); } switch (state.status) { - case "succeeded": { + case "succeeded": return poller.getResult() as NotificationHubJob; - } - case "canceled": { + case "canceled": throw new Error("Operation was canceled"); - } - case "failed": { + case "failed": throw state.error; - } case "notStarted": - case "running": { - // Unreachable - throw new Error(`polling completed without succeeding or failing`); - } + case "running": + throw new Error(`Polling completed without succeeding or failing`); } })().finally(() => { resultPromise = undefined; @@ -139,32 +145,3 @@ export async function beginSubmitNotificationHubJob( return poller; } - -const REJECTED_ERR = new Error("The operation has been aborted"); - -function sleep(ms: number, signal: AbortSignalLike): Promise { - return new Promise((resolve, reject) => { - if (signal.aborted) { - reject(REJECTED_ERR); - return; - } - - const id = setTimeout(() => { - signal.removeEventListener("abort", onAbort); - - if (signal.aborted) { - reject(REJECTED_ERR); - return; - } - - resolve(); - }, ms); - - signal.addEventListener("abort", onAbort, { once: true }); - - function onAbort(): void { - clearTimeout(id); - reject(REJECTED_ERR); - } - }); -} diff --git a/sdk/notificationhubs/notification-hubs/src/models/installation.ts b/sdk/notificationhubs/notification-hubs/src/models/installation.ts index 4b8b3530dc16..7ac46003410f 100644 --- a/sdk/notificationhubs/notification-hubs/src/models/installation.ts +++ b/sdk/notificationhubs/notification-hubs/src/models/installation.ts @@ -136,6 +136,28 @@ export function createFcmLegacyInstallation( }; } +/** + * Represents an Firebase V1 Cloud Messaging based installation. + */ +export interface FcmV1Installation extends DeviceTokenInstallation { + /** + * The platform for the installation. + */ + platform: "fcmv1"; +} + +/** + * Creates an Firebase V1 Cloud Messaging based installation. + * @param installation - A partial installation used to create the Firebase V1 Cloud Messaging installation. + * @returns The newly created Firebase V1 Cloud Messaging installation. + */ +export function createFcmV1Installation(installation: DeviceTokenInstallation): FcmV1Installation { + return { + ...installation, + platform: "fcmv1", + }; +} + /** * Represents a Xiaomi based installation. */ @@ -247,6 +269,7 @@ export type Installation = | BaiduInstallation | BrowserInstallation | FcmLegacyInstallation + | FcmV1Installation | XiaomiInstallation | WindowsInstallation; diff --git a/sdk/notificationhubs/notification-hubs/src/models/notification.ts b/sdk/notificationhubs/notification-hubs/src/models/notification.ts index ffedb357a11e..44646a7fc3e5 100644 --- a/sdk/notificationhubs/notification-hubs/src/models/notification.ts +++ b/sdk/notificationhubs/notification-hubs/src/models/notification.ts @@ -6,6 +6,7 @@ import { AdmNativeMessage, AppleNativeMessage, FirebaseLegacyNativeMessage, + FirebaseV1NativeMessage, } from "./notificationBodyBuilder.js"; import { AppleHeaders, WindowsHeaders } from "./notificationHeaderBuilder.js"; @@ -209,6 +210,47 @@ export function createFcmLegacyNotification( }; } +/** + * Represents an Firebase V1 API notification that can be sent to a device. + */ +export interface FcmV1Notification extends JsonNotification { + /** + * The platform for the push notification. + */ + platform: "fcmv1"; +} + +/** + * Represents an Firebase Legacy notification that can be sent to a device. + */ +export interface FcmV1NotificationParams { + /** + * The body for the push notification. + */ + body: string | FirebaseV1NativeMessage; + + /** + * The headers to include for the push notification. + */ + headers?: Record; +} + +/** + * Creates a notification to send to Firebase. + * @param notification - A partial message used to create a message for Firebase. + * @returns A newly created Firebase notification. + */ +export function createFcmV1Notification(notification: FcmV1NotificationParams): FcmV1Notification { + const body = isString(notification.body) ? notification.body : JSON.stringify(notification.body); + + return { + ...notification, + body, + platform: "fcmv1", + contentType: Constants.JSON_CONTENT_TYPE, + }; +} + /** * Represents a Xiaomi push notification. */ diff --git a/sdk/notificationhubs/notification-hubs/src/models/notificationBodyBuilder.ts b/sdk/notificationhubs/notification-hubs/src/models/notificationBodyBuilder.ts index 1283b8bff8aa..a842bbc34e98 100644 --- a/sdk/notificationhubs/notification-hubs/src/models/notificationBodyBuilder.ts +++ b/sdk/notificationhubs/notification-hubs/src/models/notificationBodyBuilder.ts @@ -432,6 +432,445 @@ export function createFirebaseLegacyNotificationBody( return JSON.stringify(nativeMessage); } +/** + * Represents the targets, options, and payload for HTTP JSON messages for the Firebase V1 interface. + */ +export interface FirebaseV1NativeMessage { + /** + * Custom key-value pairs of the message's payload. + */ + data?: Record; + + /** + * The predefined, user-visible key-value pairs of the notification payload. + */ + notification?: FirebaseV1NativeNotification; + + /** + * Android specific options for messages sent through FCM connection server. + */ + android?: FirebaseV1AndroidConfig; + + /** + * Webpush protocol options. + */ + webpush?: FirebaseV1WebPushConfig; + + /** + * APNs specific options. + */ + apns?: FirebaseV1ApnsConfig; + + /** + * FCM options. + */ + fcm_options?: FirebaseV1FcmOptions; + + /** + * Registration token to send a message to. + */ + token?: string; + + /** + * Topic name to send a message to, e.g. "weather". + */ + topic?: string; + + /** + * Condition to send a message to, e.g. "'foo' in topics && 'bar' in topics". + */ + condition?: string; +} + +/** + * Represents a native FCM V1 notification message payload. + */ +export interface FirebaseV1NativeNotification { + /** + * The notification's title. + */ + title?: string; + + /** + * The notification's body text. + */ + body?: string; + + /** + * Contains the URL of an image that is going to be downloaded on the device and displayed in a notification. + */ + image?: string; +} + +/** + * Android specific options for messages sent through FCM connection server. + */ +export interface FirebaseV1AndroidConfig { + /** + * An identifier of a group of messages that can be collapsed, so that only the last message gets sent when delivery can be resumed. + */ + collapse_key?: string; + + /** + * Message priority. Can take "normal" and "high" values. + */ + priority: "normal" | "high"; + + /** + * How long (in seconds) the message should be kept in FCM storage if the device is offline. + */ + ttl?: string; + + /** + * Package name of the application where the registration token must match in order to receive the message. + */ + restricted_package_name?: string; + + /** + * Custom key-value pairs of the message's payload. + */ + data?: Record; + + /** + * Notification to send to android devices. + */ + notification?: FirebaseV1AndroidNotification; + + /** + * Options for features provided by the FCM SDK for Android. + */ + fcm_options?: FirebaseV1AndroidFcmOptions; + + /** + * If set to true, messages will be allowed to be delivered to the app while the device is in direct boot mode. + */ + direct_boot_ok?: boolean; +} + +/** + * Notification to send to android devices. + */ +export interface FirebaseV1AndroidNotification { + /** + * The notification's title. + */ + title?: string; + + /** + * The notification's body text. + */ + body?: string; + + /** + * The notification's icon. + */ + icon?: string; + + /** + * The notification's icon color, expressed in #rrggbb format. + */ + color?: string; + + /** + * The sound to play when the device receives the notification. + */ + sound?: string; + + /** + * Identifier used to replace existing notifications in the notification drawer. + */ + tag?: string; + + /** + * The action associated with a user click on the notification. + */ + click_action?: string; + + /** + * The key to the body string in the app's string resources to use to localize the body text to the user's current localization. + */ + body_loc_key?: string; + + /** + * Variable string values to be used in place of the format specifiers in body_loc_key to use to localize the body text to the user's current localization. + */ + body_loc_args?: string[]; + + /** + * The key to the title string in the app's string resources to use to localize the title text to the user's current localization. + */ + title_loc_key?: string; + + /** + * Variable string values to be used in place of the format specifiers in title_loc_key to use to localize the title text to the user's current localization. + */ + title_loc_args?: string[]; + + /** + * The notification's channel id (new in Android O). + */ + channel_id?: string; + + /** + * Sets the "ticker" text, which is sent to accessibility services. + */ + ticker?: string; + + /** + * When set to false or unset, the notification is automatically dismissed when the user clicks it in the panel. + */ + sticky?: boolean; + + /** + * Set the time that the event in the notification occurred. + */ + event_time?: string; + + /** + * Set whether or not this notification is relevant only to the current device. + */ + local_only?: boolean; + + notification_priority?: + | "PRIORITY_UNSPECIFIED" + | "PRIORITY_MIN" + | "PRIORITY_LOW" + | "PRIORITY_DEFAULT" + | "PRIORITY_HIGH" + | "PRIORITY_MAX"; + + /** + * If set to true, use the Android framework's default sound for the notification. + */ + default_sound?: boolean; + + /** + * If set to true, use the Android framework's default vibrate pattern for the notification. + */ + default_vibrate_timings?: boolean; + + /** + * If set to true, use the Android framework's default light settings for the notification. + */ + default_light_settings?: boolean; + + /** + * Set the vibration pattern to use. + */ + vibrate_timings?: string[]; + + /** + * Set the Notification.visibility of the notification. + */ + visibility?: "VISIBILITY_UNSPECIFIED" | "PRIVATE" | "PUBLIC" | "SECRET"; + + /** + * Sets the number of items this notification represents. + */ + notification_count?: number; + + /** + * Settings to control the notification's LED blinking rate and color if LED is available on the device. + */ + light_settings?: { + color: { + red: number; + green: number; + blue: number; + alpha: number; + }; + light_on_duration: string; + light_off_duration: string; + }; + + /** + * Contains the URL of an image that is going to be displayed in a notification. + */ + image?: string; +} + +/** + * Options for features provided by the FCM SDK for Android. + */ +export interface FirebaseV1AndroidFcmOptions { + /** + * The label associated with the message's analytics data. + */ + analytics_label?: string; +} + +export interface FirebaseV1WebPushConfig { + /** + * A collection of WebPush protocol options. + */ + headers?: Record; + + /** + * A collection of WebPush protocol options. + */ + data?: Record; + + /** + * Web Notification options as a JSON object. + */ + notification?: FirebaseV1WebPushNotification; + + /** + * A collection of WebPush protocol options. + */ + fcm_options?: FirebaseV1WebPushFcmOptions; +} + +/** + * Represents a Web Push notification payload. + */ +export interface FirebaseV1WebPushNotification { + /** + * An array of actions to display in the notification. + */ + actions?: { + action: string; + title: string; + icon: string; + }[]; + + /** + * Defines a title for the notification. + */ + title?: string; + + /** + * The body string of the notification + */ + body?: string; + + /** + * A string containing the URL of an icon to be displayed in the notification. + */ + icon?: string; + + /** + * A string containing the URL of an image to represent the notification when there is not enough space to display the notification itself such as for example, the Android Notification Bar. + */ + badge?: string; + + /** + * The notification's data. + */ + data?: Record; + + /** + * The direction in which to display the notification. + */ + dir?: "auto" | "ltr" | "rtl"; + + /** + * A string containing the URL of an image to be displayed in the notification. + */ + image?: string; + + /** + * The notification's language. + */ + lang?: string; + + /** + * A boolean value specifying whether the user should be notified after a new notification replaces an old one. + */ + renotify?: boolean; + + /** + * Indicates that a notification should remain active until the user clicks or dismisses it, rather than closing automatically. + */ + requireInteraction?: boolean; + + /** + * A boolean value specifying whether the notification is silent + */ + silent?: boolean; + + /** + * A string representing an identifying tag for the notification. + */ + tag?: string; + + /** + * A number representing the time at which a notification is created or applicable + */ + timestamp?: number; + + /** + * A vibration pattern for the device's vibration hardware to emit with the notification. + */ + vibrate?: number[]; +} + +/** + * Options for features provided by the FCM SDK for Web. + */ +export interface FirebaseV1WebPushFcmOptions { + /** + * The link to open when the user clicks on the notification. + */ + link?: string; + + /** + * Label associated with the message's analytics data. + */ + analytics_label?: string; +} + +/** + * Apple Push Notification Service specific options. + */ +export interface FirebaseV1ApnsConfig { + /** + * A collection of APNs headers. + */ + headers?: Record; + + /** + * A collection of APNs headers. + */ + payload?: AppleNativeMessage; + + /** + * A collection of APNs headers. + */ + fcm_options?: FirebaseV1ApnsFcmOptions; +} + +/** + * Options for features provided by the FCM SDK for iOS. + */ +export interface FirebaseV1ApnsFcmOptions { + /** + * Label associated with the message's analytics data. + */ + analytics_label: string; + + /** + * Contains the URL of an image that is going to be displayed in a notification. + */ + image: string; +} + +export interface FirebaseV1FcmOptions { + /** + * Label associated with the message's analytics data. + */ + analytics_label: string; +} + +/** + * Creates a FcmV1Notification from a native Firebase payload. + * @param nativeMessage - The native message payload to send to Notification Hubs. + * @returns The JSON body to send to Notification Hubs. + */ +export function createFirebaseV1NotificationBody(nativeMessage: FirebaseV1NativeMessage): string { + return JSON.stringify(nativeMessage); +} + /** * Describes ADM notification messages. */ diff --git a/sdk/notificationhubs/notification-hubs/src/models/registration.ts b/sdk/notificationhubs/notification-hubs/src/models/registration.ts index ae4eec6ab0ad..70fedab692b0 100644 --- a/sdk/notificationhubs/notification-hubs/src/models/registration.ts +++ b/sdk/notificationhubs/notification-hubs/src/models/registration.ts @@ -15,6 +15,8 @@ export type RegistrationType = | "BrowserTemplate" | "Gcm" | "GcmTemplate" + | "FcmV1" + | "FcmV1Template" | "Mpns" | "MpnsTemplate" | "Xiaomi" @@ -424,6 +426,72 @@ export function createFcmLegacyTemplateRegistrationDescription( }; } +/** + * Represents Notification Hub registration description for Google Cloud Messaging. + */ +export interface FcmV1RegistrationDescriptionCommon extends RegistrationDescriptionCommon { + /** + * Registration id obtained from the Firebase Cloud Messaging service. + */ + fcmV1RegistrationId: string; +} + +/** + * Represents Notification Hub registration description for Google Cloud Messaging. + */ +export interface FcmV1RegistrationDescription extends FcmV1RegistrationDescriptionCommon { + /** + * The kind of the registration. + */ + kind: "FcmV1"; +} + +/** + * Creates a Firebase V1 registration description. + * @param description - A partial FCM V1 registration description. + * @returns A created FCM V1 registration description. + */ +export function createFcmV1RegistrationDescription( + description: FcmV1RegistrationDescriptionCommon, +): FcmV1RegistrationDescription { + return { + ...description, + kind: "FcmV1", + }; +} + +/** + * Represents Notification Hub template registration description for Firebase V1 Cloud Messaging. + */ +export interface FcmV1TemplateRegistrationDescriptionCommon + extends FcmV1RegistrationDescriptionCommon, + TemplateRegistrationDescription {} + +/** + * Represents Notification Hub template registration description for Firebase V1 Cloud Messaging. + */ +export interface FcmV1TemplateRegistrationDescription + extends FcmV1TemplateRegistrationDescriptionCommon { + /** + * The kind of the registration. + */ + kind: "FcmV1Template"; +} + +/** + * Creates a FCM V1 template registration description. + * @param description - A partial FCM V1 template registration description. + * @returns A created FCM V1 template registration description. + */ +export function createFcmV1TemplateRegistrationDescription( + description: FcmV1TemplateRegistrationDescriptionCommon, +): FcmV1TemplateRegistrationDescription { + return { + ...description, + kind: "FcmV1Template", + }; +} + /** * Represents a Windows Phone Notification Services registration description. * @deprecated Windows Phone is no longer supported. @@ -640,6 +708,8 @@ export type RegistrationDescription = | BrowserTemplateRegistrationDescription | GcmRegistrationDescription | GcmTemplateRegistrationDescription + | FcmV1RegistrationDescription + | FcmV1TemplateRegistrationDescription | MpnsRegistrationDescription | MpnsTemplateRegistrationDescription | XiaomiRegistrationDescription @@ -729,6 +799,20 @@ export interface FirebaseLegacyRegistrationChannel { kind: "gcm"; } +/** + * Describes an Firebase Legacy Registration channel query. + */ +export interface FirebaseV1RegistrationChannel { + /** + * The FCM V1 registration ID. + */ + fcmV1RegistrationId: string; + /** + * The kind of the registration channel. + */ + kind: "fcmv1"; +} + /** * Describes an Windows Notification Services Registration channel query. */ @@ -766,5 +850,6 @@ export type RegistrationChannel = | BaiduRegistrationChannel | BrowserRegistrationChannel | FirebaseLegacyRegistrationChannel + | FirebaseV1RegistrationChannel | XiaomiRegistrationChannel | WindowsRegistrationChannel; diff --git a/sdk/notificationhubs/notification-hubs/src/serializers/registrationSerializer.ts b/sdk/notificationhubs/notification-hubs/src/serializers/registrationSerializer.ts index 4c400599167f..564730ca4ac5 100644 --- a/sdk/notificationhubs/notification-hubs/src/serializers/registrationSerializer.ts +++ b/sdk/notificationhubs/notification-hubs/src/serializers/registrationSerializer.ts @@ -12,6 +12,8 @@ import { BrowserTemplateRegistrationDescription, GcmRegistrationDescription, GcmTemplateRegistrationDescription, + FcmV1RegistrationDescription, + FcmV1TemplateRegistrationDescription, MpnsRegistrationDescription, MpnsTemplateRegistrationDescription, RegistrationDescription, @@ -103,6 +105,20 @@ export interface RegistrationDescriptionParser { createBrowserTemplateRegistrationDescription: ( rawRegistrationDescription: Record, ) => BrowserTemplateRegistrationDescription; + /** + * @internal + * Creates a Firebase V1 Cloud Messaging (FCM) registration description from the incoming parsed XML. + */ + createFcmV1RegistrationDescription: ( + rawRegistrationDescription: Record, + ) => FcmV1RegistrationDescription; + /** + * @internal + * Creates a Firebase V1 Cloud Messaging (FCM) template registration description from the incoming parsed XML. + */ + createFcmV1TemplateRegistrationDescription: ( + rawRegistrationDescription: Record, + ) => FcmV1TemplateRegistrationDescription; /** * @internal * Creates a Google Cloud Messaging (GCM) registration description from the incoming parsed XML. @@ -330,6 +346,36 @@ export const registrationDescriptionParser: RegistrationDescriptionParser = { kind: "BrowserTemplate", }; }, + /** + * @internal + * Creates an GCM registration description from incoming XML property bag. + */ + createFcmV1RegistrationDescription( + rawRegistrationDescription: Record, + ): FcmV1RegistrationDescription { + return { + fcmV1RegistrationId: getString( + rawRegistrationDescription["FcmV1RegistrationId"], + "fcmV1RegistrationId", + ), + ...createRegistrationDescription(rawRegistrationDescription), + kind: "FcmV1", + }; + }, + + /** + * @internal + * Creates an FCM template registration description from incoming XML property bag. + */ + createFcmV1TemplateRegistrationDescription( + rawRegistrationDescription: Record, + ): FcmV1TemplateRegistrationDescription { + return { + ...this.createFcmV1RegistrationDescription(rawRegistrationDescription), + ...createTemplateRegistrationDescription(rawRegistrationDescription), + kind: "FcmV1Template", + }; + }, /** * @internal @@ -561,6 +607,20 @@ export interface RegistrationDescriptionSerializer { serializeBrowserTemplateRegistrationDescription( description: Omit, ): Record; + /** + * @internal + * Serializes a Google Cloud Messaging (GCM) registration description into an XML object for serialization. + */ + serializeFcmV1RegistrationDescription( + description: Omit, + ): Record; + /** + * @internal + * Serializes a Google Cloud Messaging (GCM) template registration description into an XML object for serialization. + */ + serializeFcmV1TemplateRegistrationDescription( + description: Omit, + ): Record; /** * @internal * Serializes a Google Cloud Messaging (GCM) registration description into an XML object for serialization. @@ -765,7 +825,32 @@ export const registrationDescriptionSerializer: RegistrationDescriptionSerialize /** * @internal - * @deprecated Should use FCM registrations instead of GCM. + * Serializes an existing FCM V1 registration description to an object for serialization. + */ + serializeFcmV1RegistrationDescription( + description: Omit, + ): Record { + return { + ...serializeRegistrationDescription(description), + FcmV1RegistrationId: getString(description.fcmV1RegistrationId, "fcmRegistrationId"), + }; + }, + + /** + * @internal + * Serializes an existing FCM V1 template registration description to an object for serialization. + */ + serializeFcmV1TemplateRegistrationDescription( + description: Omit, + ): Record { + return { + ...this.serializeFcmV1RegistrationDescription(description), + ...serializeTemplateRegistrationDescription(description), + }; + }, + + /** + * @internal * Serializes an existing GCM registration description to an object for serialization. */ serializeGcmRegistrationDescription( @@ -779,7 +864,6 @@ export const registrationDescriptionSerializer: RegistrationDescriptionSerialize /** * @internal - * @deprecated Should use FCM template registrations instead of GCM. * Serializes an existing GCM template registration description to an object for serialization. */ serializeGcmTemplateRegistrationDescription( diff --git a/sdk/notificationhubs/notification-hubs/test/internal/unit/registrationSerializer.spec.ts b/sdk/notificationhubs/notification-hubs/test/internal/unit/registrationSerializer.spec.ts index 149c18216939..a78d91797ea6 100644 --- a/sdk/notificationhubs/notification-hubs/test/internal/unit/registrationSerializer.spec.ts +++ b/sdk/notificationhubs/notification-hubs/test/internal/unit/registrationSerializer.spec.ts @@ -10,6 +10,8 @@ import { BaiduTemplateRegistrationDescription, BrowserRegistrationDescription, BrowserTemplateRegistrationDescription, + FcmV1RegistrationDescription, + FcmV1TemplateRegistrationDescription, GcmRegistrationDescription, GcmTemplateRegistrationDescription, MpnsRegistrationDescription, @@ -28,6 +30,8 @@ import { createBrowserTemplateRegistrationDescription, createFcmLegacyRegistrationDescription, createFcmLegacyTemplateRegistrationDescription, + createFcmV1RegistrationDescription, + createFcmV1TemplateRegistrationDescription, createXiaomiRegistrationDescription, createXiaomiTemplateRegistrationDescription, createWindowsRegistrationDescription, @@ -147,6 +151,29 @@ const BROWSER_TEMPLATE_REGISTRATION = ` `; +const FCMV1_REGISTRATION = ` + + + + myTag,myOtherTag + {Registration Id} + {FCM V1 Registration Id} + + +`; + +const FCMV1_TEMPLATE_REGISTRATION = ` + + + + myTag,myOtherTag + {Registration Id} + {FCM V1 Registration Id} + + + +`; + const GCM_REGISTRATION = ` @@ -360,6 +387,29 @@ describe("parseRegistrationEntry", () => { assert.equal(registration.bodyTemplate, "{Template for the body}"); }); + it("should parse an FCM V1 registration description", async () => { + const registration = (await registrationDescriptionParser.parseRegistrationEntry( + FCMV1_REGISTRATION, + )) as FcmV1RegistrationDescription; + + assert.equal(registration.kind, "FcmV1"); + assert.equal(registration.registrationId, "{Registration Id}"); + assert.equal(registration.fcmV1RegistrationId, "{FCM V1 Registration Id}"); + assert.deepEqual(registration.tags, ["myTag", "myOtherTag"]); + }); + + it("should parse a FCM V1 template registration description", async () => { + const registration = (await registrationDescriptionParser.parseRegistrationEntry( + FCMV1_TEMPLATE_REGISTRATION, + )) as FcmV1TemplateRegistrationDescription; + + assert.equal(registration.kind, "FcmV1Template"); + assert.equal(registration.registrationId, "{Registration Id}"); + assert.equal(registration.fcmV1RegistrationId, "{FCM V1 Registration Id}"); + assert.deepEqual(registration.tags, ["myTag", "myOtherTag"]); + assert.equal(registration.bodyTemplate, "{Template for the body}"); + }); + it("should parse a GCM registration description", async () => { const registration = (await registrationDescriptionParser.parseRegistrationEntry( GCM_REGISTRATION, @@ -710,6 +760,42 @@ describe("serializeRegistrationDescription", () => { assert.isTrue(xml.indexOf("") !== -1); }); + it("should serialize a FcmV1RegistrationDescription", () => { + const registration = createFcmV1RegistrationDescription({ + fcmV1RegistrationId: "{FCM V1 Registration ID}", + tags: ["myTag", "myOtherTag"], + }); + + const xml = registrationDescriptionSerializer.serializeRegistrationDescription(registration); + + assert.isTrue(xml.indexOf("{FCM V1 Registration ID}") !== -1, + ); + assert.isTrue(xml.indexOf("myTag,myOtherTag") !== -1); + assert.isTrue(xml.indexOf("") !== -1); + }); + + it("should serialize a FcmV1TemplateRegistrationDescription", () => { + const registration = createFcmV1TemplateRegistrationDescription({ + fcmV1RegistrationId: "{FCM V1 Registration ID}", + tags: ["myTag", "myOtherTag"], + bodyTemplate: "{Template for the body}", + }); + + const xml = registrationDescriptionSerializer.serializeRegistrationDescription(registration); + + assert.isTrue(xml.indexOf("{FCM V1 Registration ID}") !== -1, + ); + assert.isTrue(xml.indexOf("myTag,myOtherTag") !== -1); + assert.isTrue( + xml.indexOf("") !== -1, + ); + assert.isTrue(xml.indexOf("") !== -1); + }); + it("should serialize a GcmRegistrationDescription", () => { const registration = createFcmLegacyRegistrationDescription({ gcmRegistrationId: "{GCM Registration ID}", diff --git a/sdk/notificationhubs/notification-hubs/test/public/unit/installation.spec.ts b/sdk/notificationhubs/notification-hubs/test/public/unit/installation.spec.ts index 3641e30df179..0e266e453edf 100644 --- a/sdk/notificationhubs/notification-hubs/test/public/unit/installation.spec.ts +++ b/sdk/notificationhubs/notification-hubs/test/public/unit/installation.spec.ts @@ -6,6 +6,7 @@ import { createAppleInstallation, createBaiduInstallation, createBrowserInstallation, + createFcmV1Installation, createFcmLegacyInstallation, createXiaomiInstallation, createWindowsInstallation, @@ -73,6 +74,19 @@ describe("createBrowserInstallation", () => { }); }); +describe("createFcmV1Installation", () => { + it("should set the default properties", () => { + const installation = createFcmV1Installation({ + installationId: "abc123", + pushChannel: "zxy321", + }); + + assert.equal(installation.installationId, "abc123"); + assert.equal(installation.pushChannel, "zxy321"); + assert.equal(installation.platform, "fcmv1"); + }); +}); + describe("createFcmLegacyInstallation", () => { it("should set the default properties", () => { const installation = createFcmLegacyInstallation({ diff --git a/sdk/notificationhubs/notification-hubs/test/public/unit/notification.spec.ts b/sdk/notificationhubs/notification-hubs/test/public/unit/notification.spec.ts index 3db7eea82fa1..6c8a652a1ab9 100644 --- a/sdk/notificationhubs/notification-hubs/test/public/unit/notification.spec.ts +++ b/sdk/notificationhubs/notification-hubs/test/public/unit/notification.spec.ts @@ -8,6 +8,7 @@ import { createBaiduNotification, createBrowserNotification, createFcmLegacyNotification, + createFcmV1Notification, createTemplateNotification, createXiaomiNotification, createWindowsBadgeNotification, @@ -65,6 +66,18 @@ describe("createBrowserNotification", () => { }); }); +describe("createFcmV1Notification", () => { + it("should create a Firebase message with defaults", () => { + const notification = createFcmV1Notification({ + body: `{"notification":{"title":"TITLE","body":"Hello}}`, + }); + + assert.equal(notification.contentType, Constants.JSON_CONTENT_TYPE); + assert.equal(notification.platform, "fcmv1"); + assert.equal(notification.body, `{"notification":{"title":"TITLE","body":"Hello}}`); + }); +}); + describe("createFcmLegacyNotification", () => { it("should create a Firebase message with defaults", () => { const notification = createFcmLegacyNotification({