@forge/notification 0.0.2-next.0 → 0.1.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @forge/notification
2
2
 
3
+ ## 0.1.0-next.0
4
+
5
+ ### Minor Changes
6
+
7
+ - e9d727f: Add implementation code for notification SDK
8
+
9
+ ## 0.0.2
10
+
11
+ ### Patch Changes
12
+
13
+ - d5178f5: Forge Notification SDK skeleton
14
+
3
15
  ## 0.0.2-next.0
4
16
 
5
17
  ### Patch Changes
package/README.md CHANGED
@@ -1,3 +1,71 @@
1
1
  # @forge/notification
2
2
 
3
3
  Library for Forge Notification
4
+
5
+ ## Usage example
6
+
7
+ ### Sending a notification
8
+
9
+ ```typescript
10
+ import {
11
+ NotificationApi,
12
+ NotificationPayload,
13
+ InvalidPayloadError,
14
+ RateLimitError,
15
+ InternalServerError
16
+ } from '@forge/notification';
17
+
18
+ const notificationApi = new NotificationApi();
19
+
20
+ const payload: NotificationPayload = {
21
+ context: {
22
+ subject: 'Welcome to our app!',
23
+ htmlBody: '<h1>Welcome!</h1><p>Thank you for joining us.</p>',
24
+ },
25
+ recipientsUserIds: ['user1', 'user2', 'user3']
26
+ };
27
+
28
+ try {
29
+ const response = await notificationApi.sendNotification(payload);
30
+ console.log('Trigger ID:', response.triggerId);
31
+ } catch (error) {
32
+ if (error instanceof InvalidPayloadError) {
33
+ console.error('Invalid payload:', error.message);
34
+ } else if (error instanceof RateLimitError) {
35
+ console.error('Rate limit exceeded:', error.message);
36
+ } else if (error instanceof InternalServerError) {
37
+ console.error('Server error:', error.message);
38
+ }
39
+ }
40
+ ```
41
+
42
+ ### Check the status of a notification delivery
43
+
44
+ ```typescript
45
+ import {
46
+ NotificationApi,
47
+ RecipientType,
48
+ DeliveryChannel,
49
+ InvalidPayloadError,
50
+ RateLimitError,
51
+ InternalServerError
52
+ } from '@forge/notification';
53
+
54
+ try {
55
+ const notificationApi = new NotificationApi();
56
+
57
+ const statusResponse = await notificationApi.getNotificationStatus('triggerId', 'userId', RecipientType.ACCOUNT_ID, DeliveryChannel.EMAIL);
58
+ console.log('Notification status:', statusResponse.latestStatus);
59
+ } catch (error) {
60
+ if (error instanceof InvalidPayloadError) {
61
+ console.error('Invalid payload:', error.message);
62
+ } else if (error instanceof RateLimitError) {
63
+ console.error('Rate limit exceeded:', error.message);
64
+ } else if (error instanceof InternalServerError) {
65
+ console.error('Server error:', error.message);
66
+ }
67
+ }
68
+ ```
69
+
70
+ ## Errors
71
+ The SDK throws validation and API Errors.
@@ -0,0 +1,13 @@
1
+ export declare class NotificationError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ export declare class InvalidPayloadError extends NotificationError {
5
+ }
6
+ export declare class RateLimitError extends NotificationError {
7
+ }
8
+ export declare class InternalServerError extends NotificationError {
9
+ constructor(message: string, errorCode?: number, details?: string);
10
+ errorCode?: number;
11
+ details?: string;
12
+ }
13
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,OAAO,EAAE,MAAM;CAG5B;AAED,qBAAa,mBAAoB,SAAQ,iBAAiB;CAAG;AAE7D,qBAAa,cAAe,SAAQ,iBAAiB;CAAG;AAExD,qBAAa,mBAAoB,SAAQ,iBAAiB;gBAC5C,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM;IAMjE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB"}
package/out/errors.js ADDED
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.InternalServerError = exports.RateLimitError = exports.InvalidPayloadError = exports.NotificationError = void 0;
4
+ class NotificationError extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ }
8
+ }
9
+ exports.NotificationError = NotificationError;
10
+ class InvalidPayloadError extends NotificationError {
11
+ }
12
+ exports.InvalidPayloadError = InvalidPayloadError;
13
+ class RateLimitError extends NotificationError {
14
+ }
15
+ exports.RateLimitError = RateLimitError;
16
+ class InternalServerError extends NotificationError {
17
+ constructor(message, errorCode, details) {
18
+ super(message);
19
+ this.errorCode = errorCode;
20
+ this.details = details;
21
+ }
22
+ errorCode;
23
+ details;
24
+ }
25
+ exports.InternalServerError = InternalServerError;
package/out/fetch.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { APIResponse, FetchMethod } from '@forge/api';
2
+ export declare const POST_OFFICE_FORGE_NOTIFICATION_PATH = "/post-office/api/v1/forge/notification/{contextAri}/{environmentId}/{appId}/{cloudId}";
3
+ export declare const POST_OFFICE_FORGE_STATUS_PATH = "/post-office/api/v1/forge/notification/status/{contextAri}/{environmentId}/{appId}/{cloudId}";
4
+ export declare const post: (endpoint: string, body: unknown, apiClient: FetchMethod) => Promise<APIResponse>;
5
+ export declare const get: (endpoint: string, apiClient: FetchMethod) => Promise<APIResponse>;
6
+ //# sourceMappingURL=fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../src/fetch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEtD,eAAO,MAAM,mCAAmC,0FACyC,CAAC;AAC1F,eAAO,MAAM,6BAA6B,iGACsD,CAAC;AAEjG,eAAO,MAAM,IAAI,aAAoB,MAAM,QAAQ,OAAO,aAAa,WAAW,KAAG,QAAQ,WAAW,CAUvG,CAAC;AAEF,eAAO,MAAM,GAAG,aAAoB,MAAM,aAAa,WAAW,KAAG,QAAQ,WAAW,CASvF,CAAC"}
package/out/fetch.js ADDED
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.get = exports.post = exports.POST_OFFICE_FORGE_STATUS_PATH = exports.POST_OFFICE_FORGE_NOTIFICATION_PATH = void 0;
4
+ exports.POST_OFFICE_FORGE_NOTIFICATION_PATH = '/post-office/api/v1/forge/notification/{contextAri}/{environmentId}/{appId}/{cloudId}';
5
+ exports.POST_OFFICE_FORGE_STATUS_PATH = '/post-office/api/v1/forge/notification/status/{contextAri}/{environmentId}/{appId}/{cloudId}';
6
+ const post = async (endpoint, body, apiClient) => {
7
+ const request = {
8
+ method: 'POST',
9
+ body: JSON.stringify(body),
10
+ headers: {
11
+ 'content-type': 'application/json'
12
+ }
13
+ };
14
+ return await apiClient(endpoint, request);
15
+ };
16
+ exports.post = post;
17
+ const get = async (endpoint, apiClient) => {
18
+ const request = {
19
+ method: 'GET',
20
+ headers: {
21
+ 'content-type': 'application/json'
22
+ }
23
+ };
24
+ return await apiClient(endpoint, request);
25
+ };
26
+ exports.get = get;
package/out/index.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ export { NotificationApi } from './notification-api';
2
+ export { NotificationApi as default } from './notification-api';
3
+ export { NotificationPayload, DeliveryChannel, MessageTemplateData, MessageTemplateContext, NotificationPORequest, RecipientsToProcess, NotificationResponse, NotificationStatusResponse, EmailRecipient } from './types';
4
+ export { NotificationError, InvalidPayloadError, RateLimitError, InternalServerError } from './errors';
5
+ export { validateAPIResponse } from './validators';
6
+ export { post, POST_OFFICE_FORGE_NOTIFICATION_PATH } from './fetch';
7
+ export type { APIResponse, FetchMethod } from '@forge/api';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAGrD,OAAO,EAAE,eAAe,IAAI,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAGhE,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACpB,0BAA0B,EAC1B,cAAc,EACf,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAGvG,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAGnD,OAAO,EAAE,IAAI,EAAE,mCAAmC,EAAE,MAAM,SAAS,CAAC;AAGpE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC"}
package/out/index.js ADDED
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.POST_OFFICE_FORGE_NOTIFICATION_PATH = exports.post = exports.validateAPIResponse = exports.InternalServerError = exports.RateLimitError = exports.InvalidPayloadError = exports.NotificationError = exports.DeliveryChannel = exports.default = exports.NotificationApi = void 0;
4
+ var notification_api_1 = require("./notification-api");
5
+ Object.defineProperty(exports, "NotificationApi", { enumerable: true, get: function () { return notification_api_1.NotificationApi; } });
6
+ var notification_api_2 = require("./notification-api");
7
+ Object.defineProperty(exports, "default", { enumerable: true, get: function () { return notification_api_2.NotificationApi; } });
8
+ var types_1 = require("./types");
9
+ Object.defineProperty(exports, "DeliveryChannel", { enumerable: true, get: function () { return types_1.DeliveryChannel; } });
10
+ var errors_1 = require("./errors");
11
+ Object.defineProperty(exports, "NotificationError", { enumerable: true, get: function () { return errors_1.NotificationError; } });
12
+ Object.defineProperty(exports, "InvalidPayloadError", { enumerable: true, get: function () { return errors_1.InvalidPayloadError; } });
13
+ Object.defineProperty(exports, "RateLimitError", { enumerable: true, get: function () { return errors_1.RateLimitError; } });
14
+ Object.defineProperty(exports, "InternalServerError", { enumerable: true, get: function () { return errors_1.InternalServerError; } });
15
+ var validators_1 = require("./validators");
16
+ Object.defineProperty(exports, "validateAPIResponse", { enumerable: true, get: function () { return validators_1.validateAPIResponse; } });
17
+ var fetch_1 = require("./fetch");
18
+ Object.defineProperty(exports, "post", { enumerable: true, get: function () { return fetch_1.post; } });
19
+ Object.defineProperty(exports, "POST_OFFICE_FORGE_NOTIFICATION_PATH", { enumerable: true, get: function () { return fetch_1.POST_OFFICE_FORGE_NOTIFICATION_PATH; } });
@@ -0,0 +1,10 @@
1
+ import { FetchMethod } from '@forge/api';
2
+ import { DeliveryChannel, NotificationPayload, NotificationPORequest, NotificationResponse, NotificationStatusResponse, RecipientType } from './types';
3
+ export declare class NotificationApi {
4
+ private readonly apiClient;
5
+ constructor(apiClient?: FetchMethod);
6
+ sendNotification(payload: NotificationPayload): Promise<NotificationResponse>;
7
+ getNotificationStatus(triggerId: string, recipient: string, recipientType: RecipientType, deliveryChannel: DeliveryChannel): Promise<NotificationStatusResponse>;
8
+ createNotificationPORequest(input: NotificationPayload, triggerId: string): NotificationPORequest;
9
+ }
10
+ //# sourceMappingURL=notification-api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-api.d.ts","sourceRoot":"","sources":["../src/notification-api.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2B,WAAW,EAAE,MAAM,YAAY,CAAC;AAGlE,OAAO,EACL,eAAe,EAGf,mBAAmB,EACnB,qBAAqB,EACrB,oBAAoB,EACpB,0BAA0B,EAE1B,aAAa,EACd,MAAM,SAAS,CAAC;AAWjB,qBAAa,eAAe;IACd,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAAT,SAAS,GAAE,WAAqC;IAEvE,gBAAgB,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAe7E,qBAAqB,CACzB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,aAAa,EAC5B,eAAe,EAAE,eAAe,GAC/B,OAAO,CAAC,0BAA0B,CAAC;IAoBtC,2BAA2B,CAAC,KAAK,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,GAAG,qBAAqB;CAkClG"}
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NotificationApi = void 0;
4
+ const api_1 = require("@forge/api");
5
+ const uuid_1 = require("uuid");
6
+ const fetch_1 = require("./fetch");
7
+ const types_1 = require("./types");
8
+ const validators_1 = require("./validators");
9
+ const CLIENT_FORGE_NOTIFICATION_SDK = 'forge-notification-sdk';
10
+ const ECOSYSTEM_FORGE_NOTIFICATION_TEMPLATE = 'ecosystem-forge-notifications-spike';
11
+ const userIdToIdentityAri = (userId) => ({
12
+ userId,
13
+ toString: () => `ari:cloud:identity::user/${userId}`
14
+ });
15
+ class NotificationApi {
16
+ apiClient;
17
+ constructor(apiClient = api_1.__requestAtlassianAsApp) {
18
+ this.apiClient = apiClient;
19
+ }
20
+ async sendNotification(payload) {
21
+ (0, validators_1.validateNotificationPayload)(payload);
22
+ const triggerId = (0, uuid_1.v4)();
23
+ const notificationPORequest = this.createNotificationPORequest(payload, triggerId);
24
+ const response = await (0, fetch_1.post)(fetch_1.POST_OFFICE_FORGE_NOTIFICATION_PATH, notificationPORequest, this.apiClient);
25
+ await (0, validators_1.validateAPIResponse)(response, 'notification');
26
+ return {
27
+ ...(await response.json()),
28
+ triggerId
29
+ };
30
+ }
31
+ async getNotificationStatus(triggerId, recipient, recipientType, deliveryChannel) {
32
+ (0, validators_1.validateNotificationStatusParams)(triggerId, recipient, recipientType, deliveryChannel);
33
+ const endpoint = `${fetch_1.POST_OFFICE_FORGE_STATUS_PATH}?triggerId=${encodeURIComponent(triggerId)}&recipient=${encodeURIComponent(recipientType === types_1.RecipientType.ACCOUNT_ID ? userIdToIdentityAri(recipient).toString() : recipient)}&recipientType=${encodeURIComponent(recipientType)}&deliveryChannel=${encodeURIComponent(deliveryChannel)}`;
34
+ const response = await (0, fetch_1.get)(endpoint, this.apiClient);
35
+ await (0, validators_1.validateAPIResponse)(response, 'status');
36
+ const responseData = await response.json();
37
+ const allStatuses = Array.isArray(responseData.allStatuses) ? responseData.allStatuses : [];
38
+ return {
39
+ triggerId: responseData.triggerId,
40
+ recipient: responseData.recipient,
41
+ deliveryChannel: responseData.deliveryChannel,
42
+ latestStatus: allStatuses.length > 0 ? allStatuses[0].status : responseData.deliveryStatus,
43
+ allStatuses,
44
+ errors: responseData.errors
45
+ };
46
+ }
47
+ createNotificationPORequest(input, triggerId) {
48
+ const recipientsToProcess = {};
49
+ if (input.recipientsUserIds && input.recipientsUserIds.length > 0) {
50
+ const userAriList = input.recipientsUserIds.map((userId) => userIdToIdentityAri(userId).toString());
51
+ recipientsToProcess.users = {
52
+ userAri: userAriList
53
+ };
54
+ }
55
+ if (input.recipientEmails && input.recipientEmails.length > 0) {
56
+ recipientsToProcess.emails = {
57
+ emailAddresses: input.recipientEmails
58
+ };
59
+ }
60
+ const messageTemplateData = {
61
+ id: ECOSYSTEM_FORGE_NOTIFICATION_TEMPLATE,
62
+ context: input.context
63
+ };
64
+ return {
65
+ triggerId,
66
+ messageTemplateData,
67
+ recipientsToProcess: recipientsToProcess,
68
+ eventTime: new Date().toISOString(),
69
+ client: CLIENT_FORGE_NOTIFICATION_SDK
70
+ };
71
+ }
72
+ }
73
+ exports.NotificationApi = NotificationApi;
package/out/types.d.ts ADDED
@@ -0,0 +1,68 @@
1
+ interface Ari {
2
+ toString: () => string;
3
+ toJSON?: () => string;
4
+ }
5
+ export declare type IdentityUserAri = Ari & {
6
+ userId: string;
7
+ };
8
+ export declare enum DeliveryChannel {
9
+ EMAIL = "email"
10
+ }
11
+ export declare enum RecipientType {
12
+ EMAIL_ADDRESS = "EmailAddress",
13
+ ACCOUNT_ID = "AccountId"
14
+ }
15
+ export declare type NotificationPayload = {
16
+ deliveryChannel?: DeliveryChannel;
17
+ context: MessageTemplateContext;
18
+ recipientsUserIds?: string[];
19
+ recipientEmails?: EmailRecipient[];
20
+ };
21
+ export declare type EmailRecipient = {
22
+ emailAddress: string;
23
+ locale?: string;
24
+ };
25
+ export declare type RecipientsToProcess = {
26
+ users?: {
27
+ userAri: string[];
28
+ };
29
+ emails?: {
30
+ emailAddresses: EmailRecipient[];
31
+ };
32
+ };
33
+ export declare type MessageTemplateData = {
34
+ id: string;
35
+ context: MessageTemplateContext;
36
+ };
37
+ export declare type MessageTemplateContext = {
38
+ subject: string;
39
+ htmlBody: string;
40
+ };
41
+ export declare type NotificationPORequest = {
42
+ triggerId: string;
43
+ messageTemplateData: MessageTemplateData;
44
+ recipientsToProcess: RecipientsToProcess;
45
+ client: string;
46
+ eventTime?: string;
47
+ entity?: Record<string, any>;
48
+ shardingContextId?: string;
49
+ actors?: IdentityUserAri[];
50
+ };
51
+ export interface NotificationResponse {
52
+ triggerId?: string;
53
+ errors?: string[];
54
+ }
55
+ export interface StatusDetails {
56
+ status: string;
57
+ timestamp: number;
58
+ }
59
+ export interface NotificationStatusResponse {
60
+ triggerId: string;
61
+ recipient: string;
62
+ deliveryChannel: string;
63
+ latestStatus: string;
64
+ allStatuses: StatusDetails[];
65
+ errors?: string[];
66
+ }
67
+ export {};
68
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,UAAU,GAAG;IACX,QAAQ,EAAE,MAAM,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,MAAM,CAAC;CACvB;AAED,oBAAY,eAAe,GAAG,GAAG,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvD,oBAAY,eAAe;IACzB,KAAK,UAAU;CAChB;AAED,oBAAY,aAAa;IACvB,aAAa,iBAAiB;IAC9B,UAAU,cAAc;CACzB;AACD,oBAAY,mBAAmB,GAAG;IAChC,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,OAAO,EAAE,sBAAsB,CAAC;IAChC,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,eAAe,CAAC,EAAE,cAAc,EAAE,CAAC;CACpC,CAAC;AAEF,oBAAY,cAAc,GAAG;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,oBAAY,mBAAmB,GAAG;IAChC,KAAK,CAAC,EAAE;QACN,OAAO,EAAE,MAAM,EAAE,CAAC;KACnB,CAAC;IACF,MAAM,CAAC,EAAE;QACP,cAAc,EAAE,cAAc,EAAE,CAAC;KAClC,CAAC;CACH,CAAC;AAEF,oBAAY,mBAAmB,GAAG;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,sBAAsB,CAAC;CACjC,CAAC;AAEF,oBAAY,sBAAsB,GAAG;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,oBAAY,qBAAqB,GAAG;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,mBAAmB,EAAE,mBAAmB,CAAC;IACzC,mBAAmB,EAAE,mBAAmB,CAAC;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,MAAM,CAAC,EAAE,eAAe,EAAE,CAAC;CAC5B,CAAC;AAEF,MAAM,WAAW,oBAAoB;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAGD,MAAM,WAAW,0BAA0B;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,aAAa,EAAE,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB"}
package/out/types.js ADDED
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RecipientType = exports.DeliveryChannel = void 0;
4
+ var DeliveryChannel;
5
+ (function (DeliveryChannel) {
6
+ DeliveryChannel["EMAIL"] = "email";
7
+ })(DeliveryChannel = exports.DeliveryChannel || (exports.DeliveryChannel = {}));
8
+ var RecipientType;
9
+ (function (RecipientType) {
10
+ RecipientType["EMAIL_ADDRESS"] = "EmailAddress";
11
+ RecipientType["ACCOUNT_ID"] = "AccountId";
12
+ })(RecipientType = exports.RecipientType || (exports.RecipientType = {}));
@@ -0,0 +1,6 @@
1
+ import { DeliveryChannel, NotificationPayload, RecipientType } from './types';
2
+ import { APIResponse } from '@forge/api';
3
+ export declare const validateNotificationPayload: (payload: NotificationPayload) => boolean;
4
+ export declare const validateNotificationStatusParams: (triggerId: string, recipient: string, recipientType: RecipientType, deliveryChannel: DeliveryChannel) => void;
5
+ export declare const validateAPIResponse: (response: APIResponse, method: 'notification' | 'status') => Promise<void>;
6
+ //# sourceMappingURL=validators.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validators.d.ts","sourceRoot":"","sources":["../src/validators.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC9E,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAGzC,eAAO,MAAM,2BAA2B,YAAa,mBAAmB,YAqDvE,CAAC;AAEF,eAAO,MAAM,gCAAgC,cAChC,MAAM,aACN,MAAM,iBACF,aAAa,mBACX,eAAe,SAoCjC,CAAC;AAEF,eAAO,MAAM,mBAAmB,aAAoB,WAAW,UAAU,cAAc,GAAG,QAAQ,kBAejG,CAAC"}
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateAPIResponse = exports.validateNotificationStatusParams = exports.validateNotificationPayload = void 0;
4
+ const errors_1 = require("./errors");
5
+ const types_1 = require("./types");
6
+ const validateNotificationPayload = (payload) => {
7
+ if (!payload || typeof payload !== 'object') {
8
+ throw new errors_1.InvalidPayloadError('Payload must be an object');
9
+ }
10
+ if (payload.recipientsUserIds !== undefined) {
11
+ if (!Array.isArray(payload.recipientsUserIds)) {
12
+ throw new errors_1.InvalidPayloadError('recipientsUserIds must be an array');
13
+ }
14
+ for (const userId of payload.recipientsUserIds) {
15
+ if (typeof userId !== 'string') {
16
+ throw new errors_1.InvalidPayloadError(`Invalid userId: ${JSON.stringify(userId)}`);
17
+ }
18
+ }
19
+ }
20
+ if (payload.recipientEmails !== undefined) {
21
+ if (!Array.isArray(payload.recipientEmails)) {
22
+ throw new errors_1.InvalidPayloadError('recipientEmails must be an array');
23
+ }
24
+ for (const email of payload.recipientEmails) {
25
+ if (!email || typeof email !== 'object') {
26
+ throw new errors_1.InvalidPayloadError('Invalid email recipient object');
27
+ }
28
+ if (!email.emailAddress || typeof email.emailAddress !== 'string') {
29
+ throw new errors_1.InvalidPayloadError('Invalid email address');
30
+ }
31
+ if (email.locale !== undefined && typeof email.locale !== 'string') {
32
+ throw new errors_1.InvalidPayloadError('Invalid email locale');
33
+ }
34
+ }
35
+ }
36
+ const hasValidUserIds = payload.recipientsUserIds && Array.isArray(payload.recipientsUserIds) && payload.recipientsUserIds.length > 0;
37
+ const hasValidEmails = payload.recipientEmails && Array.isArray(payload.recipientEmails) && payload.recipientEmails.length > 0;
38
+ if (!hasValidUserIds && !hasValidEmails) {
39
+ throw new errors_1.InvalidPayloadError('Either recipientsUserIds or recipientEmails must be provided');
40
+ }
41
+ if (!payload.context?.subject || typeof payload.context?.subject !== 'string') {
42
+ throw new errors_1.InvalidPayloadError('Missing or invalid subject');
43
+ }
44
+ if (!payload.context?.htmlBody || typeof payload.context?.htmlBody !== 'string') {
45
+ throw new errors_1.InvalidPayloadError('Missing or invalid htmlBody');
46
+ }
47
+ return true;
48
+ };
49
+ exports.validateNotificationPayload = validateNotificationPayload;
50
+ const validateNotificationStatusParams = (triggerId, recipient, recipientType, deliveryChannel) => {
51
+ if (triggerId !== undefined) {
52
+ if (typeof triggerId !== 'string' || triggerId.trim() === '') {
53
+ throw new errors_1.InvalidPayloadError('Invalid triggerId: must be a non-empty string when provided');
54
+ }
55
+ }
56
+ if (recipient !== undefined) {
57
+ if (typeof recipient !== 'string' || recipient.trim() === '') {
58
+ throw new errors_1.InvalidPayloadError('Invalid recipient: must be a non-empty string when provided');
59
+ }
60
+ }
61
+ if (recipientType !== undefined) {
62
+ if (typeof recipientType !== 'string' || recipientType.trim() === '') {
63
+ throw new errors_1.InvalidPayloadError('Invalid recipientType: must be a non-empty string when provided');
64
+ }
65
+ const allowedRecipientTypes = Object.values(types_1.RecipientType);
66
+ if (!allowedRecipientTypes.includes(recipientType)) {
67
+ throw new errors_1.InvalidPayloadError(`Invalid recipientType: must be one of ${allowedRecipientTypes.join(', ')}`);
68
+ }
69
+ }
70
+ if (deliveryChannel !== undefined) {
71
+ if (typeof deliveryChannel !== 'string' || deliveryChannel.trim() === '') {
72
+ throw new errors_1.InvalidPayloadError('Invalid deliveryChannel: must be a non-empty string when provided');
73
+ }
74
+ }
75
+ const allowedChannels = Object.values(types_1.DeliveryChannel);
76
+ if (!allowedChannels.includes(deliveryChannel)) {
77
+ throw new errors_1.InvalidPayloadError(`Invalid deliveryChannel: must be one of ${allowedChannels.join(', ')}`);
78
+ }
79
+ };
80
+ exports.validateNotificationStatusParams = validateNotificationStatusParams;
81
+ const validateAPIResponse = async (response, method) => {
82
+ if (!response || typeof response !== 'object') {
83
+ throw new errors_1.InternalServerError('Unexpected response object');
84
+ }
85
+ if (response.status === 429) {
86
+ throw new errors_1.RateLimitError(`Rate limit exceeded ${response.statusText}`);
87
+ }
88
+ if (!response.ok) {
89
+ throw new errors_1.InternalServerError(`Failed to ${method === 'status' ? 'fetch status' : 'send notification'}: ${response.statusText}`, response.status, await response.text());
90
+ }
91
+ };
92
+ exports.validateAPIResponse = validateAPIResponse;
package/package.json CHANGED
@@ -1,13 +1,24 @@
1
1
  {
2
2
  "name": "@forge/notification",
3
- "version": "0.0.2-next.0",
3
+ "version": "0.1.0-next.0",
4
4
  "description": "Forge Notification SDK",
5
5
  "main": "out/index.js",
6
+ "types": "out/index.d.ts",
6
7
  "scripts": {
7
8
  "build": "yarn run clean && yarn run compile",
8
9
  "clean": "rm -rf ./out && rm -f tsconfig.tsbuildinfo",
9
10
  "compile": "tsc -b -v"
10
11
  },
12
+ "dependencies": {
13
+ "@forge/api": "^6.4.1-next.0",
14
+ "uuid": "^9.0.1"
15
+ },
16
+ "devDependencies": {
17
+ "@types/node": "20.19.1",
18
+ "@types/uuid": "^9.0.8",
19
+ "expect-type": "^0.17.3",
20
+ "jest-when": "^3.6.0"
21
+ },
11
22
  "author": "Atlassian",
12
23
  "license": "SEE LICENSE IN LICENSE.txt",
13
24
  "publishConfig": {