@memberjunction/communication-expo-push 0.0.0 → 5.45.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/README.md CHANGED
@@ -1,45 +1,35 @@
1
1
  # @memberjunction/communication-expo-push
2
2
 
3
- ## ⚠️ IMPORTANT NOTICE ⚠️
3
+ Expo push-notification provider for the MemberJunction Communication framework. Sends mobile push notifications to [Expo push tokens](https://docs.expo.dev/push-notifications/overview/) through the same communication engine used for email and SMS.
4
4
 
5
- **This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
5
+ ## What it does
6
6
 
7
- This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
7
+ Registers an `ExpoPushProvider` (via `@RegisterClass(BaseCommunicationProvider, 'Expo Push')`) that the communication engine discovers by provider name. It sends a single HTTPS `POST` to the Expo Push API (`https://exp.host/--/api/v2/push/send`) — no Expo SDK is required (it uses the global `fetch`).
8
8
 
9
- ## Purpose
9
+ ## Message mapping
10
10
 
11
- This package exists to:
12
- 1. Configure OIDC trusted publishing for the package name `@memberjunction/communication-expo-push`
13
- 2. Enable secure, token-less publishing from CI/CD workflows
14
- 3. Establish provenance for packages published under this name
11
+ | Framework `ProcessedMessage` | Expo payload |
12
+ |-----------------------------------------|--------------|
13
+ | `To` | `to` (the Expo push token) |
14
+ | `ProcessedSubject` (fallback `Subject`) | `title` |
15
+ | `ProcessedBody` (fallback `Body`) | `body` |
16
+ | `ContextData.pushData` / `.data` | `data` |
15
17
 
16
- ## What is OIDC Trusted Publishing?
18
+ The provider inspects the Expo response ticket: an `ok` ticket yields `Success: true`; an `error` ticket (or a top-level request error / non-OK HTTP status) yields `Success: false` with the Expo error surfaced in `MessageResult.Error`.
17
19
 
18
- OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
20
+ ## Configuration
19
21
 
20
- ## Setup Instructions
22
+ Both are optional and read via environment variables (or per-request credentials):
21
23
 
22
- To properly configure OIDC trusted publishing for this package:
24
+ - `EXPO_ACCESS_TOKEN` optional Expo access token sent as a `Bearer` token for higher rate limits. The provider degrades gracefully (still sends) without it.
25
+ - `EXPO_PUSH_API_URL` — override the Expo endpoint (defaults to the public Expo Push service).
23
26
 
24
- 1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
25
- 2. Configure the trusted publisher (e.g., GitHub Actions)
26
- 3. Specify the repository and workflow that should be allowed to publish
27
- 4. Use the configured workflow to publish your actual package
27
+ Per-request override:
28
28
 
29
- ## DO NOT USE THIS PACKAGE
29
+ ```typescript
30
+ await provider.SendSingleMessage(message, { accessToken: 'expo-token-xyz' });
31
+ ```
30
32
 
31
- This package is a placeholder for OIDC configuration only. It:
32
- - Contains no executable code
33
- - Provides no functionality
34
- - Should not be installed as a dependency
35
- - Exists only for administrative purposes
33
+ ## Supported operations
36
34
 
37
- ## More Information
38
-
39
- For more details about npm's trusted publishing feature, see:
40
- - [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
41
- - [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
42
-
43
- ---
44
-
45
- **Maintained for OIDC setup purposes only**
35
+ `SendSingleMessage` only. Push is a send-only channel, so `GetMessages`, `ForwardMessage`, `ReplyToMessage`, and `CreateDraft` return a "not supported" result.
@@ -0,0 +1,81 @@
1
+ import { BaseCommunicationProvider, CreateDraftParams, CreateDraftResult, ForwardMessageParams, ForwardMessageResult, GetMessagesParams, GetMessagesResult, MessageResult, ProcessedMessage, ProviderCredentialsBase, ReplyToMessageParams, ReplyToMessageResult, ProviderOperation } from "@memberjunction/communication-types";
2
+ /**
3
+ * Credentials for the Expo Push provider.
4
+ * Extends {@link ProviderCredentialsBase} to support per-request credential override.
5
+ *
6
+ * @remarks
7
+ * All fields are optional. The Expo Push API can be called anonymously; supplying
8
+ * an `accessToken` simply raises rate limits and enables enhanced push security.
9
+ * The provider therefore degrades gracefully when no token is available.
10
+ */
11
+ export interface ExpoPushCredentials extends ProviderCredentialsBase {
12
+ /** Optional Expo access token used as a Bearer token for higher rate limits. */
13
+ accessToken?: string;
14
+ }
15
+ /**
16
+ * Implementation of the Expo push-notification provider for MemberJunction's
17
+ * Communication framework. Sends mobile push notifications to Expo push tokens
18
+ * via the Expo Push API using a simple HTTPS POST (no SDK dependency).
19
+ *
20
+ * @remarks
21
+ * Push notifications are a fire-and-forget, send-only channel. Consequently this
22
+ * provider implements only {@link SendSingleMessage}; the mailbox-style operations
23
+ * (`GetMessages`, `ForwardMessage`, `ReplyToMessage`, `CreateDraft`) return a
24
+ * "not supported" result, consistent with the base-class contract.
25
+ */
26
+ export declare class ExpoPushProvider extends BaseCommunicationProvider {
27
+ /**
28
+ * Push is a real-time, send-only channel — only `SendSingleMessage` is supported.
29
+ */
30
+ getSupportedOperations(): ProviderOperation[];
31
+ /**
32
+ * Resolves credentials by merging request credentials with environment fallback.
33
+ * The access token is optional, so no required-field validation is performed.
34
+ */
35
+ private resolveCredentials;
36
+ /**
37
+ * Extracts the optional JSON `data` payload from a message's context data.
38
+ * Callers may set `ContextData.pushData` (preferred) or `ContextData.data`.
39
+ */
40
+ private extractData;
41
+ /**
42
+ * Builds the Expo Push API request headers, attaching the Bearer token when present.
43
+ */
44
+ private buildHeaders;
45
+ /**
46
+ * Normalizes the Expo response `data` (single ticket or array) to a single ticket.
47
+ */
48
+ private extractTicket;
49
+ /**
50
+ * Sends a single push notification via the Expo Push API.
51
+ *
52
+ * Maps the framework message model onto the Expo payload:
53
+ * - `message.To` → the recipient Expo push token
54
+ * - `message.ProcessedSubject` (fallback `message.Subject`) → `title`
55
+ * - `message.ProcessedBody` (fallback `message.Body`) → `body`
56
+ * - `message.ContextData.pushData` / `.data` → `data`
57
+ *
58
+ * @param message - The processed message to send.
59
+ * @param credentials - Optional per-request credential override. When omitted,
60
+ * the Expo access token (if any) is read from the environment.
61
+ * @returns The framework's standard {@link MessageResult}.
62
+ */
63
+ SendSingleMessage(message: ProcessedMessage, credentials?: ExpoPushCredentials): Promise<MessageResult>;
64
+ /**
65
+ * Expo push is send-only; retrieving messages is not supported.
66
+ */
67
+ GetMessages(params: GetMessagesParams, credentials?: ExpoPushCredentials): Promise<GetMessagesResult>;
68
+ /**
69
+ * Expo push is send-only; forwarding is not supported.
70
+ */
71
+ ForwardMessage(params: ForwardMessageParams, credentials?: ExpoPushCredentials): Promise<ForwardMessageResult>;
72
+ /**
73
+ * Expo push is send-only; replying is not supported.
74
+ */
75
+ ReplyToMessage(params: ReplyToMessageParams, credentials?: ExpoPushCredentials): Promise<ReplyToMessageResult>;
76
+ /**
77
+ * Expo push has no draft concept; creating drafts is not supported.
78
+ */
79
+ CreateDraft(params: CreateDraftParams, credentials?: ExpoPushCredentials): Promise<CreateDraftResult>;
80
+ }
81
+ //# sourceMappingURL=ExpoPushProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpoPushProvider.d.ts","sourceRoot":"","sources":["../src/ExpoPushProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,yBAAyB,EACzB,iBAAiB,EACjB,iBAAiB,EACjB,oBAAoB,EACpB,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,EACb,gBAAgB,EAChB,uBAAuB,EACvB,oBAAoB,EACpB,oBAAoB,EAEpB,iBAAiB,EAClB,MAAM,qCAAqC,CAAC;AAK7C;;;;;;;;GAQG;AACH,MAAM,WAAW,mBAAoB,SAAQ,uBAAuB;IAClE,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAyDD;;;;;;;;;;GAUG;AACH,qBACa,gBAAiB,SAAQ,yBAAyB;IAC7D;;OAEG;IACa,sBAAsB,IAAI,iBAAiB,EAAE;IAI7D;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IAM1B;;;OAGG;IACH,OAAO,CAAC,WAAW;IASnB;;OAEG;IACH,OAAO,CAAC,YAAY;IAYpB;;OAEG;IACH,OAAO,CAAC,aAAa;IAQrB;;;;;;;;;;;;;OAaG;IACU,iBAAiB,CAC5B,OAAO,EAAE,gBAAgB,EACzB,WAAW,CAAC,EAAE,mBAAmB,GAChC,OAAO,CAAC,aAAa,CAAC;IAoFzB;;OAEG;IACU,WAAW,CACtB,MAAM,EAAE,iBAAiB,EACzB,WAAW,CAAC,EAAE,mBAAmB,GAChC,OAAO,CAAC,iBAAiB,CAAC;IAQ7B;;OAEG;IACU,cAAc,CACzB,MAAM,EAAE,oBAAoB,EAC5B,WAAW,CAAC,EAAE,mBAAmB,GAChC,OAAO,CAAC,oBAAoB,CAAC;IAOhC;;OAEG;IACU,cAAc,CACzB,MAAM,EAAE,oBAAoB,EAC5B,WAAW,CAAC,EAAE,mBAAmB,GAChC,OAAO,CAAC,oBAAoB,CAAC;IAOhC;;OAEG;IACU,WAAW,CACtB,MAAM,EAAE,iBAAiB,EACzB,WAAW,CAAC,EAAE,mBAAmB,GAChC,OAAO,CAAC,iBAAiB,CAAC;CAM9B"}
@@ -0,0 +1,205 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { BaseCommunicationProvider, resolveCredentialValue } from "@memberjunction/communication-types";
8
+ import { RegisterClass } from "@memberjunction/global";
9
+ import { LogError, LogStatus } from "@memberjunction/core";
10
+ import * as Config from "./config.js";
11
+ /**
12
+ * Implementation of the Expo push-notification provider for MemberJunction's
13
+ * Communication framework. Sends mobile push notifications to Expo push tokens
14
+ * via the Expo Push API using a simple HTTPS POST (no SDK dependency).
15
+ *
16
+ * @remarks
17
+ * Push notifications are a fire-and-forget, send-only channel. Consequently this
18
+ * provider implements only {@link SendSingleMessage}; the mailbox-style operations
19
+ * (`GetMessages`, `ForwardMessage`, `ReplyToMessage`, `CreateDraft`) return a
20
+ * "not supported" result, consistent with the base-class contract.
21
+ */
22
+ let ExpoPushProvider = class ExpoPushProvider extends BaseCommunicationProvider {
23
+ /**
24
+ * Push is a real-time, send-only channel — only `SendSingleMessage` is supported.
25
+ */
26
+ getSupportedOperations() {
27
+ return ['SendSingleMessage'];
28
+ }
29
+ /**
30
+ * Resolves credentials by merging request credentials with environment fallback.
31
+ * The access token is optional, so no required-field validation is performed.
32
+ */
33
+ resolveCredentials(credentials) {
34
+ const disableFallback = credentials?.disableEnvironmentFallback ?? false;
35
+ const accessToken = resolveCredentialValue(credentials?.accessToken, Config.EXPO_ACCESS_TOKEN, disableFallback);
36
+ return { accessToken: accessToken || '' };
37
+ }
38
+ /**
39
+ * Extracts the optional JSON `data` payload from a message's context data.
40
+ * Callers may set `ContextData.pushData` (preferred) or `ContextData.data`.
41
+ */
42
+ extractData(message) {
43
+ const context = message.ContextData;
44
+ if (!context) {
45
+ return undefined;
46
+ }
47
+ const data = (context.pushData ?? context.data);
48
+ return data && typeof data === 'object' ? data : undefined;
49
+ }
50
+ /**
51
+ * Builds the Expo Push API request headers, attaching the Bearer token when present.
52
+ */
53
+ buildHeaders(creds) {
54
+ const headers = {
55
+ 'Content-Type': 'application/json',
56
+ 'Accept': 'application/json',
57
+ 'Accept-Encoding': 'gzip, deflate'
58
+ };
59
+ if (creds.accessToken) {
60
+ headers['Authorization'] = `Bearer ${creds.accessToken}`;
61
+ }
62
+ return headers;
63
+ }
64
+ /**
65
+ * Normalizes the Expo response `data` (single ticket or array) to a single ticket.
66
+ */
67
+ extractTicket(response) {
68
+ const data = response.data;
69
+ if (Array.isArray(data)) {
70
+ return data[0];
71
+ }
72
+ return data;
73
+ }
74
+ /**
75
+ * Sends a single push notification via the Expo Push API.
76
+ *
77
+ * Maps the framework message model onto the Expo payload:
78
+ * - `message.To` → the recipient Expo push token
79
+ * - `message.ProcessedSubject` (fallback `message.Subject`) → `title`
80
+ * - `message.ProcessedBody` (fallback `message.Body`) → `body`
81
+ * - `message.ContextData.pushData` / `.data` → `data`
82
+ *
83
+ * @param message - The processed message to send.
84
+ * @param credentials - Optional per-request credential override. When omitted,
85
+ * the Expo access token (if any) is read from the environment.
86
+ * @returns The framework's standard {@link MessageResult}.
87
+ */
88
+ async SendSingleMessage(message, credentials) {
89
+ try {
90
+ if (!message.To) {
91
+ return {
92
+ Message: message,
93
+ Success: false,
94
+ Error: 'Recipient push token not specified'
95
+ };
96
+ }
97
+ const creds = this.resolveCredentials(credentials);
98
+ const payload = {
99
+ to: message.To,
100
+ title: message.ProcessedSubject || message.Subject || undefined,
101
+ body: message.ProcessedBody || message.Body || '',
102
+ data: this.extractData(message)
103
+ };
104
+ const response = await fetch(Config.EXPO_PUSH_API_URL, {
105
+ method: 'POST',
106
+ headers: this.buildHeaders(creds),
107
+ body: JSON.stringify(payload)
108
+ });
109
+ if (!response.ok) {
110
+ const text = await response.text();
111
+ return {
112
+ Message: message,
113
+ Success: false,
114
+ Error: `Expo Push API returned HTTP ${response.status} ${response.statusText}: ${text}`
115
+ };
116
+ }
117
+ const json = (await response.json());
118
+ // Top-level request errors (e.g. malformed payload) take precedence over tickets
119
+ if (json.errors && json.errors.length > 0) {
120
+ const errorText = json.errors.map((e) => e.message || e.code || 'Unknown error').join('; ');
121
+ LogError(`Expo Push API request error: ${errorText}`);
122
+ return {
123
+ Message: message,
124
+ Success: false,
125
+ Error: `Expo Push API request error: ${errorText}`
126
+ };
127
+ }
128
+ const ticket = this.extractTicket(json);
129
+ if (!ticket) {
130
+ return {
131
+ Message: message,
132
+ Success: false,
133
+ Error: 'Expo Push API returned no push ticket'
134
+ };
135
+ }
136
+ if (ticket.status === 'error') {
137
+ const detail = ticket.details?.error ? ` (${String(ticket.details.error)})` : '';
138
+ const errorMessage = `${ticket.message || 'Expo push ticket returned an error'}${detail}`;
139
+ LogError(`Expo push error ticket: ${errorMessage}`);
140
+ return {
141
+ Message: message,
142
+ Success: false,
143
+ Error: errorMessage
144
+ };
145
+ }
146
+ LogStatus(`Push notification sent via Expo (receipt ID: ${ticket.id ?? 'n/a'})`);
147
+ return {
148
+ Message: message,
149
+ Success: true,
150
+ Error: ''
151
+ };
152
+ }
153
+ catch (error) {
154
+ const errorMessage = error instanceof Error ? error.message : 'Error sending push notification';
155
+ LogError('Error sending push notification via Expo', undefined, error);
156
+ return {
157
+ Message: message,
158
+ Success: false,
159
+ Error: errorMessage
160
+ };
161
+ }
162
+ }
163
+ /**
164
+ * Expo push is send-only; retrieving messages is not supported.
165
+ */
166
+ async GetMessages(params, credentials) {
167
+ return {
168
+ Success: false,
169
+ Messages: [],
170
+ ErrorMessage: `Expo Push does not support GetMessages (Identifier: ${params.Identifier ?? 'n/a'}, credentials provided: ${!!credentials})`
171
+ };
172
+ }
173
+ /**
174
+ * Expo push is send-only; forwarding is not supported.
175
+ */
176
+ async ForwardMessage(params, credentials) {
177
+ return {
178
+ Success: false,
179
+ ErrorMessage: `Expo Push does not support ForwardMessage (MessageID: ${params.MessageID}, credentials provided: ${!!credentials})`
180
+ };
181
+ }
182
+ /**
183
+ * Expo push is send-only; replying is not supported.
184
+ */
185
+ async ReplyToMessage(params, credentials) {
186
+ return {
187
+ Success: false,
188
+ ErrorMessage: `Expo Push does not support ReplyToMessage (MessageID: ${params.MessageID}, credentials provided: ${!!credentials})`
189
+ };
190
+ }
191
+ /**
192
+ * Expo push has no draft concept; creating drafts is not supported.
193
+ */
194
+ async CreateDraft(params, credentials) {
195
+ return {
196
+ Success: false,
197
+ ErrorMessage: `Expo Push does not support creating draft messages (credentials provided: ${!!credentials}).`
198
+ };
199
+ }
200
+ };
201
+ ExpoPushProvider = __decorate([
202
+ RegisterClass(BaseCommunicationProvider, 'Expo Push')
203
+ ], ExpoPushProvider);
204
+ export { ExpoPushProvider };
205
+ //# sourceMappingURL=ExpoPushProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpoPushProvider.js","sourceRoot":"","sources":["../src/ExpoPushProvider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ExpoPushProvider.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpoPushProvider.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/ExpoPushProvider.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Unit tests for the Expo push provider.
3
+ * Tests: payload construction from a message, success (ok ticket), error ticket
4
+ * handling, HTTP/transport errors, missing-token handling, and access-token header.
5
+ * All network access is mocked — no real requests are made.
6
+ */
7
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
8
+ // ---------------------------------------------------------------------------
9
+ // Mocks
10
+ // ---------------------------------------------------------------------------
11
+ const { mockFetch } = vi.hoisted(() => ({
12
+ mockFetch: vi.fn(),
13
+ }));
14
+ // Install a mocked global fetch
15
+ vi.stubGlobal('fetch', mockFetch);
16
+ // Mock dotenv (avoid reading a real .env during import)
17
+ vi.mock('dotenv', () => ({
18
+ default: { config: vi.fn() },
19
+ }));
20
+ // Mock config.ts directly to avoid dotenv/env-var import concerns
21
+ vi.mock('../config', () => ({
22
+ EXPO_PUSH_API_URL: 'https://exp.host/--/api/v2/push/send',
23
+ EXPO_ACCESS_TOKEN: '',
24
+ }));
25
+ vi.mock('@memberjunction/communication-types', () => ({
26
+ BaseCommunicationProvider: class {
27
+ getSupportedOperations() { return []; }
28
+ },
29
+ resolveCredentialValue: (requestVal, envVal, disableFallback) => {
30
+ if (requestVal)
31
+ return requestVal;
32
+ if (!disableFallback && envVal)
33
+ return envVal;
34
+ return undefined;
35
+ },
36
+ }));
37
+ vi.mock('@memberjunction/global', async (importOriginal) => {
38
+ const actual = await importOriginal();
39
+ return {
40
+ ...actual,
41
+ RegisterClass: () => (target) => target,
42
+ };
43
+ });
44
+ vi.mock('@memberjunction/core', () => ({
45
+ LogError: vi.fn(),
46
+ LogStatus: vi.fn(),
47
+ }));
48
+ // ---------------------------------------------------------------------------
49
+ // Import after mocks
50
+ // ---------------------------------------------------------------------------
51
+ import { ExpoPushProvider } from '../ExpoPushProvider.js';
52
+ // ---------------------------------------------------------------------------
53
+ // Helpers
54
+ // ---------------------------------------------------------------------------
55
+ const createMessage = (overrides = {}) => ({
56
+ From: '',
57
+ To: 'ExponentPushToken[abc123]',
58
+ Body: '',
59
+ Subject: '',
60
+ ProcessedBody: 'You have a new message',
61
+ ProcessedHTMLBody: '',
62
+ ProcessedSubject: 'New Message',
63
+ ContextData: {},
64
+ ...overrides,
65
+ });
66
+ const mockResponse = (body, init = {}) => ({
67
+ ok: init.ok ?? true,
68
+ status: init.status ?? 200,
69
+ statusText: init.statusText ?? 'OK',
70
+ json: vi.fn().mockResolvedValue(body),
71
+ text: vi.fn().mockResolvedValue(typeof body === 'string' ? body : JSON.stringify(body)),
72
+ });
73
+ // ---------------------------------------------------------------------------
74
+ // Tests
75
+ // ---------------------------------------------------------------------------
76
+ describe('ExpoPushProvider', () => {
77
+ let provider;
78
+ beforeEach(() => {
79
+ vi.clearAllMocks();
80
+ provider = new ExpoPushProvider();
81
+ });
82
+ describe('getSupportedOperations', () => {
83
+ it('should support only SendSingleMessage', () => {
84
+ const ops = provider.getSupportedOperations();
85
+ expect(ops).toEqual(['SendSingleMessage']);
86
+ });
87
+ });
88
+ describe('SendSingleMessage', () => {
89
+ it('should build the Expo payload correctly from a message', async () => {
90
+ mockFetch.mockResolvedValue(mockResponse({ data: { status: 'ok', id: 'receipt-1' } }));
91
+ const result = await provider.SendSingleMessage(createMessage({ ContextData: { pushData: { screen: 'inbox' } } }));
92
+ expect(result.Success).toBe(true);
93
+ expect(mockFetch).toHaveBeenCalledTimes(1);
94
+ const [url, requestInit] = mockFetch.mock.calls[0];
95
+ expect(url).toBe('https://exp.host/--/api/v2/push/send');
96
+ expect(requestInit.method).toBe('POST');
97
+ const payload = JSON.parse(requestInit.body);
98
+ expect(payload).toEqual({
99
+ to: 'ExponentPushToken[abc123]',
100
+ title: 'New Message',
101
+ body: 'You have a new message',
102
+ data: { screen: 'inbox' },
103
+ });
104
+ });
105
+ it('should NOT send an Authorization header when no access token is configured', async () => {
106
+ mockFetch.mockResolvedValue(mockResponse({ data: { status: 'ok', id: 'receipt-2' } }));
107
+ await provider.SendSingleMessage(createMessage());
108
+ const requestInit = mockFetch.mock.calls[0][1];
109
+ expect(requestInit.headers.Authorization).toBeUndefined();
110
+ });
111
+ it('should send a Bearer Authorization header when an access token is provided', async () => {
112
+ mockFetch.mockResolvedValue(mockResponse({ data: { status: 'ok', id: 'receipt-3' } }));
113
+ await provider.SendSingleMessage(createMessage(), { accessToken: 'expo-token-xyz' });
114
+ const requestInit = mockFetch.mock.calls[0][1];
115
+ expect(requestInit.headers.Authorization).toBe('Bearer expo-token-xyz');
116
+ });
117
+ it('should return success on an ok ticket', async () => {
118
+ mockFetch.mockResolvedValue(mockResponse({ data: { status: 'ok', id: 'receipt-4' } }));
119
+ const result = await provider.SendSingleMessage(createMessage());
120
+ expect(result.Success).toBe(true);
121
+ expect(result.Error).toBe('');
122
+ });
123
+ it('should normalize an array-form data ticket', async () => {
124
+ mockFetch.mockResolvedValue(mockResponse({ data: [{ status: 'ok', id: 'receipt-5' }] }));
125
+ const result = await provider.SendSingleMessage(createMessage());
126
+ expect(result.Success).toBe(true);
127
+ });
128
+ it('should return failure on an error ticket', async () => {
129
+ mockFetch.mockResolvedValue(mockResponse({
130
+ data: { status: 'error', message: 'Device not registered', details: { error: 'DeviceNotRegistered' } },
131
+ }));
132
+ const result = await provider.SendSingleMessage(createMessage());
133
+ expect(result.Success).toBe(false);
134
+ expect(result.Error).toContain('Device not registered');
135
+ expect(result.Error).toContain('DeviceNotRegistered');
136
+ });
137
+ it('should return failure on top-level request errors', async () => {
138
+ mockFetch.mockResolvedValue(mockResponse({
139
+ errors: [{ code: 'PUSH_TOO_MANY_EXPERIENCE_IDS', message: 'Invalid batch' }],
140
+ }));
141
+ const result = await provider.SendSingleMessage(createMessage());
142
+ expect(result.Success).toBe(false);
143
+ expect(result.Error).toContain('Invalid batch');
144
+ });
145
+ it('should return failure on a non-OK HTTP response', async () => {
146
+ mockFetch.mockResolvedValue(mockResponse('Too Many Requests', { ok: false, status: 429, statusText: 'Too Many Requests' }));
147
+ const result = await provider.SendSingleMessage(createMessage());
148
+ expect(result.Success).toBe(false);
149
+ expect(result.Error).toContain('429');
150
+ });
151
+ it('should return failure when the recipient push token is missing', async () => {
152
+ const result = await provider.SendSingleMessage(createMessage({ To: '' }));
153
+ expect(result.Success).toBe(false);
154
+ expect(result.Error).toContain('Recipient push token not specified');
155
+ expect(mockFetch).not.toHaveBeenCalled();
156
+ });
157
+ it('should handle transport/fetch errors gracefully', async () => {
158
+ mockFetch.mockRejectedValue(new Error('Network down'));
159
+ const result = await provider.SendSingleMessage(createMessage());
160
+ expect(result.Success).toBe(false);
161
+ expect(result.Error).toContain('Network down');
162
+ });
163
+ it('should fall back to Subject/Body when processed fields are empty', async () => {
164
+ mockFetch.mockResolvedValue(mockResponse({ data: { status: 'ok', id: 'receipt-6' } }));
165
+ await provider.SendSingleMessage(createMessage({
166
+ ProcessedSubject: '',
167
+ ProcessedBody: '',
168
+ Subject: 'Fallback Title',
169
+ Body: 'Fallback Body',
170
+ }));
171
+ const payload = JSON.parse(mockFetch.mock.calls[0][1].body);
172
+ expect(payload.title).toBe('Fallback Title');
173
+ expect(payload.body).toBe('Fallback Body');
174
+ });
175
+ });
176
+ describe('unsupported operations', () => {
177
+ it('GetMessages should return unsupported', async () => {
178
+ const result = await provider.GetMessages({ NumMessages: 5 });
179
+ expect(result.Success).toBe(false);
180
+ expect(result.Messages).toEqual([]);
181
+ expect(result.ErrorMessage).toContain('does not support');
182
+ });
183
+ it('CreateDraft should return unsupported', async () => {
184
+ const result = await provider.CreateDraft({});
185
+ expect(result.Success).toBe(false);
186
+ expect(result.ErrorMessage).toContain('does not support');
187
+ });
188
+ });
189
+ });
190
+ //# sourceMappingURL=ExpoPushProvider.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpoPushProvider.test.js","sourceRoot":"","sources":["../../src/__tests__/ExpoPushProvider.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The Expo Push API endpoint. Overridable via the EXPO_PUSH_API_URL environment
3
+ * variable (useful for testing or self-hosted proxies), otherwise defaults to
4
+ * Expo's public push service.
5
+ */
6
+ export declare const EXPO_PUSH_API_URL: string;
7
+ /**
8
+ * Optional Expo access token. When configured, it is sent as a Bearer token to
9
+ * raise Expo's rate limits and enable enhanced push security. The provider
10
+ * degrades gracefully (still sends) when this is not set.
11
+ */
12
+ export declare const EXPO_ACCESS_TOKEN: string;
13
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAMA;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,QAGjB,CAAC;AAEd;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,QAAsD,CAAC"}
package/dist/config.js ADDED
@@ -0,0 +1,20 @@
1
+ import env from 'env-var';
2
+ import dotenv from 'dotenv';
3
+ // Load environment variables from .env file
4
+ dotenv.config({ quiet: true });
5
+ /**
6
+ * The Expo Push API endpoint. Overridable via the EXPO_PUSH_API_URL environment
7
+ * variable (useful for testing or self-hosted proxies), otherwise defaults to
8
+ * Expo's public push service.
9
+ */
10
+ export const EXPO_PUSH_API_URL = env
11
+ .get('EXPO_PUSH_API_URL')
12
+ .default('https://exp.host/--/api/v2/push/send')
13
+ .asString();
14
+ /**
15
+ * Optional Expo access token. When configured, it is sent as a Bearer token to
16
+ * raise Expo's rate limits and enable enhanced push security. The provider
17
+ * degrades gracefully (still sends) when this is not set.
18
+ */
19
+ export const EXPO_ACCESS_TOKEN = env.get('EXPO_ACCESS_TOKEN').default('').asString();
20
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,SAAS,CAAC;AAC1B,OAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B,4CAA4C;AAC5C,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AAE/B;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG;KACjC,GAAG,CAAC,mBAAmB,CAAC;KACxB,OAAO,CAAC,sCAAsC,CAAC;KAC/C,QAAQ,EAAE,CAAC;AAEd;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC"}
@@ -0,0 +1,13 @@
1
+ export * from './ExpoPushProvider.js';
2
+ export * from './config.js';
3
+ export type { ExpoPushCredentials } from './ExpoPushProvider.js';
4
+ /**
5
+ * Load-prevention export.
6
+ *
7
+ * Modern bundlers (ESBuild, Vite) tree-shake classes that are only ever
8
+ * instantiated dynamically via MJ's `ClassFactory`. Calling this no-op function
9
+ * from a consuming application forces a static reference to this module so the
10
+ * `@RegisterClass`-decorated {@link ExpoPushProvider} is retained in the bundle.
11
+ */
12
+ export declare function LoadExpoPushProvider(): void;
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,oBAAoB,CAAC;AACnC,cAAc,UAAU,CAAC;AAGzB,YAAY,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAG3C"}
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ // PUBLIC API SURFACE AREA
2
+ export * from './ExpoPushProvider.js';
3
+ export * from './config.js';
4
+ /**
5
+ * Load-prevention export.
6
+ *
7
+ * Modern bundlers (ESBuild, Vite) tree-shake classes that are only ever
8
+ * instantiated dynamically via MJ's `ClassFactory`. Calling this no-op function
9
+ * from a consuming application forces a static reference to this module so the
10
+ * `@RegisterClass`-decorated {@link ExpoPushProvider} is retained in the bundle.
11
+ */
12
+ export function LoadExpoPushProvider() {
13
+ // Intentionally empty — referencing this module prevents tree-shaking removal
14
+ // of the @RegisterClass-decorated ExpoPushProvider.
15
+ }
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,0BAA0B;AAC1B,cAAc,oBAAoB,CAAC;AACnC,cAAc,UAAU,CAAC;AAKzB;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB;IAClC,8EAA8E;IAC9E,oDAAoD;AACtD,CAAC"}
package/package.json CHANGED
@@ -1,10 +1,31 @@
1
1
  {
2
2
  "name": "@memberjunction/communication-expo-push",
3
- "version": "0.0.0",
4
- "description": "OIDC trusted publishing setup package for @memberjunction/communication-expo-push",
5
- "keywords": [
6
- "oidc",
7
- "trusted-publishing",
8
- "setup"
9
- ]
3
+ "type": "module",
4
+ "version": "5.45.0",
5
+ "description": "Expo push notification provider for MemberJunction Communication framework",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist/**/*"
10
+ ],
11
+ "scripts": {
12
+ "build": "tsc && tsc-alias -f",
13
+ "clean": "rimraf dist",
14
+ "test": "vitest run"
15
+ },
16
+ "dependencies": {
17
+ "@memberjunction/communication-types": "5.45.0",
18
+ "@memberjunction/core": "5.45.0",
19
+ "@memberjunction/global": "5.45.0",
20
+ "dotenv": "^17.2.4",
21
+ "env-var": "^7.4.1"
22
+ },
23
+ "devDependencies": {
24
+ "typescript": "^5.9.3",
25
+ "rimraf": "^6.1.2"
26
+ },
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/MemberJunction/MJ"
30
+ }
10
31
  }