@xapp/stentor-handler-contact-capture 1.80.3
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/lib/ContactCaptureHandler.d.ts +34 -0
- package/lib/ContactCaptureHandler.js +101 -0
- package/lib/ContactCaptureHandler.js.map +1 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +22 -0
- package/lib/index.js.map +1 -0
- package/lib/models.d.ts +87 -0
- package/lib/models.js +4 -0
- package/lib/models.js.map +1 -0
- package/package.json +37 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ContactCaptureHandlerConfig } from "./models";
|
|
2
|
+
/**
|
|
3
|
+
* ContactCaptureHandler manages lead collection from form submissions.
|
|
4
|
+
*
|
|
5
|
+
* When a FORM_SUBMIT action arrives from the chat widget, the result object
|
|
6
|
+
* contains all form field values as key-value pairs in `formSlots`. This handler:
|
|
7
|
+
*
|
|
8
|
+
* 1. Separates configured lead data fields (matched against `leadDataList`) from
|
|
9
|
+
* tracking/extra fields (UTM params, TrustedForm cert URLs, etc.).
|
|
10
|
+
* 2. Calls `sendLead()` to forward the lead and extras to the configured CRM service.
|
|
11
|
+
*/
|
|
12
|
+
export declare class ContactCaptureHandler {
|
|
13
|
+
private config;
|
|
14
|
+
constructor(config?: ContactCaptureHandlerConfig);
|
|
15
|
+
/**
|
|
16
|
+
* Sends a lead to the configured CRM service.
|
|
17
|
+
*
|
|
18
|
+
* TrustedForm fields (`xxTrustedFormCertUrl`, `xxTrustedFormPingUrl`) are
|
|
19
|
+
* passed through in `extras` so downstream integrations (e.g., Boberdoo)
|
|
20
|
+
* can forward them to lead buyers. They do not appear in `ExternalLead.fields`
|
|
21
|
+
* because they are not configured in `leadDataList`.
|
|
22
|
+
*
|
|
23
|
+
* UTM params, click IDs, and `xxTrustedForm*` fields follow the same "extras"
|
|
24
|
+
* pattern used for attribution tracking.
|
|
25
|
+
*
|
|
26
|
+
* @param formSlots - All key-value pairs from the FORM_SUBMIT payload result
|
|
27
|
+
* @param requestAttributes - Top-level request attributes (may also carry UTM/TrustedForm data)
|
|
28
|
+
* @param transcript - Conversation transcript
|
|
29
|
+
*/
|
|
30
|
+
sendLead(formSlots: Record<string, string>, requestAttributes?: Record<string, string>, transcript?: unknown[]): Promise<{
|
|
31
|
+
status: string;
|
|
32
|
+
message?: string;
|
|
33
|
+
}>;
|
|
34
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.ContactCaptureHandler = void 0;
|
|
13
|
+
/*! Copyright (c) 2026, XAPP AI */
|
|
14
|
+
const stentor_logger_1 = require("stentor-logger");
|
|
15
|
+
/**
|
|
16
|
+
* Tracks known "extra" field names that flow through to downstream integrations
|
|
17
|
+
* as `extras` rather than as lead data fields.
|
|
18
|
+
*/
|
|
19
|
+
const EXTRA_FIELD_NAMES = new Set([
|
|
20
|
+
"utm_source",
|
|
21
|
+
"utm_campaign",
|
|
22
|
+
"utm_medium",
|
|
23
|
+
"utm_content",
|
|
24
|
+
"utm_term",
|
|
25
|
+
"gclid",
|
|
26
|
+
"fbclid",
|
|
27
|
+
"rwg_token",
|
|
28
|
+
"xxTrustedFormCertUrl",
|
|
29
|
+
"xxTrustedFormPingUrl"
|
|
30
|
+
]);
|
|
31
|
+
/**
|
|
32
|
+
* ContactCaptureHandler manages lead collection from form submissions.
|
|
33
|
+
*
|
|
34
|
+
* When a FORM_SUBMIT action arrives from the chat widget, the result object
|
|
35
|
+
* contains all form field values as key-value pairs in `formSlots`. This handler:
|
|
36
|
+
*
|
|
37
|
+
* 1. Separates configured lead data fields (matched against `leadDataList`) from
|
|
38
|
+
* tracking/extra fields (UTM params, TrustedForm cert URLs, etc.).
|
|
39
|
+
* 2. Calls `sendLead()` to forward the lead and extras to the configured CRM service.
|
|
40
|
+
*/
|
|
41
|
+
class ContactCaptureHandler {
|
|
42
|
+
constructor(config = {}) {
|
|
43
|
+
this.config = config;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Sends a lead to the configured CRM service.
|
|
47
|
+
*
|
|
48
|
+
* TrustedForm fields (`xxTrustedFormCertUrl`, `xxTrustedFormPingUrl`) are
|
|
49
|
+
* passed through in `extras` so downstream integrations (e.g., Boberdoo)
|
|
50
|
+
* can forward them to lead buyers. They do not appear in `ExternalLead.fields`
|
|
51
|
+
* because they are not configured in `leadDataList`.
|
|
52
|
+
*
|
|
53
|
+
* UTM params, click IDs, and `xxTrustedForm*` fields follow the same "extras"
|
|
54
|
+
* pattern used for attribution tracking.
|
|
55
|
+
*
|
|
56
|
+
* @param formSlots - All key-value pairs from the FORM_SUBMIT payload result
|
|
57
|
+
* @param requestAttributes - Top-level request attributes (may also carry UTM/TrustedForm data)
|
|
58
|
+
* @param transcript - Conversation transcript
|
|
59
|
+
*/
|
|
60
|
+
sendLead(formSlots_1) {
|
|
61
|
+
return __awaiter(this, arguments, void 0, function* (formSlots, requestAttributes = {}, transcript = []) {
|
|
62
|
+
const leadDataList = this.config.leadDataList || [];
|
|
63
|
+
const leadDataNames = new Set(leadDataList.map((s) => s.name));
|
|
64
|
+
const fields = [];
|
|
65
|
+
const extras = {};
|
|
66
|
+
// Iterate all form slot keys and route to either `fields` or `extras`
|
|
67
|
+
for (const [key, value] of Object.entries(formSlots)) {
|
|
68
|
+
if (!value)
|
|
69
|
+
continue;
|
|
70
|
+
if (EXTRA_FIELD_NAMES.has(key)) {
|
|
71
|
+
// Known extra field — pass through as an extra, not a lead field
|
|
72
|
+
extras[key] = value;
|
|
73
|
+
}
|
|
74
|
+
else if (leadDataNames.has(key) || leadDataList.length === 0) {
|
|
75
|
+
fields.push({ name: key, value });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Also pick up TrustedForm and UTM data from top-level request attributes
|
|
79
|
+
// when they are not already present in formSlots (belt-and-suspenders)
|
|
80
|
+
for (const key of EXTRA_FIELD_NAMES) {
|
|
81
|
+
if (requestAttributes[key] && !extras[key]) {
|
|
82
|
+
extras[key] = requestAttributes[key];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (!this.config.crmService) {
|
|
86
|
+
(0, stentor_logger_1.log)().warn("ContactCaptureHandler: no CRM service configured, lead not sent");
|
|
87
|
+
return { status: "Failure", message: "No CRM service configured" };
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
const result = yield this.config.crmService.send({ fields, transcript }, extras);
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
(0, stentor_logger_1.log)().error(`ContactCaptureHandler sendLead() error: ${err instanceof Error ? err.message : String(err)}`);
|
|
95
|
+
return { status: "Failure", message: err instanceof Error ? err.message : String(err) };
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
exports.ContactCaptureHandler = ContactCaptureHandler;
|
|
101
|
+
//# sourceMappingURL=ContactCaptureHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContactCaptureHandler.js","sourceRoot":"","sources":["../src/ContactCaptureHandler.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,kCAAkC;AAClC,mDAAqC;AAIrC;;;GAGG;AACH,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC;IAC9B,YAAY;IACZ,cAAc;IACd,YAAY;IACZ,aAAa;IACb,UAAU;IACV,OAAO;IACP,QAAQ;IACR,WAAW;IACX,sBAAsB;IACtB,sBAAsB;CACzB,CAAC,CAAC;AAEH;;;;;;;;;GASG;AACH,MAAa,qBAAqB;IAG9B,YAAY,SAAsC,EAAE;QAChD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACzB,CAAC;IAED;;;;;;;;;;;;;;OAcG;IACU,QAAQ;6DACjB,SAAiC,EACjC,oBAA4C,EAAE,EAC9C,aAAwB,EAAE;YAE1B,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,IAAI,EAAE,CAAC;YACpD,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;YAE/D,MAAM,MAAM,GAAe,EAAE,CAAC;YAC9B,MAAM,MAAM,GAAe,EAAE,CAAC;YAE9B,sEAAsE;YACtE,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;gBACnD,IAAI,CAAC,KAAK;oBAAE,SAAS;gBAErB,IAAI,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;oBAC7B,iEAAiE;oBACjE,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;gBACxB,CAAC;qBAAM,IAAI,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBAC7D,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;gBACtC,CAAC;YACL,CAAC;YAED,0EAA0E;YAC1E,uEAAuE;YACvE,KAAK,MAAM,GAAG,IAAI,iBAAiB,EAAE,CAAC;gBAClC,IAAI,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;oBACzC,MAAM,CAAC,GAAG,CAAC,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;gBACzC,CAAC;YACL,CAAC;YAED,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;gBAC1B,IAAA,oBAAG,GAAE,CAAC,IAAI,CAAC,iEAAiE,CAAC,CAAC;gBAC9E,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,2BAA2B,EAAE,CAAC;YACvE,CAAC;YAED,IAAI,CAAC;gBACD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,MAAM,CAAC,CAAC;gBACjF,OAAO,MAAM,CAAC;YAClB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACX,IAAA,oBAAG,GAAE,CAAC,KAAK,CAAC,2CAA2C,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;gBAC3G,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;YAC5F,CAAC;QACL,CAAC;KAAA;CACJ;AAlED,sDAkEC"}
|
package/lib/index.d.ts
ADDED
package/lib/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
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
|
+
exports.ContactCaptureHandler = void 0;
|
|
18
|
+
/*! Copyright (c) 2026, XAPP AI */
|
|
19
|
+
var ContactCaptureHandler_1 = require("./ContactCaptureHandler");
|
|
20
|
+
Object.defineProperty(exports, "ContactCaptureHandler", { enumerable: true, get: function () { return ContactCaptureHandler_1.ContactCaptureHandler; } });
|
|
21
|
+
__exportStar(require("./models"), exports);
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,kCAAkC;AAClC,iEAAgE;AAAvD,8HAAA,qBAAqB,OAAA;AAC9B,2CAAyB"}
|
package/lib/models.d.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/*! Copyright (c) 2026, XAPP AI */
|
|
2
|
+
/**
|
|
3
|
+
* A single form field from the FORM_SUBMIT payload.
|
|
4
|
+
*/
|
|
5
|
+
export interface FormSlot {
|
|
6
|
+
name: string;
|
|
7
|
+
value: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Extra data passed to the CRM service alongside the lead fields.
|
|
11
|
+
* Includes tracking params (UTM, click IDs) and TrustedForm cert URLs.
|
|
12
|
+
*/
|
|
13
|
+
export interface LeadExtras {
|
|
14
|
+
/**
|
|
15
|
+
* UTM source tracking parameter (e.g., "google", "facebook")
|
|
16
|
+
*/
|
|
17
|
+
utm_source?: string;
|
|
18
|
+
/**
|
|
19
|
+
* UTM campaign tracking parameter
|
|
20
|
+
*/
|
|
21
|
+
utm_campaign?: string;
|
|
22
|
+
/**
|
|
23
|
+
* UTM medium tracking parameter
|
|
24
|
+
*/
|
|
25
|
+
utm_medium?: string;
|
|
26
|
+
/**
|
|
27
|
+
* UTM content tracking parameter
|
|
28
|
+
*/
|
|
29
|
+
utm_content?: string;
|
|
30
|
+
/**
|
|
31
|
+
* UTM term tracking parameter
|
|
32
|
+
*/
|
|
33
|
+
utm_term?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Google click ID
|
|
36
|
+
*/
|
|
37
|
+
gclid?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Facebook click ID
|
|
40
|
+
*/
|
|
41
|
+
fbclid?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Reserve-with-Google token — indicates the lead originated from a Google Business Profile
|
|
44
|
+
*/
|
|
45
|
+
rwg_token?: string;
|
|
46
|
+
/**
|
|
47
|
+
* TrustedForm certificate URL.
|
|
48
|
+
* Set when the form-widget has TrustedForm enabled.
|
|
49
|
+
* Lead buyers on Boberdoo (and other platforms) use this to claim the certificate.
|
|
50
|
+
*/
|
|
51
|
+
xxTrustedFormCertUrl?: string;
|
|
52
|
+
/**
|
|
53
|
+
* TrustedForm ping URL used to extend certificate retention windows.
|
|
54
|
+
*/
|
|
55
|
+
xxTrustedFormPingUrl?: string;
|
|
56
|
+
[key: string]: string | undefined;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Configuration for a contact capture lead data field.
|
|
60
|
+
*/
|
|
61
|
+
export interface LeadDataSlot {
|
|
62
|
+
/** The form slot name (e.g., "FIRST_NAME", "PHONE") */
|
|
63
|
+
name: string;
|
|
64
|
+
/** Human-readable label */
|
|
65
|
+
label?: string;
|
|
66
|
+
/** Whether this field is required before sending the lead */
|
|
67
|
+
required?: boolean;
|
|
68
|
+
}
|
|
69
|
+
export interface ContactCaptureHandlerConfig {
|
|
70
|
+
/**
|
|
71
|
+
* The list of lead data fields to collect from form slots.
|
|
72
|
+
* Only slots with matching names end up in the ExternalLead.fields array.
|
|
73
|
+
*/
|
|
74
|
+
leadDataList?: LeadDataSlot[];
|
|
75
|
+
/**
|
|
76
|
+
* Optional CRM service to send leads to.
|
|
77
|
+
*/
|
|
78
|
+
crmService?: {
|
|
79
|
+
send(lead: {
|
|
80
|
+
fields: FormSlot[];
|
|
81
|
+
transcript: unknown[];
|
|
82
|
+
}, extras?: LeadExtras): Promise<{
|
|
83
|
+
status: string;
|
|
84
|
+
message?: string;
|
|
85
|
+
}>;
|
|
86
|
+
};
|
|
87
|
+
}
|
package/lib/models.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"models.js","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":";AAAA,kCAAkC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xapp/stentor-handler-contact-capture",
|
|
3
|
+
"license": "Apache-2.0",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"version": "1.80.3",
|
|
8
|
+
"description": "Contact capture handler that routes form submissions to CRM services",
|
|
9
|
+
"types": "lib/index",
|
|
10
|
+
"main": "lib/index",
|
|
11
|
+
"files": [
|
|
12
|
+
"lib"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": "^20 || ^22 || ^24"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/chai": "5.2.3",
|
|
19
|
+
"@types/sinon": "4.3.3",
|
|
20
|
+
"@xapp/config": "0.3.0",
|
|
21
|
+
"chai": "4.5.0",
|
|
22
|
+
"mocha": "11.7.5",
|
|
23
|
+
"sinon": "21.0.0",
|
|
24
|
+
"stentor-logger": "1.72.3",
|
|
25
|
+
"ts-node": "10.9.2",
|
|
26
|
+
"typescript": "5.9.3"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"stentor-logger": "1.x"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsc -d true -p .",
|
|
33
|
+
"clean": "rm -rf ./lib/*",
|
|
34
|
+
"test": "mocha --recursive --timeout 9000 -r ts-node/register \"./src/**/__test__/*.test.ts\""
|
|
35
|
+
},
|
|
36
|
+
"gitHead": "06fa3fc79deca527eab7f75696fae30afa5e1587"
|
|
37
|
+
}
|