@meetelise/chat 1.43.34 → 1.43.35

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.
@@ -4,171 +4,236 @@
4
4
  */
5
5
 
6
6
  import { LogType, sendLoggingEvent } from "./analytics";
7
+ import { PROTECTED_NUMBERS } from "./protected-phone-numbers";
8
+
9
+ const isProtectedNumber = (formattedNumber: string): boolean => {
10
+ const digits = formattedNumber
11
+ .replace(/\D/g, "")
12
+ .replace(/^1(\d{10})$/, "$1");
13
+ return PROTECTED_NUMBERS.has(digits);
14
+ };
7
15
 
8
16
  export const insertDNIIntoWebsite = (
9
17
  cleanPhoneNumber: string, // format of 1234567890, +11234567890, or 11234567890
10
18
  orgSlug: string,
11
19
  buildingSlug?: string
12
20
  ): number => {
13
- // Strip any "+1" prefix or leading "1" to ensure we work with the 10-digit number
14
- const cleanedNumber = cleanPhoneNumber
15
- .replace(/\D/g, "") // Remove all non-digits first
16
- .replace(/^1(\d{10})$/, "$1"); // Only strip leading 1 if we have 11 digits total
21
+ try {
22
+ // Strip any "+1" prefix or leading "1" to ensure we work with the 10-digit number
23
+ const cleanedNumber = cleanPhoneNumber
24
+ .replace(/\D/g, "") // Remove all non-digits first
25
+ .replace(/^1(\d{10})$/, "$1"); // Only strip leading 1 if we have 11 digits total
26
+
27
+ const newHrefNumber = `tel:${cleanedNumber}`;
28
+ const newHrefSmsNumber = `sms:${cleanedNumber}`;
29
+ const newWhatsAppNumber = `whatsapp://send?phone=${cleanedNumber}`;
30
+ const newFacetimeNumber = `facetime:${cleanedNumber}`;
31
+ const newFacetimeAudioNumber = `facetime-audio:${cleanedNumber}`;
32
+
33
+ const phoneNumberPattern =
34
+ /(?:\+1[\s.-]?)?(?:\(\d{3}\)[\s.-]?|\d{3}[\s.-])\d{3}[\s.-]?\d{4}/g;
35
+ const hrefNumberPattern =
36
+ /(tel|sms):(?:\+1\s)?(?:(?:\(\d{3}\)\s|\d{3}-)\d{3}-\d{4}|\d{10,11})/g;
37
+
38
+ const whatsappNumberPattern = /whatsapp:\/\/send\?phone=\d+/g;
39
+ const facetimeNumberPattern = /(facetime|facetime-audio):\d+/g;
40
+
41
+ const emergencyPattern = /emergency|emergencia/i;
42
+
43
+ const MAX_EMERGENCY_CHECK_LENGTH = 300; // max length of the emergency text to check (if its an essay, probably not an emergency number)
44
+
45
+ const hasEmergencyText = (text: string | null | undefined): boolean => {
46
+ // example.) https://www.livecherryhilltowers.com/ (the emergency text at the bottom)
47
+ if (!text || text.length > MAX_EMERGENCY_CHECK_LENGTH) return false;
48
+ return emergencyPattern.test(text);
49
+ };
50
+
51
+ const hasEmergencyContext = (element: Element): boolean => {
52
+ if (hasEmergencyText(element.textContent)) {
53
+ return true;
54
+ }
17
55
 
18
- const newHrefNumber = `tel:${cleanedNumber}`;
19
- const newHrefSmsNumber = `sms:${cleanedNumber}`;
20
- const newWhatsAppNumber = `whatsapp://send?phone=${cleanedNumber}`;
21
- const newFacetimeNumber = `facetime:${cleanedNumber}`;
22
- const newFacetimeAudioNumber = `facetime-audio:${cleanedNumber}`;
56
+ if (
57
+ hasEmergencyText(element.previousElementSibling?.textContent) ||
58
+ hasEmergencyText(element.nextElementSibling?.textContent)
59
+ ) {
60
+ return true;
61
+ }
23
62
 
24
- const phoneNumberPattern = /(?:\+1\s)?(?:\(\d{3}\)\s|\d{3}-)\d{3}-\d{4}/g;
25
- const hrefNumberPattern =
26
- /(tel|sms):(?:\+1\s)?(?:(?:\(\d{3}\)\s|\d{3}-)\d{3}-\d{4}|\d{10,11})/g;
63
+ const parent = element.parentElement;
64
+ if (parent) {
65
+ if (
66
+ hasEmergencyText(parent.previousElementSibling?.textContent) ||
67
+ hasEmergencyText(parent.nextElementSibling?.textContent)
68
+ ) {
69
+ return true;
70
+ }
71
+ }
27
72
 
28
- const whatsappNumberPattern = /whatsapp:\/\/send\?phone=\d+/g;
29
- const facetimeNumberPattern = /(facetime|facetime-audio):\d+/g;
73
+ return false;
74
+ };
30
75
 
31
- const allHTMLItems = document.body.getElementsByTagName("*");
76
+ const allHTMLItems = document.body.getElementsByTagName("*");
32
77
 
33
- let foundPhoneNumber = false;
78
+ let foundPhoneNumber = false;
34
79
 
35
- // from a phone format of 1234567890 to (123) 456-7890
36
- const newPhoneNumber = cleanedNumber.replace(
37
- /(\d{3})(\d{3})(\d{4})/,
38
- "($1) $2-$3"
39
- );
80
+ // from a phone format of 1234567890 to (123) 456-7890
81
+ const newPhoneNumber = cleanedNumber.replace(
82
+ /(\d{3})(\d{3})(\d{4})/,
83
+ "($1) $2-$3"
84
+ );
40
85
 
41
- let shouldLogTextInsertionError = false;
42
- let shouldLogHrefInsertionError = false;
86
+ let shouldLogTextInsertionError = false;
87
+ let shouldLogHrefInsertionError = false;
43
88
 
44
- let totalReplacements = 0;
89
+ let totalReplacements = 0;
45
90
 
46
- for (let i = 0; i < allHTMLItems.length; i++) {
47
- if (allHTMLItems[i].nodeType !== Node.ELEMENT_NODE) {
48
- continue;
49
- }
91
+ for (let i = 0; i < allHTMLItems.length; i++) {
92
+ if (allHTMLItems[i].nodeType !== Node.ELEMENT_NODE) {
93
+ continue;
94
+ }
50
95
 
51
- // Handles the actual display phone number
52
- if (
53
- allHTMLItems[i].childNodes.length === 1 &&
54
- allHTMLItems[i].childNodes[0].nodeType == Node.TEXT_NODE
55
- ) {
56
- const oldTextContent = allHTMLItems[i].textContent;
57
- if (!oldTextContent) continue;
58
-
59
- const newTextContent = oldTextContent.replace(
60
- phoneNumberPattern,
61
- (match) => {
62
- // If the matched number already has "+1 ", preserve it; otherwise don't add it
63
- const hasIntlCode = match.startsWith("+1 ");
64
- return hasIntlCode ? `+1 ${newPhoneNumber}` : newPhoneNumber;
65
- }
66
- );
67
- if (oldTextContent !== newTextContent) {
68
- totalReplacements++;
69
- allHTMLItems[i].textContent = newTextContent;
96
+ if (hasEmergencyContext(allHTMLItems[i])) {
97
+ continue;
70
98
  }
71
99
 
72
- foundPhoneNumber = true;
100
+ // Handles the actual display phone number
101
+ if (
102
+ allHTMLItems[i].childNodes.length === 1 &&
103
+ allHTMLItems[i].childNodes[0].nodeType == Node.TEXT_NODE
104
+ ) {
105
+ const oldTextContent = allHTMLItems[i].textContent;
106
+ if (!oldTextContent) continue;
107
+
108
+ const newTextContent = oldTextContent.replace(
109
+ phoneNumberPattern,
110
+ (match) => {
111
+ if (isProtectedNumber(match)) return match;
112
+ const hasIntlCode = match.startsWith("+1 ");
113
+ return hasIntlCode ? `+1 ${newPhoneNumber}` : newPhoneNumber;
114
+ }
115
+ );
116
+ if (oldTextContent !== newTextContent) {
117
+ totalReplacements++;
118
+ allHTMLItems[i].textContent = newTextContent;
119
+ }
73
120
 
74
- if (!shouldLogTextInsertionError) {
75
- shouldLogTextInsertionError = true;
76
- }
77
- }
121
+ foundPhoneNumber = true;
78
122
 
79
- // Handles the href phone number (so when you click the link, it will call the phone number)
80
- if (allHTMLItems[i].hasAttribute("href")) {
81
- const href = allHTMLItems[i].getAttribute("href");
82
- if (!href) {
83
- continue;
123
+ if (!shouldLogTextInsertionError) {
124
+ shouldLogTextInsertionError = true;
125
+ }
84
126
  }
85
127
 
86
- let newHrefValue = "";
87
- let currentPattern = hrefNumberPattern;
88
-
89
- if (href.startsWith("sms:")) {
90
- newHrefValue = newHrefSmsNumber;
91
- } else if (href.startsWith("whatsapp:")) {
92
- newHrefValue = newWhatsAppNumber;
93
- currentPattern = whatsappNumberPattern;
94
- } else if (href.startsWith("facetime:")) {
95
- newHrefValue = newFacetimeNumber;
96
- currentPattern = facetimeNumberPattern;
97
- } else if (href.startsWith("facetime-audio:")) {
98
- newHrefValue = newFacetimeAudioNumber;
99
- currentPattern = facetimeNumberPattern;
100
- } else {
101
- newHrefValue = newHrefNumber;
102
- }
128
+ // Handles the href phone number (so when you click the link, it will call the phone number)
129
+ if (allHTMLItems[i].hasAttribute("href")) {
130
+ const href = allHTMLItems[i].getAttribute("href");
131
+ if (!href) {
132
+ continue;
133
+ }
103
134
 
104
- const newHref = href.replace(currentPattern, newHrefValue);
105
- allHTMLItems[i].setAttribute("href", newHref);
135
+ let newHrefValue = "";
136
+ let currentPattern = hrefNumberPattern;
137
+
138
+ if (href.startsWith("sms:")) {
139
+ newHrefValue = newHrefSmsNumber;
140
+ } else if (href.startsWith("whatsapp:")) {
141
+ newHrefValue = newWhatsAppNumber;
142
+ currentPattern = whatsappNumberPattern;
143
+ } else if (href.startsWith("facetime:")) {
144
+ newHrefValue = newFacetimeNumber;
145
+ currentPattern = facetimeNumberPattern;
146
+ } else if (href.startsWith("facetime-audio:")) {
147
+ newHrefValue = newFacetimeAudioNumber;
148
+ currentPattern = facetimeNumberPattern;
149
+ } else {
150
+ newHrefValue = newHrefNumber;
151
+ }
106
152
 
107
- if (
108
- (href.startsWith("tel:") || href.startsWith("sms:")) &&
109
- newHref === href &&
110
- newHref !== newHrefValue
111
- ) {
112
- // Strip out tel: / sms: prefix and check if we have enough digits to be a phone number
113
- // This is a safety catch for odd formatting that our regex missed
114
- const digits = href.replace(/\D/g, "");
115
- if (digits.length >= 7) {
116
- allHTMLItems[i].setAttribute(
117
- "href",
118
- href.startsWith("tel:") ? newHrefNumber : newHrefSmsNumber
119
- );
153
+ if (isProtectedNumber(href)) continue;
154
+
155
+ const newHref = href.replace(currentPattern, newHrefValue);
156
+ allHTMLItems[i].setAttribute("href", newHref);
157
+
158
+ if (
159
+ (href.startsWith("tel:") || href.startsWith("sms:")) &&
160
+ newHref === href &&
161
+ newHref !== newHrefValue
162
+ ) {
163
+ // Strip out tel: / sms: prefix and check if we have enough digits to be a phone number
164
+ // This is a safety catch for odd formatting that our regex missed
165
+ const digits = href.replace(/\D/g, "");
166
+ if (digits.length >= 7) {
167
+ allHTMLItems[i].setAttribute(
168
+ "href",
169
+ href.startsWith("tel:") ? newHrefNumber : newHrefSmsNumber
170
+ );
171
+ }
120
172
  }
121
- }
122
173
 
123
- if (!shouldLogHrefInsertionError) {
124
- shouldLogHrefInsertionError = true;
174
+ if (!shouldLogHrefInsertionError) {
175
+ shouldLogHrefInsertionError = true;
176
+ }
125
177
  }
126
178
  }
127
- }
128
179
 
129
- // We could make these logs more specific but for now we'll just log the
130
- // if there is even a single error to limit the error logs we throw
180
+ // We could make these logs more specific but for now we'll just log the
181
+ // if there is even a single error to limit the error logs we throw
182
+
183
+ if (shouldLogTextInsertionError) {
184
+ sendLoggingEvent({
185
+ logTitle: "DNI_WEBSITE_INSERTION",
186
+ logData: {
187
+ type: "text-insertion",
188
+ newPhoneNumber,
189
+ website: window.location.href,
190
+ },
191
+ logType: LogType.info,
192
+ buildingSlug,
193
+ orgSlug,
194
+ });
195
+ }
131
196
 
132
- if (shouldLogTextInsertionError) {
133
- sendLoggingEvent({
134
- logTitle: "DNI_WEBSITE_INSERTION",
135
- logData: {
136
- type: "text-insertion",
137
- newPhoneNumber,
138
- website: window.location.href,
139
- },
140
- logType: LogType.info,
141
- buildingSlug,
142
- orgSlug,
143
- });
144
- }
197
+ if (shouldLogHrefInsertionError) {
198
+ sendLoggingEvent({
199
+ logTitle: "DNI_WEBSITE_INSERTION",
200
+ logData: {
201
+ type: "href-insertion",
202
+ newPhoneNumber,
203
+ website: window.location.href,
204
+ },
205
+ logType: LogType.info,
206
+ buildingSlug,
207
+ orgSlug,
208
+ });
209
+ }
145
210
 
146
- if (shouldLogHrefInsertionError) {
147
- sendLoggingEvent({
148
- logTitle: "DNI_WEBSITE_INSERTION",
149
- logData: {
150
- type: "href-insertion",
151
- newPhoneNumber,
152
- website: window.location.href,
153
- },
154
- logType: LogType.info,
155
- buildingSlug,
156
- orgSlug,
157
- });
158
- }
211
+ if (!foundPhoneNumber) {
212
+ sendLoggingEvent({
213
+ logTitle: "DNI_WEBSITE_INSERTION",
214
+ logData: {
215
+ type: "no-phone-number-found",
216
+ website: window.location.href,
217
+ },
218
+ logType: LogType.warn,
219
+ buildingSlug,
220
+ orgSlug,
221
+ });
222
+ }
159
223
 
160
- if (!foundPhoneNumber) {
224
+ return totalReplacements;
225
+ } catch (e) {
161
226
  sendLoggingEvent({
162
227
  logTitle: "DNI_WEBSITE_INSERTION",
163
228
  logData: {
164
- type: "no-phone-number-found",
229
+ type: "insertion-error",
230
+ error: e instanceof Error ? e.message : String(e),
165
231
  website: window.location.href,
166
232
  },
167
- logType: LogType.warn,
233
+ logType: LogType.error,
168
234
  buildingSlug,
169
235
  orgSlug,
170
236
  });
237
+ return 0;
171
238
  }
172
-
173
- return totalReplacements;
174
239
  };
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Phone numbers that must never be replaced with DNI numbers.
3
+ *
4
+ * National emergency/crisis hotlines:
5
+ * - https://telehealth.hhs.gov/patients/additional-resources/emergency-numbers
6
+ * - https://nfound.org/resources/
7
+ * - https://connect.usa.gov/four-important-phone-numbers-you-should-save
8
+ *
9
+ * HUD (Housing and Urban Development):
10
+ * - https://www.hud.gov/contactus
11
+ * - https://www.hud.gov/contactus/fairhousing
12
+ * - https://www.hud.gov/contactus/multifamily-housing-complaint
13
+ * - https://www.hudoig.gov/hotline
14
+ *
15
+ * NJ state helplines & hotlines:
16
+ * - https://www.nj.gov/humanservices/dds/home/helplinesindex.html
17
+ */
18
+
19
+ // https://telehealth.hhs.gov/patients/additional-resources/emergency-numbers
20
+ const HHS_PROTECTED_NUMBERS = [
21
+ "8009855990", // Disaster Distress Helpline
22
+ "8338526262", // National Maternal Mental Health Hotline
23
+ "8002221222", // Poison Control
24
+ "8006624357", // SAMHSA Substance Abuse & Mental Health
25
+ ];
26
+
27
+ // https://nfound.org/resources/
28
+ const NFOUND_PROTECTED_NUMBERS = [
29
+ "8002738255", // National Suicide Prevention Lifeline (legacy)
30
+ "8007997233", // National Domestic Violence Hotline
31
+ "8007874889", // National Domestic Violence TTY
32
+ "8006564673", // RAINN Sexual Assault Hotline
33
+ "8004224453", // Childhelp Child Abuse Hotline
34
+ "8883737888", // National Human Trafficking Hotline
35
+ "8006213362", // FEMA Disaster Relief
36
+ "8009506264", // NAMI Mental Illness
37
+ "8558272736", // National Parent Helpline
38
+ "8008435678", // National Center for Missing & Exploited Children
39
+ "8007862929", // National Runaway Safeline
40
+ "8008523836", // Teen Hotline
41
+ "8007849433", // Suicide Hotline (1-800-SUICIDE)
42
+ "8007729100", // National Abortion Federation
43
+ ];
44
+
45
+ // https://www.hud.gov/contactus
46
+ // https://www.hud.gov/contactus/fairhousing
47
+ // https://www.hud.gov/contactus/multifamily-housing-complaint
48
+ // https://www.hudoig.gov/hotline
49
+ const HUD_PROTECTED_NUMBERS = [
50
+ "8006699777", // Fair Housing Discrimination Hotline
51
+ "8006858470", // Multifamily Housing Complaint Line
52
+ "8004322209", // Multifamily Housing Complaint TTY
53
+ "8003473735", // HUD OIG Fraud/Waste/Abuse Hotline
54
+ "8008778339", // Federal Relay (TTY/ASCII)
55
+ "8009552232", // Public & Indian Housing Customer Service
56
+ "8002255342", // FHA Resource Center
57
+ "8009272891", // Manufactured Housing Programs
58
+ "8003049320", // HUD Disaster Line
59
+ "8007937724", // FHFA OIG
60
+ ];
61
+
62
+ // https://www.nj.gov/humanservices/dds/home/helplinesindex.html
63
+ const NJ_PROTECTED_NUMBERS = [
64
+ "8002382333", // Addictions Hotline of NJ
65
+ "8003353863", // Catastrophic Illness in Children Relief Fund
66
+ "8776858878", // Commission for the Blind & Visually Impaired
67
+ "8003329227", // Child Care Help Line
68
+ "8776554371", // Child Support Hotline
69
+ "8772944357", // Disaster Mental Health
70
+ "8772944356", // Disaster Mental Health TTY
71
+ "6092925760", // Division of Addiction Services
72
+ "6092926735", // Division of Addiction Services TTY
73
+ "8007928339", // Division of the Deaf and Hard of Hearing
74
+ "8008329173", // Division of Developmental Disabilities
75
+ "8882853036", // Division of Disability Services / NJ WorkAbility / TBI Fund
76
+ "6092921210", // Division of Disability Services TTY / TBI Fund TDD
77
+ "8007929773", // Division of Family Development / WorkFirst NJ
78
+ "8003561561", // Division of Medical Assistance / NJ FamilyCare Medicaid
79
+ "8003826717", // Division of Mental Health Services
80
+ "8005727233", // Domestic Violence
81
+ "8009291040", // EITC Federal
82
+ "8888958179", // EITC State
83
+ "8006879512", // Food Stamps
84
+ "8004262537", // Gambling Addiction
85
+ "8773475463", // Good Neighbors
86
+ "8774149251", // Health Benefits Identification Card Unit
87
+ "8005103102", // Low Income Home Energy Assistance
88
+ "8889372835", // Medicaid Fraud and Abuse Hotline
89
+ "8007010710", // NJ FamilyCare
90
+ "8007010720", // NJ FamilyCare TTY
91
+ "8774288844", // NJ Housing Resource Center
92
+ "8007929745", // PAAD
93
+ "8003283838", // PPMD Hotline
94
+ "6092927060", // State Disability Insurance
95
+ "6092928319", // State Disability Insurance TDD
96
+ "8008527899", // NJ Relay
97
+ "8662401347", // Universal Service Fund
98
+ "8009923678", // Adoption Information
99
+ "8776522873", // Child Abuse/Neglect Hotline
100
+ "8776527624", // Child Behavioral Health Services
101
+ "8003313937", // DYFS Action Line
102
+ "8008435437", // Family Help Line / Teen Pregnancy Hotline
103
+ "8776536783", // Foster Care Information
104
+ "8778392339", // Safe Haven for Infants
105
+ "8003676274", // NJ Self Help Group Clearinghouse
106
+ "8006896411", // Oxford House
107
+ ];
108
+
109
+ export const PROTECTED_NUMBERS = new Set([
110
+ ...HHS_PROTECTED_NUMBERS,
111
+ ...NFOUND_PROTECTED_NUMBERS,
112
+ ...HUD_PROTECTED_NUMBERS,
113
+ ...NJ_PROTECTED_NUMBERS,
114
+ ]);