diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9485214 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +## 1.9.0 + +### New Features + +- **New Feature: App Icon Badge Count** - Show MRR, user counts, stock prices, and more on your ActivitySmith app icon. diff --git a/README.md b/README.md index bf604c2..7dc1631 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,9 @@ See [API reference](https://activitysmith.com/docs/api-reference/introduction) - [Live Activity Action](#live-activity-action) - [Icons and Badges](#icons-and-badges) - [Live Activity Colors](#live-activity-colors) -- [Channels](#channels) - [Widgets](#widgets) +- [App Icon Badge Count](#app-icon-badge-count) +- [Channels](#channels) ## Installation @@ -468,18 +469,6 @@ Choose from these colors for the Live Activity accent, including progress bars a `lime`, `green`, `cyan`, `blue`, `purple`, `magenta`, `red`, `orange`, `yellow`, `gray` -## Channels - -Channels are used to target specific team members or devices. Can be used for both push notifications and live activities. - -```ts -await activitysmith.notifications.send({ - title: "New subscription 💸", - message: "Customer upgraded to Pro plan", - channels: ["sales", "customer-success"], // Optional -}); -``` - ## Widgets

@@ -504,6 +493,62 @@ String metric values work too. await activitysmith.metrics.update("prod.status", "healthy"); ``` +## App Icon Badge Count + +

+ ActivitySmith app icon with an App Icon Badge Count +

+ +Show the number you care about on your ActivitySmith app icon. Track MRR, a customer count, a stock price, or any other value you want to keep in view. + +Set or update the badge value. + +```ts +await activitysmith.badgeCount(8333); +``` + +To clear the badge, set its value to 0. + +```ts +await activitysmith.badgeCount(0); +``` + +## Channels + +Use `channels` to target specific team members or devices + +### Push Notifications + +```ts +await activitysmith.notifications.send({ + title: "New subscription 💸", + message: "Customer upgraded to Pro plan", + channels: ["sales", "customer-success"], +}); +``` + +### Live Activities + +```ts +await activitysmith.liveActivities.start({ + content_state: { + title: "Nightly Database Backup", + subtitle: "verify restore", + type: "progress", + percentage: 62, + }, + channels: ["sales", "customer-success"], +}); +``` + +### App Icon Badge Count + +```ts +await activitysmith.badgeCount(3, { + channels: ["sales", "customer-success"], +}); +``` + ## Error Handling ```ts diff --git a/generated/apis/AppIconBadgesApi.ts b/generated/apis/AppIconBadgesApi.ts new file mode 100644 index 0000000..f78cbe1 --- /dev/null +++ b/generated/apis/AppIconBadgesApi.ts @@ -0,0 +1,81 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * ActivitySmith API + * Send push notifications and Live Activities to your own devices via a single API key. + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import * as runtime from '../runtime'; +import type { + AppIconBadgeCountUpdateRequest, + AppIconBadgeCountUpdateResponse, + BadRequestError, + ForbiddenError, + NoRecipientsError, + RateLimitError, +} from '../models/index'; + +export interface UpdateAppIconBadgeCountRequest { + appIconBadgeCountUpdateRequest: AppIconBadgeCountUpdateRequest; +} + +/** + * + */ +export class AppIconBadgesApi extends runtime.BaseAPI { + + /** + * Updates the App Icon Badge Count on devices matched by API key scope and optional target channels. Send `badge: 0` to clear the count. Badge updates are independent of push notifications and do not create a push notification history item. + * Update App Icon Badge Count + */ + async updateAppIconBadgeCountRaw(requestParameters: UpdateAppIconBadgeCountRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + if (requestParameters['appIconBadgeCountUpdateRequest'] == null) { + throw new runtime.RequiredError( + 'appIconBadgeCountUpdateRequest', + 'Required parameter "appIconBadgeCountUpdateRequest" was null or undefined when calling updateAppIconBadgeCount().' + ); + } + + 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: `/badge`, + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: requestParameters['appIconBadgeCountUpdateRequest'], + }, initOverrides); + + return new runtime.JSONApiResponse(response); + } + + /** + * Updates the App Icon Badge Count on devices matched by API key scope and optional target channels. Send `badge: 0` to clear the count. Badge updates are independent of push notifications and do not create a push notification history item. + * Update App Icon Badge Count + */ + async updateAppIconBadgeCount(requestParameters: UpdateAppIconBadgeCountRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + const response = await this.updateAppIconBadgeCountRaw(requestParameters, initOverrides); + return await response.value(); + } + +} diff --git a/generated/apis/index.ts b/generated/apis/index.ts index f269477..ecb06b5 100644 --- a/generated/apis/index.ts +++ b/generated/apis/index.ts @@ -1,5 +1,6 @@ /* tslint:disable */ /* eslint-disable */ +export * from './AppIconBadgesApi'; export * from './LiveActivitiesApi'; export * from './MetricsApi'; export * from './PushNotificationsApi'; diff --git a/generated/models/index.ts b/generated/models/index.ts index 69b9e93..3d12627 100644 --- a/generated/models/index.ts +++ b/generated/models/index.ts @@ -6,7 +6,6 @@ * @interface ActivityMetric */ export interface ActivityMetric { - [key: string]: any | any; /** * * @type {string} @@ -63,7 +62,6 @@ export type ActivityMetricValue = number | string; * @interface AlertPayload */ export interface AlertPayload { - [key: string]: any | any; /** * * @type {string} @@ -77,13 +75,74 @@ export interface AlertPayload { */ body?: string; } +/** + * App Icon Badge Count update. Send badge 0 to clear the count. + * @export + * @interface AppIconBadgeCountUpdateRequest + */ +export interface AppIconBadgeCountUpdateRequest { + /** + * The count to show on the ActivitySmith app icon. Send 0 to clear it. + * @type {number} + * @memberof AppIconBadgeCountUpdateRequest + */ + badge: number; + /** + * + * @type {ChannelTarget} + * @memberof AppIconBadgeCountUpdateRequest + */ + target?: ChannelTarget; +} +/** + * + * @export + * @interface AppIconBadgeCountUpdateResponse + */ +export interface AppIconBadgeCountUpdateResponse { + /** + * + * @type {boolean} + * @memberof AppIconBadgeCountUpdateResponse + */ + success: boolean; + /** + * + * @type {number} + * @memberof AppIconBadgeCountUpdateResponse + */ + badge: number; + /** + * + * @type {number} + * @memberof AppIconBadgeCountUpdateResponse + */ + devices_notified: number; + /** + * + * @type {number} + * @memberof AppIconBadgeCountUpdateResponse + */ + users_notified: number; + /** + * + * @type {Array} + * @memberof AppIconBadgeCountUpdateResponse + */ + effective_channel_slugs: Array; + /** + * + * @type {string} + * @memberof AppIconBadgeCountUpdateResponse + */ + timestamp: string; +} /** * * @export * @interface BadRequestError */ export interface BadRequestError { - [key: string]: any | any; /** * * @type {string} @@ -103,7 +162,6 @@ export interface BadRequestError { * @interface ChannelTarget */ export interface ChannelTarget { - [key: string]: any | any; /** * Channel slugs. When omitted, API key scope determines recipients. * @type {Array} @@ -117,7 +175,6 @@ export interface ChannelTarget { * @interface ContentStateEnd */ export interface ContentStateEnd { - [key: string]: any | any; /** * * @type {string} @@ -305,7 +362,6 @@ export type ContentStateEndStepColorsEnum = typeof ContentStateEndStepColorsEnum * @interface ContentStateStart */ export interface ContentStateStart { - [key: string]: any | any; /** * * @type {string} @@ -487,7 +543,6 @@ export type ContentStateStartStepColorsEnum = typeof ContentStateStartStepColors * @interface ContentStateUpdate */ export interface ContentStateUpdate { - [key: string]: any | any; /** * * @type {string} @@ -669,7 +724,6 @@ export type ContentStateUpdateStepColorsEnum = typeof ContentStateUpdateStepColo * @interface ForbiddenError */ export interface ForbiddenError { - [key: string]: any | any; /** * * @type {string} @@ -716,10 +770,10 @@ export interface LiveActivityAction { method?: LiveActivityWebhookMethod; /** * Optional webhook payload body. Used only when type=webhook. - * @type {{ [key: string]: any; }} + * @type {object} * @memberof LiveActivityAction */ - body?: { [key: string]: any; }; + body?: object; } /** @@ -738,7 +792,6 @@ export type LiveActivityActionType = typeof LiveActivityActionType[keyof typeof * @interface LiveActivityAlertBadge */ export interface LiveActivityAlertBadge { - [key: string]: any | any; /** * * @type {string} @@ -758,7 +811,6 @@ export interface LiveActivityAlertBadge { * @interface LiveActivityAlertIcon */ export interface LiveActivityAlertIcon { - [key: string]: any | any; /** * Apple SF Symbol name. * @type {string} @@ -797,7 +849,6 @@ export type LiveActivityColor = typeof LiveActivityColor[keyof typeof LiveActivi * @interface LiveActivityEndRequest */ export interface LiveActivityEndRequest { - [key: string]: any | any; /** * * @type {string} @@ -829,7 +880,6 @@ export interface LiveActivityEndRequest { * @interface LiveActivityEndResponse */ export interface LiveActivityEndResponse { - [key: string]: any | any; /** * * @type {boolean} @@ -867,7 +917,6 @@ export interface LiveActivityEndResponse { * @interface LiveActivityLimitError */ export interface LiveActivityLimitError { - [key: string]: any | any; /** * * @type {string} @@ -899,7 +948,6 @@ export interface LiveActivityLimitError { * @interface LiveActivityStartRequest */ export interface LiveActivityStartRequest { - [key: string]: any | any; /** * * @type {ContentStateStart} @@ -937,7 +985,6 @@ export interface LiveActivityStartRequest { * @interface LiveActivityStartResponse */ export interface LiveActivityStartResponse { - [key: string]: any | any; /** * * @type {boolean} @@ -981,7 +1028,6 @@ export interface LiveActivityStartResponse { * @interface LiveActivityStreamDeleteRequest */ export interface LiveActivityStreamDeleteRequest { - [key: string]: any | any; /** * * @type {StreamContentState} @@ -1013,7 +1059,6 @@ export interface LiveActivityStreamDeleteRequest { * @interface LiveActivityStreamDeleteResponse */ export interface LiveActivityStreamDeleteResponse { - [key: string]: any | any; /** * * @type {boolean} @@ -1073,7 +1118,6 @@ export type LiveActivityStreamDeleteResponseOperationEnum = typeof LiveActivityS * @interface LiveActivityStreamPutResponse */ export interface LiveActivityStreamPutResponse { - [key: string]: any | any; /** * * @type {boolean} @@ -1155,7 +1199,6 @@ export type LiveActivityStreamPutResponseOperationEnum = typeof LiveActivityStre * @interface LiveActivityStreamRequest */ export interface LiveActivityStreamRequest { - [key: string]: any | any; /** * * @type {StreamContentState} @@ -1199,7 +1242,6 @@ export interface LiveActivityStreamRequest { * @interface LiveActivityUpdateRequest */ export interface LiveActivityUpdateRequest { - [key: string]: any | any; /** * * @type {string} @@ -1231,7 +1273,6 @@ export interface LiveActivityUpdateRequest { * @interface LiveActivityUpdateResponse */ export interface LiveActivityUpdateResponse { - [key: string]: any | any; /** * * @type {boolean} @@ -1280,7 +1321,6 @@ export type LiveActivityWebhookMethod = typeof LiveActivityWebhookMethod[keyof t * @interface MetricError */ export interface MetricError { - [key: string]: any | any; /** * * @type {string} @@ -1300,7 +1340,6 @@ export interface MetricError { * @interface MetricValueUpdateRequest */ export interface MetricValueUpdateRequest { - [key: string]: any | any; /** * * @type {MetricValueUpdateRequestValue} @@ -1326,7 +1365,6 @@ export type MetricValueUpdateRequestValue = number | string; * @interface MetricValueUpdateResponse */ export interface MetricValueUpdateResponse { - [key: string]: any | any; /** * * @type {boolean} @@ -1340,7 +1378,6 @@ export interface MetricValueUpdateResponse { * @interface NoRecipientsError */ export interface NoRecipientsError { - [key: string]: any | any; /** * * @type {string} @@ -1366,7 +1403,6 @@ export interface NoRecipientsError { * @interface NotFoundError */ export interface NotFoundError { - [key: string]: any | any; /** * * @type {string} @@ -1413,10 +1449,10 @@ export interface PushNotificationAction { method?: PushNotificationWebhookMethod; /** * Optional webhook payload body. Used only when type=webhook. - * @type {{ [key: string]: any; }} + * @type {object} * @memberof PushNotificationAction */ - body?: { [key: string]: any; }; + body?: object; } /** @@ -1474,10 +1510,10 @@ export interface PushNotificationRequest { actions?: Array; /** * - * @type {{ [key: string]: any; }} + * @type {object} * @memberof PushNotificationRequest */ - payload?: { [key: string]: any; }; + payload?: object; /** * * @type {number} @@ -1503,7 +1539,6 @@ export interface PushNotificationRequest { * @interface PushNotificationResponse */ export interface PushNotificationResponse { - [key: string]: any | any; /** * * @type {boolean} @@ -1552,7 +1587,6 @@ export type PushNotificationWebhookMethod = typeof PushNotificationWebhookMethod * @interface RateLimitError */ export interface RateLimitError { - [key: string]: any | any; /** * * @type {string} @@ -1578,7 +1612,6 @@ export type SendPushNotification429Response = LiveActivityLimitError | RateLimit * @interface StreamContentState */ export interface StreamContentState { - [key: string]: any | any; /** * * @type {string} diff --git a/package-lock.json b/package-lock.json index 518baed..713333d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "activitysmith", - "version": "1.8.0", + "version": "1.9.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "activitysmith", - "version": "1.8.0", + "version": "1.9.0", "license": "MIT", "devDependencies": { "typescript": "^5.3.3", diff --git a/package.json b/package.json index ea282fc..9a1241e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "activitysmith", - "version": "1.8.0", + "version": "1.9.0", "description": "Official ActivitySmith Node.js SDK", "keywords": [ "activitysmith", diff --git a/src/ActivitySmith.ts b/src/ActivitySmith.ts index f2aaffe..1222797 100644 --- a/src/ActivitySmith.ts +++ b/src/ActivitySmith.ts @@ -1,6 +1,12 @@ -import { Configuration, PushNotificationsApi, LiveActivitiesApi, MetricsApi } from "../generated/index"; - -const SDK_VERSION = "1.8.0"; +import { + AppIconBadgesApi, + Configuration, + LiveActivitiesApi, + MetricsApi, + PushNotificationsApi, +} from "../generated/index"; + +const SDK_VERSION = "1.9.0"; const SDK_HEADER_NAME = "X-ActivitySmith-SDK"; const SDK_HEADER_VALUE = `node-v${SDK_VERSION}`; @@ -26,9 +32,16 @@ type MetricUpdateRequestBody = MetricUpdateParameters["metricValueUpdateRequest" type MetricValue = MetricUpdateRequestBody["value"]; type MetricUpdateOptions = Omit; type MetricInitOverrides = Parameters[1]; +type AppIconBadgeRequestBody = + Parameters[0]["appIconBadgeCountUpdateRequest"]; +type AppIconBadgeInitOverrides = Parameters[1]; type ChannelTargetInput = { channels?: string[] }; type PushSendRequest = PushRequestBody & { channels?: string[] }; +export type BadgeCountOptions = { + channels?: string[]; +}; + const LiveActivityTypes = { segmentedProgress: "segmented_progress", progress: "progress", @@ -355,6 +368,7 @@ export class ActivitySmith { public readonly notifications: NotificationsResource; public readonly liveActivities: LiveActivitiesResource; public readonly metrics: MetricsResource; + private readonly appIconBadgesApi: AppIconBadgesApi; constructor(opts: ActivitySmithOptions) { if (!opts?.apiKey) { @@ -372,5 +386,22 @@ export class ActivitySmith { this.notifications = new NotificationsResource(new PushNotificationsApi(config)); this.liveActivities = new LiveActivitiesResource(new LiveActivitiesApi(config)); this.metrics = new MetricsResource(new MetricsApi(config)); + this.appIconBadgesApi = new AppIconBadgesApi(config); + } + + badgeCount( + badge: number, + options: BadgeCountOptions = {}, + initOverrides?: AppIconBadgeInitOverrides, + ) { + return this.appIconBadgesApi.updateAppIconBadgeCount( + { + appIconBadgeCountUpdateRequest: withTargetChannels({ + badge, + ...options, + }) as AppIconBadgeRequestBody, + }, + initOverrides, + ); } } diff --git a/tests/resources.test.js b/tests/resources.test.js index 4d88e81..87f1499 100644 --- a/tests/resources.test.js +++ b/tests/resources.test.js @@ -158,6 +158,46 @@ describe("resource wrappers", () => { ).toThrow("ActivitySmith: media cannot be combined with actions"); }); + it("passes zero through to clear the app icon badge count", async () => { + const ActivitySmith = require("../dist/src/index.js"); + const generated = require("../dist/generated/index.js"); + + const updateSpy = vi + .spyOn(generated.AppIconBadgesApi.prototype, "updateAppIconBadgeCount") + .mockResolvedValue({ success: true, badge: 0 }); + + const client = new ActivitySmith({ apiKey: "test" }); + const result = await client.badgeCount(0); + + expect(result).toEqual({ success: true, badge: 0 }); + expect(updateSpy).toHaveBeenCalledWith( + { appIconBadgeCountUpdateRequest: { badge: 0 } }, + undefined, + ); + }); + + it("maps channels to target.channels for badgeCount", async () => { + const ActivitySmith = require("../dist/src/index.js"); + const generated = require("../dist/generated/index.js"); + + const updateSpy = vi + .spyOn(generated.AppIconBadgesApi.prototype, "updateAppIconBadgeCount") + .mockResolvedValue({ success: true, badge: 3 }); + + const client = new ActivitySmith({ apiKey: "test" }); + await client.badgeCount(3, { channels: ["sales", "customer-success"] }); + + expect(updateSpy).toHaveBeenCalledWith( + { + appIconBadgeCountUpdateRequest: { + badge: 3, + target: { channels: ["sales", "customer-success"] }, + }, + }, + undefined, + ); + }); + it("wraps live activity payloads for short methods", async () => { const ActivitySmith = require("../dist/src/index.js"); const generated = require("../dist/generated/index.js");