From d0f4db6e80fb16db6d021050105b92a5e64edd26 Mon Sep 17 00:00:00 2001 From: activitysmith-bot Date: Mon, 12 Oct 2026 03:56:18 +0000 Subject: [PATCH 1/4] chore: regenerate SDK --- generated/apis/PushNotificationsApi.ts | 65 ++++- generated/models/index.ts | 333 +++++++++++++++++++++++++ generated/openapi-source.json | 4 +- 3 files changed, 398 insertions(+), 4 deletions(-) diff --git a/generated/apis/PushNotificationsApi.ts b/generated/apis/PushNotificationsApi.ts index 0f06c10..46666ee 100644 --- a/generated/apis/PushNotificationsApi.ts +++ b/generated/apis/PushNotificationsApi.ts @@ -19,11 +19,19 @@ import type { BillingBlockedError, ForbiddenError, NoRecipientsError, + NotFoundError, + PushNotificationReplaceRequest, + PushNotificationReplaceResponse, PushNotificationRequest, PushNotificationResponse, SendPushNotification429Response, } from '../models/index'; +export interface ReplacePushNotificationRequest { + notificationId: string; + pushNotificationReplaceRequest: PushNotificationReplaceRequest; +} + export interface SendPushNotificationRequest { pushNotificationRequest: PushNotificationRequest; } @@ -34,7 +42,60 @@ export interface SendPushNotificationRequest { export class PushNotificationsApi extends runtime.BaseAPI { /** - * Sends a push notification to devices matched by API key scope and optional target channels. Supports optional redirection URL, optional media preview or playback when the notification is expanded, and up to 4 interactive actions. `media` cannot be combined with `actions`. Optional `icon` shows a custom image, such as a service logo or avatar, next to a smaller app icon. Optional `interruption_level` sets how urgently iOS presents the notification. Optional tags to organize and filter notification history. + * Replaces a Push Notification you already sent, using the `notification_id` from the send response. The replacement takes the original\'s place on the Lock Screen and in Notification Center instead of appearing as a second notification. It is delivered silently, with no sound or screen wake, to the same recipients as the original. Notification history is updated in place. + * Replace a push notification + */ + async replacePushNotificationRaw(requestParameters: ReplacePushNotificationRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + if (requestParameters['notificationId'] == null) { + throw new runtime.RequiredError( + 'notificationId', + 'Required parameter "notificationId" was null or undefined when calling replacePushNotification().' + ); + } + + if (requestParameters['pushNotificationReplaceRequest'] == null) { + throw new runtime.RequiredError( + 'pushNotificationReplaceRequest', + 'Required parameter "pushNotificationReplaceRequest" was null or undefined when calling replacePushNotification().' + ); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters['Content-Type'] = 'application/json'; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = await token("apiKeyAuth", []); + + if (tokenString) { + headerParameters["Authorization"] = `Bearer ${tokenString}`; + } + } + const response = await this.request({ + path: `/push-notification/{notification_id}`.replace(`{${"notification_id"}}`, encodeURIComponent(String(requestParameters['notificationId']))), + method: 'PUT', + headers: headerParameters, + query: queryParameters, + body: requestParameters['pushNotificationReplaceRequest'], + }, initOverrides); + + return new runtime.JSONApiResponse(response); + } + + /** + * Replaces a Push Notification you already sent, using the `notification_id` from the send response. The replacement takes the original\'s place on the Lock Screen and in Notification Center instead of appearing as a second notification. It is delivered silently, with no sound or screen wake, to the same recipients as the original. Notification history is updated in place. + * Replace a push notification + */ + async replacePushNotification(requestParameters: ReplacePushNotificationRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + const response = await this.replacePushNotificationRaw(requestParameters, initOverrides); + return await response.value(); + } + + /** + * Sends a push notification to devices matched by API key scope and optional target channels. Supports optional redirection URL, optional media preview or playback when the notification is expanded, and up to 4 interactive actions. `media` cannot be combined with `actions`. Optional `icon` shows a custom image, such as a logo or avatar, next to a smaller app icon. Optional `interruption_level` sets how urgently iOS presents the notification. Optional tags to organize and filter notification history. * Send a push notification */ async sendPushNotificationRaw(requestParameters: SendPushNotificationRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { @@ -71,7 +132,7 @@ export class PushNotificationsApi extends runtime.BaseAPI { } /** - * Sends a push notification to devices matched by API key scope and optional target channels. Supports optional redirection URL, optional media preview or playback when the notification is expanded, and up to 4 interactive actions. `media` cannot be combined with `actions`. Optional `icon` shows a custom image, such as a service logo or avatar, next to a smaller app icon. Optional `interruption_level` sets how urgently iOS presents the notification. Optional tags to organize and filter notification history. + * Sends a push notification to devices matched by API key scope and optional target channels. Supports optional redirection URL, optional media preview or playback when the notification is expanded, and up to 4 interactive actions. `media` cannot be combined with `actions`. Optional `icon` shows a custom image, such as a logo or avatar, next to a smaller app icon. Optional `interruption_level` sets how urgently iOS presents the notification. Optional tags to organize and filter notification history. * Send a push notification */ async sendPushNotification(requestParameters: SendPushNotificationRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { diff --git a/generated/models/index.ts b/generated/models/index.ts index 3af0838..4f748a1 100644 --- a/generated/models/index.ts +++ b/generated/models/index.ts @@ -223,6 +223,24 @@ export interface AppIconBadgeCountUpdateResponse { * @memberof AppIconBadgeCountUpdateResponse */ timestamp: string; + /** + * Number of device tokens targeted. + * @type {number} + * @memberof AppIconBadgeCountUpdateResponse + */ + devices_targeted?: number; + /** + * Number of device tokens deactivated because APNs reported them unregistered. + * @type {number} + * @memberof AppIconBadgeCountUpdateResponse + */ + devices_deactivated?: number; + /** + * Number of targeted device tokens APNs reported as unregistered. + * @type {number} + * @memberof AppIconBadgeCountUpdateResponse + */ + devices_unregistered?: number; } /** * @@ -1074,6 +1092,42 @@ export interface LiveActivityEndResponse { * @memberof LiveActivityEndResponse */ timestamp: string; + /** + * True when the Live Activity had already ended and the request had no effect. + * @type {boolean} + * @memberof LiveActivityEndResponse + */ + ignored?: boolean; + /** + * + * @type {string} + * @memberof LiveActivityEndResponse + */ + message?: string; + /** + * + * @type {string} + * @memberof LiveActivityEndResponse + */ + end_reason?: string | null; + /** + * True when the Live Activity was marked complete without a confirmed APNs end delivery. + * @type {boolean} + * @memberof LiveActivityEndResponse + */ + forced_completion?: boolean; + /** + * + * @type {string} + * @memberof LiveActivityEndResponse + */ + warning?: string; + /** + * Per-device delivery errors when some devices could not be reached. + * @type {Array} + * @memberof LiveActivityEndResponse + */ + errors?: Array; } /** * @@ -1215,6 +1269,24 @@ export interface LiveActivityStartResponse { * @memberof LiveActivityStartResponse */ timestamp: string; + /** + * Present when starting this Live Activity ended an older one to stay within the device limit. + * @type {string} + * @memberof LiveActivityStartResponse + */ + ended_activity_id?: string; + /** + * + * @type {string} + * @memberof LiveActivityStartResponse + */ + message?: string; + /** + * Per-device delivery errors when some devices could not be reached. + * @type {Array} + * @memberof LiveActivityStartResponse + */ + errors?: Array; } /** * Optional payload for ending a managed stream. When omitted, ActivitySmith ends the stream using the latest known state when possible. @@ -1307,6 +1379,36 @@ export interface LiveActivityStreamDeleteResponse { * @memberof LiveActivityStreamDeleteResponse */ timestamp: string; + /** + * True when the stream was already ended and the request had no effect. + * @type {boolean} + * @memberof LiveActivityStreamDeleteResponse + */ + ignored?: boolean; + /** + * + * @type {string} + * @memberof LiveActivityStreamDeleteResponse + */ + message?: string; + /** + * + * @type {boolean} + * @memberof LiveActivityStreamDeleteResponse + */ + forced_completion?: boolean; + /** + * + * @type {string} + * @memberof LiveActivityStreamDeleteResponse + */ + warning?: string; + /** + * Per-device delivery errors when some devices could not be reached. + * @type {Array} + * @memberof LiveActivityStreamDeleteResponse + */ + errors?: Array; } @@ -1384,12 +1486,42 @@ export interface LiveActivityStreamPutResponse { * @memberof LiveActivityStreamPutResponse */ tags?: Array; + /** + * Present on `updated` responses when the current Live Activity has received more than 12 updates and has averaged more than one update every 2 minutes since it started. The update is still sent, but iOS may throttle Live Activities that update this often. + * @type {string} + * @memberof LiveActivityStreamPutResponse + */ + warning?: string; /** * * @type {string} * @memberof LiveActivityStreamPutResponse */ timestamp: string; + /** + * True when the request had no effect. + * @type {boolean} + * @memberof LiveActivityStreamPutResponse + */ + ignored?: boolean; + /** + * + * @type {boolean} + * @memberof LiveActivityStreamPutResponse + */ + remote_push_sent?: boolean; + /** + * + * @type {string} + * @memberof LiveActivityStreamPutResponse + */ + message?: string; + /** + * Per-device delivery errors when some devices could not be reached. + * @type {Array} + * @memberof LiveActivityStreamPutResponse + */ + errors?: Array; } @@ -1539,6 +1671,48 @@ export interface LiveActivityUpdateResponse { * @memberof LiveActivityUpdateResponse */ timestamp: string; + /** + * + * @type {number} + * @memberof LiveActivityUpdateResponse + */ + users_notified?: number; + /** + * + * @type {Array} + * @memberof LiveActivityUpdateResponse + */ + effective_channel_slugs?: Array; + /** + * Optional tags to organize and filter notification history. + * @type {Array} + * @memberof LiveActivityUpdateResponse + */ + tags?: Array; + /** + * Present when the update started a new Live Activity for a managed stream. + * @type {string} + * @memberof LiveActivityUpdateResponse + */ + operation?: string; + /** + * + * @type {string} + * @memberof LiveActivityUpdateResponse + */ + stream_key?: string; + /** + * + * @type {string} + * @memberof LiveActivityUpdateResponse + */ + previous_activity_id?: string; + /** + * Per-device delivery errors when some devices could not be reached. + * @type {Array} + * @memberof LiveActivityUpdateResponse + */ + errors?: Array; } /** * @type LiveActivityValue @@ -1725,6 +1899,129 @@ export const PushNotificationActionType = { } as const; export type PushNotificationActionType = typeof PushNotificationActionType[keyof typeof PushNotificationActionType]; +/** + * Replacement content. title, subtitle, message, redirection, media, icon, actions, and payload fully replace the original values. Omit metadata or tags to keep the stored values; send {} or [] to clear them. + * @export + * @interface PushNotificationReplaceRequest + */ +export interface PushNotificationReplaceRequest { + [key: string]: any | any; + /** + * Additional information shown in notification and Live Activity details in ActivitySmith. Not displayed in the Push Notification or Live Activity on the device. Values must be strings, finite numbers, or booleans. At most 50 entries and 16 KB of serialized UTF-8 JSON. Omit on updates to preserve existing Metadata; send {} to clear it. + * @type {{ [key: string]: MetadataValue; }} + * @memberof PushNotificationReplaceRequest + */ + metadata?: { [key: string]: MetadataValue; }; + /** + * + * @type {string} + * @memberof PushNotificationReplaceRequest + */ + title: string; + /** + * + * @type {string} + * @memberof PushNotificationReplaceRequest + */ + message?: string; + /** + * + * @type {string} + * @memberof PushNotificationReplaceRequest + */ + subtitle?: string; + /** + * Optional HTTPS image URL without credentials for a custom notification icon. If the image cannot be loaded, the app icon is used. iOS may omit subtitle when displaying a custom icon. + * @type {string} + * @memberof PushNotificationReplaceRequest + */ + icon?: string; + /** + * Optional HTTPS URL for an image, audio file, or video that users can preview or play when they expand the notification. If `redirection` is omitted, tapping the notification opens this URL. Cannot be combined with `actions`. + * @type {string} + * @memberof PushNotificationReplaceRequest + */ + media?: string; + /** + * Optional HTTP, HTTPS, Shortcuts, or installed app URL opened when the user taps the notification body. Custom schemes such as spotify:// and spotify:track:123 require iOS 1.13.4 build 2 or later and an installed handler; no web fallback is provided. Internal and executable schemes are blocked. Overrides the default tap target from media. + * @type {string} + * @memberof PushNotificationReplaceRequest + */ + redirection?: string; + /** + * Optional interactive actions shown when users expand the notification. Cannot be combined with `media`. + * @type {Array} + * @memberof PushNotificationReplaceRequest + */ + actions?: Array; + /** + * + * @type {object} + * @memberof PushNotificationReplaceRequest + */ + payload?: object; + /** + * + * @type {number} + * @memberof PushNotificationReplaceRequest + */ + badge?: number; + /** + * Optional tags to organize and filter notification history. + * @type {Array} + * @memberof PushNotificationReplaceRequest + */ + tags?: Array; +} +/** + * Apple APNs acceptance result for the replacement. Acceptance does not confirm device delivery or presentation. + * @export + * @interface PushNotificationReplaceResponse + */ +export interface PushNotificationReplaceResponse { + /** + * True when APNs accepts the replacement for at least one device. + * @type {boolean} + * @memberof PushNotificationReplaceResponse + */ + success: boolean; + /** + * ID of the replaced Push Notification. It stays the same, so you can replace it again. + * @type {string} + * @memberof PushNotificationReplaceResponse + */ + notification_id: string; + /** + * Number of device tokens for which APNs accepted the notification, without confirming on-device delivery. + * @type {number} + * @memberof PushNotificationReplaceResponse + */ + devices_notified?: number; + /** + * + * @type {number} + * @memberof PushNotificationReplaceResponse + */ + users_notified?: number; + /** + * + * @type {Array} + * @memberof PushNotificationReplaceResponse + */ + effective_channel_slugs?: Array; + /** + * Optional tags to organize and filter notification history. + * @type {Array} + * @memberof PushNotificationReplaceResponse + */ + tags?: Array; + /** + * + * @type {string} + * @memberof PushNotificationReplaceResponse + */ + timestamp: string; +} /** * * @export @@ -1829,6 +2126,12 @@ export interface PushNotificationResponse { * @memberof PushNotificationResponse */ success: boolean; + /** + * ID of this Push Notification. Pass it to `PUT /push-notification/{notification_id}` to replace the notification later. + * @type {string} + * @memberof PushNotificationResponse + */ + notification_id?: string; /** * Number of device tokens for which APNs accepted the notification, without confirming on-device delivery. * @type {number} @@ -1859,6 +2162,36 @@ export interface PushNotificationResponse { * @memberof PushNotificationResponse */ timestamp: string; + /** + * Number of device tokens targeted. + * @type {number} + * @memberof PushNotificationResponse + */ + devices_targeted?: number; + /** + * Number of device tokens deactivated because APNs reported them unregistered. + * @type {number} + * @memberof PushNotificationResponse + */ + devices_deactivated?: number; + /** + * Number of targeted device tokens APNs reported as unregistered. + * @type {number} + * @memberof PushNotificationResponse + */ + devices_unregistered?: number; + /** + * True when the request was an integration connection test and no paired devices were available. + * @type {boolean} + * @memberof PushNotificationResponse + */ + credential_test?: boolean; + /** + * + * @type {string} + * @memberof PushNotificationResponse + */ + message?: string; } /** diff --git a/generated/openapi-source.json b/generated/openapi-source.json index a320213..7ff5c4a 100644 --- a/generated/openapi-source.json +++ b/generated/openapi-source.json @@ -1,6 +1,6 @@ { "repository": "ActivitySmithHQ/activitysmith-backend", - "commit": "ce15d69eeb25776a3ae395e77d8b907ff7ba0709", + "commit": "28d53b08b0f8ae50d6b9d29fe878ae8065faa64e", "path": "openapi.json", - "sha256": "b113d50e06d6581707d9472dda6e371b996441f58dceba87a88f79cb96508a7d" + "sha256": "1f7f54dd50f0459fc18a4efc0dd0bbecd42788fca936abb32250f53a8238c0ef" } From e2916e2a62ba55d949bbbc1121e61850fc098cd6 Mon Sep 17 00:00:00 2001 From: bardonadam Date: Mon, 12 Oct 2026 11:00:47 +0700 Subject: [PATCH 2/4] feat: add notifications.replace and release 1.14.0 Replace a sent Push Notification by its notification_id. Also picks up the corrected response contract, so the SDK reads every field the API returns. Co-Authored-By: Claude Opus 5.5 --- package-lock.json | 4 ++-- package.json | 2 +- src/ActivitySmith.ts | 24 +++++++++++++++++++++++- tests/resources.test.js | 33 +++++++++++++++++++++++++++++++++ tests/smoke.test.js | 32 ++++++++++++++++++++++++++++++++ 5 files changed, 91 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 36923f6..fcc7db8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "activitysmith", - "version": "1.13.0", + "version": "1.14.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "activitysmith", - "version": "1.13.0", + "version": "1.14.0", "license": "MIT", "devDependencies": { "typescript": "^5.3.3", diff --git a/package.json b/package.json index b2b85aa..5c13ad2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "activitysmith", - "version": "1.13.0", + "version": "1.14.0", "description": "Official ActivitySmith Node.js SDK", "keywords": [ "activitysmith", diff --git a/src/ActivitySmith.ts b/src/ActivitySmith.ts index bafb0d9..c7261b7 100644 --- a/src/ActivitySmith.ts +++ b/src/ActivitySmith.ts @@ -7,7 +7,7 @@ import { PushNotificationsApi, } from "../generated/index"; -const SDK_VERSION = "1.13.0"; +const SDK_VERSION = "1.14.0"; const SDK_HEADER_NAME = "X-ActivitySmith-SDK"; const DEFAULT_SDK_NAME = "node"; const SDK_HEADER_VALUE = `${DEFAULT_SDK_NAME}-v${SDK_VERSION}`; @@ -24,6 +24,9 @@ type PushRequestBody = Parameters[ type PushSendParameters = Parameters[0]; type PushRawParameters = Parameters[0]; type SendInitOverrides = Parameters[1]; +type PushReplaceRequestBody = + Parameters[0]["pushNotificationReplaceRequest"]; +type PushReplaceParameters = Parameters[0]; type StartRequestBody = Parameters[0]["liveActivityStartRequest"]; type UpdateRequestBody = Parameters[0]["liveActivityUpdateRequest"]; type EndRequestBody = Parameters[0]["liveActivityEndRequest"]; @@ -232,6 +235,25 @@ export class NotificationsResource { ); } + /** + * Silently replaces a sent Push Notification, using the notification_id returned by send(). + * The replacement takes the original's place on the Lock Screen and in history. + */ + replace(notificationId: string, request: PushReplaceRequestBody, initOverrides?: SendInitOverrides) { + assertValidPushRequest(request); + + return this.api.replacePushNotification( + { notificationId, pushNotificationReplaceRequest: request }, + initOverrides, + ); + } + + // Generated-style alias. + replacePushNotification(requestParameters: PushReplaceParameters, initOverrides?: SendInitOverrides) { + assertValidPushRequest(requestParameters.pushNotificationReplaceRequest); + return this.api.replacePushNotification(requestParameters, initOverrides); + } + // Backward-compatible alias. sendPushNotification(requestParameters: PushSendParameters, initOverrides?: SendInitOverrides) { assertValidPushRequest(requestParameters.pushNotificationRequest); diff --git a/tests/resources.test.js b/tests/resources.test.js index a692b28..b88ecec 100644 --- a/tests/resources.test.js +++ b/tests/resources.test.js @@ -152,6 +152,39 @@ describe("resource wrappers", () => { ).toThrow("ActivitySmith: media cannot be combined with actions"); }); + it("wraps notification id and payload for notifications.replace", async () => { + const ActivitySmith = require("../dist/src/index.js"); + const generated = require("../dist/generated/index.js"); + + const replaceSpy = vi + .spyOn(generated.PushNotificationsApi.prototype, "replacePushNotification") + .mockResolvedValue({ success: true, notification_id: "pn1" }); + + const client = new ActivitySmith({ apiKey: "test" }); + const payload = { title: "Answered from desk" }; + const result = await client.notifications.replace("pn1", payload); + + expect(result).toEqual({ success: true, notification_id: "pn1" }); + expect(replaceSpy).toHaveBeenCalledWith( + { notificationId: "pn1", pushNotificationReplaceRequest: payload }, + undefined, + ); + }); + + it("rejects media with actions for notifications.replace", async () => { + const ActivitySmith = require("../dist/src/index.js"); + + const client = new ActivitySmith({ apiKey: "test" }); + + expect(() => + client.notifications.replace("pn1", { + title: "Done", + media: "https://cdn.activitysmith.com/voice_over.mp3", + actions: [{ title: "Open", type: "open_url", url: "https://example.com" }], + }), + ).toThrow("ActivitySmith: media cannot be combined with actions"); + }); + it("keeps long notification alias working", async () => { const ActivitySmith = require("../dist/src/index.js"); const generated = require("../dist/generated/index.js"); diff --git a/tests/smoke.test.js b/tests/smoke.test.js index 499803e..43db68d 100644 --- a/tests/smoke.test.js +++ b/tests/smoke.test.js @@ -71,6 +71,38 @@ describe("smoke", () => { } }); + it("replaces a sent push notification through the real transport", async () => { + const fetchSpy = vi.fn(async () => + new Response( + JSON.stringify({ + success: true, + notification_id: "Xk3pQ8mV2nR7tL9wB4yZ", + devices_notified: 1, + timestamp: "2026-10-12T00:00:00.000Z", + }), + { status: 200, headers: { "content-type": "application/json" } }, + ), + ); + globalThis.fetch = fetchSpy; + + const ActivitySmith = require("../dist/src/index.js"); + const client = new ActivitySmith({ apiKey: "test" }); + + const result = await client.notifications.replace("Xk3pQ8mV2nR7tL9wB4yZ", { + title: "Answered from desk", + metadata: { attempts: 0, resolved: false }, + }); + + expect(result.notification_id).toBe("Xk3pQ8mV2nR7tL9wB4yZ"); + const [url, init] = fetchSpy.mock.calls[0]; + expect(String(url)).toMatch(/\/push-notification\/Xk3pQ8mV2nR7tL9wB4yZ$/); + expect(init.method).toBe("PUT"); + expect(JSON.parse(init.body)).toEqual({ + title: "Answered from desk", + metadata: { attempts: 0, resolved: false }, + }); + }); + it("allows official wrappers to override the SDK tracking identity", async () => { const fetchSpy = vi.fn(async () => new Response( From db6b1e3528e31f41f87eb9f09c8696f5bc4206da Mon Sep 17 00:00:00 2001 From: bardonadam Date: Mon, 12 Oct 2026 11:23:55 +0700 Subject: [PATCH 3/4] chore: regenerate SDK from backend dd6757c --- generated/openapi-source.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/generated/openapi-source.json b/generated/openapi-source.json index 7ff5c4a..28e1d47 100644 --- a/generated/openapi-source.json +++ b/generated/openapi-source.json @@ -1,6 +1,6 @@ { "repository": "ActivitySmithHQ/activitysmith-backend", - "commit": "28d53b08b0f8ae50d6b9d29fe878ae8065faa64e", + "commit": "dd6757c5defa3d1e6524faef789b6829184fb852", "path": "openapi.json", - "sha256": "1f7f54dd50f0459fc18a4efc0dd0bbecd42788fca936abb32250f53a8238c0ef" + "sha256": "24dae2d064095351d5e5cf800228e64a2d23c40b1e31a9754503081ddf31c5d8" } From 6350385e2a9629ad74874f710e67bd622a6e0c73 Mon Sep 17 00:00:00 2001 From: bardonadam Date: Mon, 12 Oct 2026 11:32:08 +0700 Subject: [PATCH 4/4] docs: add the 1.14.0 changelog and replace docs to the README Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 4 ++++ README.md | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8bfe76..cb78d18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.14.0 + +Adds the ability to replace a Push Notification you already sent with a new one. + ## 1.13.0 Adds custom icons and interruption levels for Push Notifications. Set `icon` to show a service logo or avatar on the notification, and `interruption_level` to deliver it as `passive`, `active`, or `time-sensitive`. diff --git a/README.md b/README.md index 666214c..3b63d33 100644 --- a/README.md +++ b/README.md @@ -190,6 +190,24 @@ await activitysmith.notifications.send({ }); ``` +### Replace a Push Notification + +Every send returns a `notification_id`. Pass it to `replace` to update a Push Notification you already sent, for example when an incident you alerted on is resolved. The replacement takes the old notification's place on the Lock Screen and in history instead of stacking a second one. It arrives silently, without sound or waking the screen, and goes to the same recipients as the original. + +Omit `metadata` or `tags` to keep their stored values. + +```js +const sent = await activitysmith.notifications.send({ + title: "🚨 checkout-api is down", + message: "500 errors on 62% of requests.", +}); + +await activitysmith.notifications.replace(sent.notification_id, { + title: "✅ checkout-api recovered", + message: "Error rate is back to normal. Incident resolved.", +}); +``` + ## Live Activities Choose the Live Activity type that matches what you want to show: