waha-shared 1.0.527 → 1.0.529

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.
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SubscribeDripBody = exports.DonorSequenceBody = exports.ContactFormBody = exports.NewsletterSignupBody = exports.DripLanguageId = exports.DRIP_LANGUAGES = exports.DripSequenceId = void 0;
7
+ exports.dripLanguagesWithStatus = dripLanguagesWithStatus;
8
+ const zod_1 = __importDefault(require("zod"));
9
+ const languages_1 = require("../data/languages");
10
+ /** The two onboarding drip audiences a new signup can be enrolled in. */
11
+ exports.DripSequenceId = zod_1.default.enum(['drip_believer', 'drip_seeker']);
12
+ /** Ids of languages whose `status.drip` in languages.json is one of these. */
13
+ function dripLanguagesWithStatus(...statuses) {
14
+ return languages_1.languages
15
+ .filter((language) => statuses.includes(language.status.drip ?? 'notStarted'))
16
+ .map((language) => language.languageId);
17
+ }
18
+ /**
19
+ * Languages the onboarding drip is live in: provisioned in Resend and enrolled
20
+ * into on signup. Driven by `status.drip` in languages.json.
21
+ */
22
+ exports.DRIP_LANGUAGES = dripLanguagesWithStatus('launched');
23
+ exports.DripLanguageId = zod_1.default
24
+ .string()
25
+ .refine((id) => exports.DRIP_LANGUAGES.includes(id), 'Not a launched drip language.');
26
+ /*
27
+ * Request bodies for the website endpoints that write to Resend. The website
28
+ * parses incoming requests with these, and the app types what it sends with
29
+ * them. Auth (a reCAPTCHA token or a Firebase ID token) isn't part of the body
30
+ * schemas; each endpoint checks it separately.
31
+ */
32
+ const Email = zod_1.default.string().trim().pipe(zod_1.default.email());
33
+ const Name = zod_1.default.string().trim().max(200);
34
+ /**
35
+ * Whether the user ticked the newsletter box alongside this signup. Shipped app
36
+ * versions don't send it, and missing means no: the newsletter needs consent.
37
+ */
38
+ const Newsletter = zod_1.default.boolean().optional();
39
+ /** `POST /api/submit-subscribe` (form data): an explicit newsletter signup. */
40
+ exports.NewsletterSignupBody = zod_1.default.object({
41
+ email: Email,
42
+ name: Name.optional(),
43
+ });
44
+ /** `POST /api/submit-contact` (form data): the website's contact form. */
45
+ exports.ContactFormBody = zod_1.default.object({
46
+ email: Email,
47
+ name: Name,
48
+ message: zod_1.default.string().trim().min(1).max(5000),
49
+ });
50
+ /**
51
+ * `POST /api/add-to-donation-sequence` (JSON): starts the donor thank-you
52
+ * drip.
53
+ */
54
+ exports.DonorSequenceBody = zod_1.default.object({
55
+ email: Email,
56
+ name: Name.optional(),
57
+ newsletter: Newsletter,
58
+ });
59
+ /**
60
+ * `POST /api/subscribe-drip` (JSON, Firebase ID token). Any language is
61
+ * accepted so callers don't need to know which ones have a drip; unsupported
62
+ * ones are a no-op.
63
+ */
64
+ exports.SubscribeDripBody = zod_1.default.object({
65
+ sequence: exports.DripSequenceId,
66
+ languageId: zod_1.default.string(),
67
+ firstName: Name.optional(),
68
+ newsletter: Newsletter,
69
+ });
@@ -17,7 +17,6 @@ export declare const SharedUserReduxState: z.ZodObject<{
17
17
  }, z.core.$strip>>;
18
18
  phoneNumber: z.ZodOptional<z.ZodString>;
19
19
  isInAGroup: z.ZodOptional<z.ZodBoolean>;
20
- isSubscribedToEmailUpdates: z.ZodOptional<z.ZodBoolean>;
21
20
  hasDonated: z.ZodOptional<z.ZodBoolean>;
22
21
  }, z.core.$strip>;
