@tilli-pro/nudge.js 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Tilli
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # nudge.js
2
+ > A nudge in time saves nine
3
+
4
+ The nudge.js SDK is a lightweight (**ZERO** dependencies) wrapper around [nudge](http://nudge.pro/) and its [APIs](https://app.nudge.net/api/swagger/index.html#/).
5
+
6
+ ## Getting Started
7
+
8
+ The nudge.js SDK clients come in 2 flavors:
9
+
10
+ 1) The fully featured nudge.js client that exposes all API capabilities within nudge
11
+ 2) The "Send" client which specifically only supports sending real-time nudges
12
+
13
+ To use the nudge.js SDK you need either [an API key](https://help.nudge.net/article/38-nudge-api-documentation) (for sending real-time nudges via the "Send" client) or the user credentials used to log into app.nudge.net (all other functionality).
14
+
15
+ > [!TIP]
16
+ > If you're not sure how to get an API key, you can read more here: [Nudge API Documentation](https://help.nudge.net/article/38-nudge-api-documentation)
17
+
18
+ In either case, getting started is the same:
19
+
20
+ ```ts
21
+ import { createSendClient, createClient } from "@tilli-pro/nudge.js";
22
+
23
+ const apiKey = ""; // get this from the nudge dashboard
24
+ const sendClient = createSendClient({ apiKey });
25
+
26
+ // credentials used to login to app.nudge.net
27
+ const authCredentials = {
28
+ email: "",
29
+ password: "",
30
+ };
31
+ const client = createClient({ apiKey, authCredentials });
32
+ ```
33
+
34
+ The clients can be used immediately for sending real-time nudges:
35
+
36
+ ```ts
37
+ sendClient.send({
38
+ nudgeId: "1234" // you can get this from the nudge dashboard,
39
+ recipient: {
40
+ email: "ibrahims@tilli.pro",
41
+ name: "Ibrahim Ali", // OPTIONAL
42
+ },
43
+ options: { // OPTIONAL
44
+ cc: ["michaelv@tilli.pro"] // OPTIONAL
45
+ bcc: ["avdhoots@tilli.pro"] // OPTIONAL
46
+ },
47
+ mergeTags: { // OPTIONAL
48
+ productName: "tilliX",
49
+ },
50
+ files: [ // OPTIONAL
51
+ new File(["test_document_text"], "test_document.txt", { type: "text/plain" }),
52
+ ],
53
+ })
54
+ ```
55
+
56
+ That's it. Your nudge has been sent.
@@ -0,0 +1,47 @@
1
+ import type { NudgeSendClient } from "./send";
2
+ interface NudgeBaseResponse<T> {
3
+ data: T | null;
4
+ code: number;
5
+ errCode: number;
6
+ errors: string[];
7
+ hasErrors: boolean;
8
+ errorGuid: string;
9
+ }
10
+ export interface ClientCreationOpts {
11
+ apiKey: string;
12
+ baseUrl?: string;
13
+ authCredentials: {
14
+ email: string;
15
+ password: string;
16
+ };
17
+ }
18
+ interface NudgeListOpts {
19
+ top?: number;
20
+ orderBy?: "ScheduleOn desc" | "ScheduleOn asc";
21
+ }
22
+ interface NudgeListResponse {
23
+ pageData: {
24
+ id: number;
25
+ nudgeName: string;
26
+ insensitiveName: string;
27
+ description: string | null;
28
+ scheduledOn: string;
29
+ createdOn: string;
30
+ status: number;
31
+ nudgeType: number;
32
+ templateId: number;
33
+ sendWithoutRecipient: boolean;
34
+ projectName: string | null;
35
+ insensitiveProjectName: string | null;
36
+ lastModifiedByUser: string;
37
+ insensitiveLastModifiedByUser: string;
38
+ modifiedDate: string;
39
+ }[];
40
+ totalCount: number;
41
+ errors: number;
42
+ }
43
+ interface NudgeClient extends NudgeSendClient {
44
+ listNudges: (opts?: NudgeListOpts) => Promise<NudgeBaseResponse<NudgeListResponse>>;
45
+ }
46
+ export declare function createClient({ baseUrl, apiKey, authCredentials }: ClientCreationOpts): NudgeClient;
47
+ export {};
package/dist/client.js ADDED
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createClient = createClient;
4
+ const config_1 = require("./config");
5
+ const send_1 = require("./send");
6
+ function createClient({ baseUrl = config_1.BASE_PROD_URL, apiKey, authCredentials }) {
7
+ if (typeof fetch !== "function") {
8
+ throw new Error("fetch must exist to use nudge.js.");
9
+ }
10
+ else if (baseUrl.endsWith("/")) {
11
+ baseUrl = baseUrl.slice(0, -1); // Remove trailing slash if present
12
+ }
13
+ let bearerToken = null;
14
+ const $connect = async () => {
15
+ try {
16
+ const signInResponse = await fetch(`${baseUrl}/Session/SignIn`, {
17
+ method: "POST",
18
+ headers: {
19
+ "Content-Type": "application/json",
20
+ Accept: "application/json"
21
+ },
22
+ body: JSON.stringify({
23
+ email: authCredentials.email,
24
+ password: authCredentials.password,
25
+ keepMeSignedIn: false
26
+ }),
27
+ });
28
+ if (!signInResponse.ok) {
29
+ throw new Error("Failed to sign in to Nudge API.");
30
+ }
31
+ const signInData = (await signInResponse.json());
32
+ if (signInData.hasErrors || !signInData.data || !signInData.data.jwtToken) {
33
+ throw new Error("Failed to sign in to Nudge API: " + signInData.errors.join(", "));
34
+ }
35
+ bearerToken = signInData.data.jwtToken;
36
+ return true;
37
+ }
38
+ catch (e) {
39
+ console.error("Error connecting to Nudge API:", e);
40
+ }
41
+ return false;
42
+ };
43
+ let connectionPromise = null;
44
+ const checkConnected = (fn) => async (...args) => {
45
+ if (!connectionPromise && !bearerToken) {
46
+ connectionPromise = $connect();
47
+ }
48
+ if (connectionPromise) {
49
+ const result = await connectionPromise;
50
+ if (!bearerToken || !result) {
51
+ throw new Error("Not connected to Nudge API.");
52
+ }
53
+ }
54
+ try {
55
+ return fn(...args);
56
+ }
57
+ catch (e) {
58
+ if (e instanceof Error && e.message.includes("Unauthorized")) {
59
+ bearerToken = null;
60
+ }
61
+ throw e;
62
+ }
63
+ };
64
+ console.warn("The Nudge API client is currently undergoing active development. The vast majority of API features are not yet implemented. Please use with caution and report any issues you encounter.");
65
+ return {
66
+ send: (opts) => (0, send_1.sendNudge)({ baseUrl, apiKey }, opts),
67
+ listNudges: checkConnected(async (opts) => {
68
+ const apiUrl = `${baseUrl}/api/EmailNudge/List`;
69
+ const params = new URLSearchParams();
70
+ // TODO: What validations exist for these options?
71
+ if (opts?.top) {
72
+ params.append("top", opts.top.toString());
73
+ }
74
+ if (opts?.orderBy) {
75
+ params.append("orderBy", opts.orderBy);
76
+ }
77
+ const stringParams = params.toString();
78
+ const url = stringParams ? `${apiUrl}?${stringParams}` : apiUrl;
79
+ const listResponse = await fetch(url, {
80
+ method: "GET",
81
+ headers: {
82
+ accept: "application/json",
83
+ authorization: `Bearer ${bearerToken}`,
84
+ }
85
+ });
86
+ return listResponse.json();
87
+ })
88
+ };
89
+ }
@@ -0,0 +1 @@
1
+ export declare const BASE_PROD_URL = "https://app.nudge.net/api";
package/dist/config.js ADDED
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BASE_PROD_URL = void 0;
4
+ exports.BASE_PROD_URL = "https://app.nudge.net/api";
@@ -0,0 +1,3 @@
1
+ export * from "./client";
2
+ export * from "./send";
3
+ export * from "./types";
package/dist/index.js ADDED
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./client"), exports);
18
+ __exportStar(require("./send"), exports);
19
+ __exportStar(require("./types"), exports);
package/dist/send.d.ts ADDED
@@ -0,0 +1,33 @@
1
+ import type { ClientCreationOpts } from "./client";
2
+ export interface NudgeSendOpts {
3
+ nudgeId: string;
4
+ recipient: EmailRecipient;
5
+ options?: {
6
+ cc?: string[];
7
+ bcc?: string[];
8
+ };
9
+ mergeTags?: MergeTag[] | Record<string, string>;
10
+ files?: File[];
11
+ }
12
+ export type NudgeSendResult = {
13
+ success: boolean;
14
+ error?: undefined;
15
+ } | {
16
+ success: false;
17
+ error: unknown;
18
+ };
19
+ export interface NudgeSendClient {
20
+ send(opts: NudgeSendOpts): Promise<NudgeSendResult>;
21
+ }
22
+ export interface EmailRecipient {
23
+ email: string;
24
+ name?: string;
25
+ }
26
+ export interface MergeTag {
27
+ tagName: string;
28
+ tagValue: string;
29
+ }
30
+ export declare function sendNudge(client: SendClientCreationOpts, opts: NudgeSendOpts): Promise<NudgeSendResult>;
31
+ type SendClientCreationOpts = Omit<ClientCreationOpts, "authCredentials">;
32
+ export declare function createSendClient({ baseUrl, apiKey }: SendClientCreationOpts): NudgeSendClient;
33
+ export {};
package/dist/send.js ADDED
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sendNudge = sendNudge;
4
+ exports.createSendClient = createSendClient;
5
+ async function createFileBuffer(file) {
6
+ const buffer = await file.arrayBuffer();
7
+ const uint8Array = new Uint8Array(buffer);
8
+ if (typeof btoa === "function") {
9
+ return await new Blob([buffer]).text();
10
+ }
11
+ else if (typeof Buffer === "function") {
12
+ return Buffer.from(uint8Array).toString("base64");
13
+ }
14
+ else {
15
+ throw new Error("No base64 encoding method available.");
16
+ }
17
+ }
18
+ async function sendNudge(client, opts) {
19
+ const _mergeTags = opts.mergeTags ?? [];
20
+ const mergeTags = Array.isArray(_mergeTags) ? _mergeTags : Object.entries(_mergeTags).map(([tagName, tagValue]) => ({ tagName, tagValue }));
21
+ const emailAttachments = [];
22
+ if (opts.files?.length) {
23
+ const filePromises = opts.files.map(file => {
24
+ return new Promise((resolve, reject) => {
25
+ createFileBuffer(file).then(text => resolve({ content: text, mimeType: file.type, fileName: file.name })).catch(reject);
26
+ });
27
+ });
28
+ emailAttachments.push(...(await Promise.all(filePromises)));
29
+ }
30
+ const result = await fetch(`${client.baseUrl}/Nudge/Send`, {
31
+ method: "POST",
32
+ headers: {
33
+ accept: "application/json",
34
+ "content-type": "application/json",
35
+ authorization: client.apiKey,
36
+ },
37
+ body: JSON.stringify({
38
+ nudgeId: opts.nudgeId,
39
+ toEmailAddress: opts.recipient.email,
40
+ toName: opts.recipient.name,
41
+ mergeTags,
42
+ channel: 0,
43
+ emailCc: opts.options?.cc?.join(","),
44
+ emailBcc: opts.options?.bcc?.join(","),
45
+ emailAttachments
46
+ })
47
+ });
48
+ if (!result.ok) {
49
+ const error = await result.json();
50
+ return { success: false, error };
51
+ }
52
+ const response = await result.json();
53
+ if (response.error) {
54
+ return { success: false, error: response.error };
55
+ }
56
+ return { success: true };
57
+ }
58
+ function createSendClient({ baseUrl = "https://app.nudge.net/api/v2", apiKey }) {
59
+ if (typeof fetch !== "function") {
60
+ throw new Error("fetch must exist to use nudge.js.");
61
+ }
62
+ const client = {
63
+ send: async (opts) => {
64
+ return sendNudge({ baseUrl, apiKey }, opts);
65
+ }
66
+ };
67
+ return client;
68
+ }
@@ -0,0 +1,2 @@
1
+ export type AnyFunction = (...args: any[]) => any;
2
+ export type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@tilli-pro/nudge.js",
3
+ "version": "0.1.0",
4
+ "description": "",
5
+ "main": "dist/index.js",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "package.json"
17
+ ],
18
+ "keywords": [
19
+ "transactional email",
20
+ "email",
21
+ "sms",
22
+ "whatsapp",
23
+ "push notification",
24
+ "nudge"
25
+ ],
26
+ "private": false,
27
+ "author": "Ibrahim Saberi",
28
+ "license": "MIT",
29
+ "devDependencies": {
30
+ "@types/node": "^22.15.21",
31
+ "typescript": "^5.8.3"
32
+ },
33
+ "scripts": {
34
+ "build": "tsc"
35
+ }
36
+ }