23
22
  /**
@@ -33,6 +32,7 @@ export declare const DeviceUserReduxState: z.ZodObject<{
33
32
  engagementScore: z.ZodOptional<z.ZodNumber>;
34
33
  finishedOnboarding: z.ZodOptional<z.ZodNumber>;
35
34
  firstOpen: z.ZodOptional<z.ZodNumber>;
35
+ newsletterOptIn: z.ZodOptional<z.ZodBoolean>;
36
36
  }, z.core.$strip>;
37
37
  export declare const UserReduxState: z.ZodObject<{
38
38
  pastLocations: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -44,6 +44,7 @@ export declare const UserReduxState: z.ZodObject<{
44
44
  engagementScore: z.ZodOptional<z.ZodNumber>;
45
45
  finishedOnboarding: z.ZodOptional<z.ZodNumber>;
46
46
  firstOpen: z.ZodOptional<z.ZodNumber>;
47
+ newsletterOptIn: z.ZodOptional<z.ZodBoolean>;
47
48
  email: z.ZodOptional<z.ZodEmail>;
48
49
  location: z.ZodOptional<z.ZodObject<{
49
50
  city: z.ZodOptional<z.ZodString>;
@@ -52,7 +53,6 @@ export declare const UserReduxState: z.ZodObject<{
52
53
  }, z.core.$strip>>;
53
54
  phoneNumber: z.ZodOptional<z.ZodString>;
54
55
  isInAGroup: z.ZodOptional<z.ZodBoolean>;
55
- isSubscribedToEmailUpdates: z.ZodOptional<z.ZodBoolean>;
56
56
  hasDonated: z.ZodOptional<z.ZodBoolean>;
57
57
  }, z.core.$strip>;
58
58
  export type UserReduxState = z.infer<typeof UserReduxState>;
@@ -79,6 +79,7 @@ export declare const DeviceInfo: z.ZodObject<{
79
79
  engagementScore: z.ZodOptional<z.ZodNumber>;
80
80
  finishedOnboarding: z.ZodOptional<z.ZodNumber>;
81
81
  firstOpen: z.ZodOptional<z.ZodNumber>;
82
+ newsletterOptIn: z.ZodOptional<z.ZodBoolean>;
82
83
  phoneLocation: z.ZodOptional<z.ZodString>;
83
84
  pushToken: z.ZodOptional<z.ZodString>;
84
85
  notificationPermission: z.ZodOptional<z.ZodEnum<{
@@ -107,6 +108,7 @@ export declare const DeviceInfoFields: {
107
108
  engagementScore: "engagementScore";
108
109
  finishedOnboarding: "finishedOnboarding";
109
110
  firstOpen: "firstOpen";
111
+ newsletterOptIn: "newsletterOptIn";
110
112
  phoneLocation: "phoneLocation";
111
113
  pushToken: "pushToken";
112
114
  notificationPermission: "notificationPermission";
@@ -128,7 +130,6 @@ export declare const WahaUser: z.ZodObject<{
128
130
  }, z.core.$strip>>;
129
131
  phoneNumber: z.ZodOptional<z.ZodString>;
130
132
  isInAGroup: z.ZodOptional<z.ZodBoolean>;
131
- isSubscribedToEmailUpdates: z.ZodOptional<z.ZodBoolean>;
132
133
  hasDonated: z.ZodOptional<z.ZodBoolean>;
133
134
  wahaUserId: z.ZodString;
134
135
  name: z.ZodOptional<z.ZodString>;
@@ -151,7 +152,6 @@ export declare const WahaUserFields: {
151
152
  location: "location";
152
153
  phoneNumber: "phoneNumber";
153
154
  isInAGroup: "isInAGroup";
154
- isSubscribedToEmailUpdates: "isSubscribedToEmailUpdates";
155
155
  hasDonated: "hasDonated";
156
156
  lastUpdate: "lastUpdate";
157
157
  hasAccount: "hasAccount";
@@ -244,7 +244,6 @@ export declare const LegacyWahaUser: z.ZodObject<{
244
244
  time: z.ZodNumber;
245
245
  content: z.ZodEnum<{
246
246
  lesson: "lesson";
247
- note: "note";
248
247
  lessonLink: "lessonLink";
249
248
  setLink: "setLink";
250
249
  app: "app";
@@ -264,6 +263,7 @@ export declare const LegacyWahaUser: z.ZodObject<{
264
263
  scriptureAudio: "scriptureAudio";
265
264
  inviteGroup: "inviteGroup";
266
265
  offlineVersionLink: "offlineVersionLink";
266
+ note: "note";
267
267
  customSet: "customSet";
268
268
  microLesson: "microLesson";
269
269
  }>;
@@ -318,7 +318,6 @@ export declare const LegacyWahaUserFields: {
318
318
  location: "location";
319
319
  phoneNumber: "phoneNumber";
320
320
  isInAGroup: "isInAGroup";
321
- isSubscribedToEmailUpdates: "isSubscribedToEmailUpdates";
322
321
  hasDonated: "hasDonated";
323
322
  pastLocations: "pastLocations";
324
323
  utm: "utm";
@@ -337,6 +336,7 @@ export declare const LegacyWahaUserFields: {
337
336
  sharingScore: "sharingScore";
338
337
  isAdvocate: "isAdvocate";
339
338
  isCCWorker: "isCCWorker";
339
+ isSubscribedToEmailUpdates: "isSubscribedToEmailUpdates";
340
340
  howFamiliarWithBible: "howFamiliarWithBible";
341
341
  followingFor2Years: "followingFor2Years";
342
342
  notificationsClicked: "notificationsClicked";
@@ -22,7 +22,6 @@ exports.SharedUserReduxState = zod_1.default
22
22
  location: location_1.WahaUserLocation,
23
23
  phoneNumber: zod_1.default.string(),
24
24
  isInAGroup: zod_1.default.boolean(),
25
- isSubscribedToEmailUpdates: zod_1.default.boolean(),
26
25
  hasDonated: zod_1.default.boolean(),
27
26
  })
28
27
  .partial();
@@ -36,6 +35,11 @@ exports.DeviceUserReduxState = zod_1.default
36
35
  engagementScore: zod_1.default.number(),
37
36
  finishedOnboarding: zod_1.default.number(),
38
37
  firstOpen: zod_1.default.number(),
38
+ /**
39
+ * Whether the newsletter box was ticked on the latest sign-in attempt.
40
+ * Kept here so the choice survives the trip out to a magic link and back.
41
+ */
42
+ newsletterOptIn: zod_1.default.boolean(),
39
43
  })
40
44
  .partial();
41
45
  exports.UserReduxState = zod_1.default.object({
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "waha-shared",
3
- "version": "1.0.527",
3
+ "version": "1.0.529",
4
4
  "author": "Waha",
5
5
  "dependencies": {
6
6
  "@types/signale": "^1.4.7",
7
+ "resend": "^6.27.0",
7
8
  "signale": "^1.4.0"
8
9
  },
9
10
  "peerDependencies": {
@@ -28,7 +29,7 @@
28
29
  "license": "MIT",
29
30
  "scripts": {
30
31
  "build": "npx tsc && npx copyfiles \"data/**/*.json\" dist",
31
- "update": "npm run build && REGISTRY_VERSION=$(npm view waha-shared version) && npm version $REGISTRY_VERSION --no-git-tag-version --allow-same-version && npm version patch --no-git-tag-version && git add . && (git commit -m 'chore: build waha-shared' || true) && bash -c 'npm publish --access public --registry https://registry.npmjs.org --//registry.npmjs.org/:_authToken=$(grep NPM_TOKEN= ../.env | cut -d \"=\" -f2-)'",
32
- "updateManual": "(npm whoami || npm login) && npm run build && REGISTRY_VERSION=$(npm view waha-shared version) && npm version $REGISTRY_VERSION --no-git-tag-version --allow-same-version && npm version patch --no-git-tag-version && git add dist package.json && (git commit -m 'chore: build waha-shared' || true) && npm publish"
32
+ "update": "bash publish.sh",
33
+ "update:preview": "bash publish.sh --preview"
33
34
  }
34
35
  }
@@ -1,26 +0,0 @@
1
- declare const listNumbers: {
2
- newsletter: number;
3
- volunteer: number;
4
- 'dm-course': number;
5
- 'in-app-donation-sequence': number;
6
- };
7
- /**
8
- * The ActiveCampaign API URL that looks up a contact by email. The email is
9
- * encoded, since a raw `+` in a query string reads as a space and
10
- * `name+tag@x.com` would never match.
11
- */
12
- export declare function contactLookupUrl(apiUrl: string, email: string): string;
13
- export declare function addToACList({ baseUrl, email, list, token, name, }: {
14
- token: string;
15
- baseUrl: string;
16
- email: string;
17
- list: keyof typeof listNumbers;
18
- name?: string;
19
- }): Promise<void>;
20
- export declare function getContactsInList({ baseUrl, limit, list, token, }: {
21
- token: string;
22
- baseUrl: string;
23
- list: keyof typeof listNumbers;
24
- limit?: number;
25
- }): Promise<string[]>;
26
- export {};
@@ -1,119 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.contactLookupUrl = contactLookupUrl;
7
- exports.addToACList = addToACList;
8
- exports.getContactsInList = getContactsInList;
9
- const signale_1 = __importDefault(require("signale"));
10
- const zod_1 = require("zod");
11
- const listNumbers = {
12
- newsletter: 3,
13
- volunteer: 5,
14
- 'dm-course': 9,
15
- 'in-app-donation-sequence': 17,
16
- };
17
- /**
18
- * The ActiveCampaign API URL that looks up a contact by email. The email is
19
- * encoded, since a raw `+` in a query string reads as a space and
20
- * `name+tag@x.com` would never match.
21
- */
22
- function contactLookupUrl(apiUrl, email) {
23
- return `${apiUrl}/contacts?email=${encodeURIComponent(email)}`;
24
- }
25
- /** The part of an ActiveCampaign list-contacts response that's read. */
26
- const ContactsPage = zod_1.z.object({
27
- contacts: zod_1.z.array(zod_1.z.object({ id: zod_1.z.string(), email: zod_1.z.string() })).optional(),
28
- });
29
- /** The part of an ActiveCampaign contact lookup response that's read. */
30
- const ContactLookup = zod_1.z.object({
31
- contacts: zod_1.z.array(zod_1.z.object({ id: zod_1.z.string() })),
32
- });
33
- /** The part of an ActiveCampaign create-contact response that's read. */
34
- const CreatedContact = zod_1.z.object({ contact: zod_1.z.object({ id: zod_1.z.string() }) });
35
- async function addToACList({ baseUrl, email, list, token, name, }) {
36
- const headers = { 'Api-Token': token, 'Content-Type': 'application/json' };
37
- const url = `${baseUrl}/api/3`;
38
- const findContact = await fetch(contactLookupUrl(url, email), {
39
- method: 'GET',
40
- headers,
41
- });
42
- const findContactJson = await findContact.json();
43
- if (!findContact.ok) {
44
- throw new Error(`Failed to retrieve contact with ${email}. ${JSON.stringify(findContactJson)}`);
45
- }
46
- const [existingContact] = ContactLookup.parse(findContactJson).contacts;
47
- let contactId;
48
- if (existingContact) {
49
- contactId = existingContact.id;
50
- }
51
- else {
52
- const createContact = await fetch(`${url}/contacts`, {
53
- method: 'POST',
54
- headers,
55
- body: JSON.stringify({
56
- contact: { email, name },
57
- }),
58
- });
59
- const createContactJson = await createContact.json();
60
- if (!createContact.ok) {
61
- throw new Error(`Failed to create new contact with ${email}. ${JSON.stringify(createContactJson)}`);
62
- }
63
- signale_1.default.success(`Created new AC contact with email ${email}.`);
64
- contactId = CreatedContact.parse(createContactJson).contact.id;
65
- }
66
- const subscribe = await fetch(`${url}/contactLists`, {
67
- method: 'POST',
68
- headers: { 'Api-Token': token, 'Content-Type': 'application/json' },
69
- body: JSON.stringify({
70
- contactList: {
71
- list: listNumbers[list],
72
- status: 1,
73
- contact: contactId,
74
- },
75
- }),
76
- });
77
- const subscribeJson = await subscribe.json();
78
- if (!subscribe.ok) {
79
- throw new Error(`Failed to add contact to list ${list}. Response: ${JSON.stringify(subscribeJson)}`);
80
- }
81
- signale_1.default.success(`${email} added to AC list ${list}.`);
82
- }
83
- async function getContactsInList({ baseUrl, limit = 100, list, token, }) {
84
- const headers = { 'Api-Token': token, 'Content-Type': 'application/json' };
85
- const url = `${baseUrl}/api/3`;
86
- const allEmails = [];
87
- let lastContactId = null;
88
- let hasMoreContacts = true;
89
- while (hasMoreContacts) {
90
- let requestUrl = `${url}/contacts?listid=${listNumbers[list]}&orders[id]=ASC&limit=${limit}`;
91
- if (lastContactId) {
92
- requestUrl += `&id_greater=${lastContactId}`;
93
- }
94
- const response = await fetch(requestUrl, {
95
- method: 'GET',
96
- headers,
97
- });
98
- const data = await response.json();
99
- if (!response.ok) {
100
- throw new Error(`Failed to retrieve contacts in list ${list}. ${JSON.stringify(data)}`);
101
- }
102
- const contacts = ContactsPage.parse(data).contacts || [];
103
- if (contacts.length === 0) {
104
- hasMoreContacts = false;
105
- }
106
- else {
107
- // Add emails from this batch
108
- const emails = contacts.map((contact) => contact.email);
109
- allEmails.push(...emails);
110
- // Update the last contact ID for next iteration
111
- lastContactId = contacts[contacts.length - 1].id;
112
- // If we got fewer contacts than the limit, we've reached the end
113
- if (contacts.length < limit) {
114
- hasMoreContacts = false;
115
- }
116
- }
117
- }
118
- return allEmails;
119
- }