@cstar.help/js 0.17.5 → 0.18.1
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 +43 -25
- package/dist/auth/index.cjs +220 -21
- package/dist/auth/index.d.cts +83 -31
- package/dist/auth/index.d.ts +83 -31
- package/dist/auth/index.js +210 -21
- package/dist/chat/index.cjs +134 -22
- package/dist/chat/index.d.cts +32 -4
- package/dist/chat/index.d.ts +32 -4
- package/dist/chat/index.js +134 -22
- package/dist/index.cjs +101 -40
- package/dist/index.d.cts +477 -157
- package/dist/index.d.ts +477 -157
- package/dist/index.js +101 -40
- package/dist/proactive/index.cjs +46 -33
- package/dist/proactive/index.js +45 -34
- package/dist/webhook/index.d.cts +1 -1
- package/dist/webhook/index.d.ts +1 -1
- package/dist/webhook/node.d.cts +1 -1
- package/dist/webhook/node.d.ts +1 -1
- package/dist/{webhookEvents-1N2i2GcQ.d.cts → webhookEvents-Lqpsj935.d.cts} +0 -1
- package/dist/{webhookEvents-1N2i2GcQ.d.ts → webhookEvents-Lqpsj935.d.ts} +0 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -237,7 +237,7 @@ function parseError(statusCode, body, retryAfter) {
|
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
// src/client.ts
|
|
240
|
-
var SDK_VERSION = "0.
|
|
240
|
+
var SDK_VERSION = "0.18.1";
|
|
241
241
|
var DEFAULT_BASE_URL = "https://www.cstar.help";
|
|
242
242
|
var DEFAULT_API_VERSION = "2026-03-01";
|
|
243
243
|
var DEFAULT_MAX_RETRIES = 3;
|
|
@@ -257,7 +257,7 @@ var HttpClient = class {
|
|
|
257
257
|
this.lastMeta = { requestId: null, timestamp: null, pagination: null };
|
|
258
258
|
if (!config || typeof config.apiKey !== "string" || config.apiKey.length === 0) {
|
|
259
259
|
throw new Error(
|
|
260
|
-
"[cStar] CStarClient: apiKey is required. Pass a secret key (sk_live_... / sk_test_...)
|
|
260
|
+
"[cStar] CStarClient: apiKey is required. Pass a secret key (sk_live_... / sk_test_...). Publishable keys (pk_...) cannot read team endpoints; use them with LibraryClient or CommunityClient. See https://cstar.help/developers/quickstart"
|
|
261
261
|
);
|
|
262
262
|
}
|
|
263
263
|
if (typeof config.teamId !== "string" || config.teamId.length === 0) {
|
|
@@ -316,10 +316,12 @@ var HttpClient = class {
|
|
|
316
316
|
const body = options?.body ? JSON.stringify(options.body) : void 0;
|
|
317
317
|
const canRetryAmbiguousFailure = !["POST", "PATCH"].includes(method.toUpperCase()) || !!options?.idempotencyKey;
|
|
318
318
|
let lastError = null;
|
|
319
|
+
const RATE_LIMIT_RETRY_CAP_MS = 6e4;
|
|
320
|
+
let inProgressWaitMs = null;
|
|
319
321
|
for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {
|
|
320
322
|
if (attempt > 0 && lastError) {
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
+
const delay = inProgressWaitMs ?? (lastError instanceof CStarRateLimitError ? Math.min(lastError.retryAfter * 1e3, RATE_LIMIT_RETRY_CAP_MS) : this.getBackoffDelay(attempt));
|
|
324
|
+
inProgressWaitMs = null;
|
|
323
325
|
await this.sleep(delay);
|
|
324
326
|
}
|
|
325
327
|
let response;
|
|
@@ -359,6 +361,14 @@ var HttpClient = class {
|
|
|
359
361
|
if (requestId && errorBody?.error) {
|
|
360
362
|
errorBody.error.request_id = requestId;
|
|
361
363
|
}
|
|
364
|
+
if (response.status === 409 && options?.idempotencyKey && errorBody?.error?.code === "idempotency_key_in_use" && retryAfter !== void 0) {
|
|
365
|
+
lastError = parseError(409, errorBody, retryAfter);
|
|
366
|
+
if (attempt < this.config.maxRetries) {
|
|
367
|
+
inProgressWaitMs = Math.min(retryAfter * 1e3, RATE_LIMIT_RETRY_CAP_MS);
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
throw lastError;
|
|
371
|
+
}
|
|
362
372
|
if (response.status < 500 && response.status !== 429) {
|
|
363
373
|
throw parseError(response.status, errorBody, retryAfter);
|
|
364
374
|
}
|
|
@@ -489,6 +499,8 @@ var CStarRealtime = class {
|
|
|
489
499
|
this.retryTimer = null;
|
|
490
500
|
this._connected = false;
|
|
491
501
|
this.destroyed = false;
|
|
502
|
+
/** The server closed the stream because the API key was revoked or expired. */
|
|
503
|
+
this.revoked = false;
|
|
492
504
|
this.connecting = false;
|
|
493
505
|
/** Short-lived SSE token (exchanged from secret key). */
|
|
494
506
|
this.sseToken = null;
|
|
@@ -564,7 +576,7 @@ var CStarRealtime = class {
|
|
|
564
576
|
}
|
|
565
577
|
/** Start the SSE connection. Called automatically on first handler registration. */
|
|
566
578
|
connect() {
|
|
567
|
-
if (this.eventSource || this.destroyed || this.connecting) return;
|
|
579
|
+
if (this.eventSource || this.destroyed || this.revoked || this.connecting) return;
|
|
568
580
|
if (!this.resolveEventSource()) {
|
|
569
581
|
throw new CStarRealtimeUnavailableError();
|
|
570
582
|
}
|
|
@@ -624,6 +636,7 @@ var CStarRealtime = class {
|
|
|
624
636
|
this.eventSource.close();
|
|
625
637
|
this.eventSource = null;
|
|
626
638
|
}
|
|
639
|
+
if (this.revoked) return;
|
|
627
640
|
if (this.autoReconnect && !this.destroyed && this.retryCount < this.maxRetries) {
|
|
628
641
|
this.scheduleReconnect();
|
|
629
642
|
} else if (this.retryCount >= this.maxRetries) {
|
|
@@ -633,6 +646,9 @@ var CStarRealtime = class {
|
|
|
633
646
|
this.eventSource.onmessage = (event) => {
|
|
634
647
|
this.handleMessage(event);
|
|
635
648
|
};
|
|
649
|
+
this.eventSource.addEventListener("revoked", (event) => {
|
|
650
|
+
this.handleRevoked(event);
|
|
651
|
+
});
|
|
636
652
|
const knownTypes = [
|
|
637
653
|
"ticket.created",
|
|
638
654
|
"ticket.updated",
|
|
@@ -761,6 +777,40 @@ var CStarRealtime = class {
|
|
|
761
777
|
for (const handler of this.connectionHandlers) handler(value);
|
|
762
778
|
}
|
|
763
779
|
}
|
|
780
|
+
handleRevoked(event) {
|
|
781
|
+
if (this.revoked || this.destroyed) return;
|
|
782
|
+
this.revoked = true;
|
|
783
|
+
let body = {};
|
|
784
|
+
try {
|
|
785
|
+
body = JSON.parse(event.data).error ?? {};
|
|
786
|
+
} catch {
|
|
787
|
+
}
|
|
788
|
+
if (this.retryTimer) {
|
|
789
|
+
clearTimeout(this.retryTimer);
|
|
790
|
+
this.retryTimer = null;
|
|
791
|
+
}
|
|
792
|
+
if (this.tokenRefreshTimer) {
|
|
793
|
+
clearTimeout(this.tokenRefreshTimer);
|
|
794
|
+
this.tokenRefreshTimer = null;
|
|
795
|
+
}
|
|
796
|
+
if (this.eventSource) {
|
|
797
|
+
this.eventSource.close();
|
|
798
|
+
this.eventSource = null;
|
|
799
|
+
}
|
|
800
|
+
this.sseToken = null;
|
|
801
|
+
this.sseTokenExpiresAt = 0;
|
|
802
|
+
this.connecting = false;
|
|
803
|
+
this.setConnected(false);
|
|
804
|
+
this.emitError(
|
|
805
|
+
new CStarAuthenticationError(401, {
|
|
806
|
+
type: body.type ?? "authentication_error",
|
|
807
|
+
code: body.code ?? "invalid_api_key",
|
|
808
|
+
message: body.message ?? "This API key was revoked or has expired, so the stream is closed.",
|
|
809
|
+
doc_url: body.doc_url ?? "https://www.cstar.help/developers/errors#invalid_api_key",
|
|
810
|
+
...body.request_id ? { request_id: body.request_id } : {}
|
|
811
|
+
})
|
|
812
|
+
);
|
|
813
|
+
}
|
|
764
814
|
emitError(error) {
|
|
765
815
|
for (const handler of this.errorHandlers) handler(error);
|
|
766
816
|
}
|
|
@@ -1480,11 +1530,17 @@ var SettingsResource = class {
|
|
|
1480
1530
|
async update(params) {
|
|
1481
1531
|
return this.client.request("PATCH", "/settings", { body: params });
|
|
1482
1532
|
}
|
|
1483
|
-
/**
|
|
1533
|
+
/**
|
|
1534
|
+
* Get the chat widget's online hours (outside them the widget shows its
|
|
1535
|
+
* offline message). Not the SLA working hours that SLA deadlines use.
|
|
1536
|
+
*/
|
|
1484
1537
|
async getBusinessHours() {
|
|
1485
1538
|
return this.client.request("GET", "/settings/business-hours");
|
|
1486
1539
|
}
|
|
1487
|
-
/**
|
|
1540
|
+
/**
|
|
1541
|
+
* Update the chat widget's online hours. SLA deadlines don't change:
|
|
1542
|
+
* they follow the SLA working hours in Settings > Business Hours.
|
|
1543
|
+
*/
|
|
1488
1544
|
async updateBusinessHours(params) {
|
|
1489
1545
|
return this.client.request("PATCH", "/settings/business-hours", {
|
|
1490
1546
|
body: params
|
|
@@ -1509,8 +1565,10 @@ var NotificationsResource = class {
|
|
|
1509
1565
|
}
|
|
1510
1566
|
/** List notifications for a user. */
|
|
1511
1567
|
async list(params) {
|
|
1568
|
+
const { unreadOnly, ...query } = params;
|
|
1569
|
+
if (unreadOnly && query.isRead === void 0) query.isRead = false;
|
|
1512
1570
|
return this.client.request("GET", "/notifications", {
|
|
1513
|
-
query
|
|
1571
|
+
query
|
|
1514
1572
|
});
|
|
1515
1573
|
}
|
|
1516
1574
|
/** Get unread notification count for a user. */
|
|
@@ -1584,15 +1642,22 @@ var AttachmentsResource = class {
|
|
|
1584
1642
|
constructor(client) {
|
|
1585
1643
|
this.client = client;
|
|
1586
1644
|
}
|
|
1587
|
-
/** List
|
|
1645
|
+
/** List message files, each with a short-lived signed `url`. */
|
|
1588
1646
|
async list(params) {
|
|
1589
1647
|
return this.client.request("GET", "/attachments", {
|
|
1590
1648
|
query: params
|
|
1591
1649
|
});
|
|
1592
1650
|
}
|
|
1593
|
-
/**
|
|
1594
|
-
|
|
1595
|
-
|
|
1651
|
+
/**
|
|
1652
|
+
* Get a signed download URL, valid for `expiresIn` seconds, for a stored
|
|
1653
|
+
* file. Pass `storagePath` and `bucket` from `list()`.
|
|
1654
|
+
*/
|
|
1655
|
+
async get(storagePath, params) {
|
|
1656
|
+
return this.client.request(
|
|
1657
|
+
"GET",
|
|
1658
|
+
`/attachments/${storagePath}`,
|
|
1659
|
+
params?.bucket ? { query: { bucket: params.bucket } } : void 0
|
|
1660
|
+
);
|
|
1596
1661
|
}
|
|
1597
1662
|
/** Delete an attachment. */
|
|
1598
1663
|
async del(attachmentId) {
|
|
@@ -1608,16 +1673,12 @@ var CustomFieldsResource = class {
|
|
|
1608
1673
|
constructor(client) {
|
|
1609
1674
|
this.client = client;
|
|
1610
1675
|
}
|
|
1611
|
-
/** List custom
|
|
1676
|
+
/** List custom field definitions, optionally for one record type. There is no single-field GET. */
|
|
1612
1677
|
async list(params) {
|
|
1613
1678
|
return this.client.request("GET", "/custom-fields", {
|
|
1614
1679
|
query: params
|
|
1615
1680
|
});
|
|
1616
1681
|
}
|
|
1617
|
-
/** Get a single custom field. */
|
|
1618
|
-
async get(fieldId) {
|
|
1619
|
-
return this.client.request("GET", `/custom-fields/${fieldId}`);
|
|
1620
|
-
}
|
|
1621
1682
|
/** Create a custom field. */
|
|
1622
1683
|
async create(params, options) {
|
|
1623
1684
|
return this.client.request("POST", "/custom-fields", {
|
|
@@ -1652,11 +1713,9 @@ var SlaResource = class {
|
|
|
1652
1713
|
constructor(client) {
|
|
1653
1714
|
this.client = client;
|
|
1654
1715
|
}
|
|
1655
|
-
/** List SLA rules. */
|
|
1656
|
-
async list(
|
|
1657
|
-
return this.client.request("GET", "/sla/rules"
|
|
1658
|
-
query: params
|
|
1659
|
-
});
|
|
1716
|
+
/** List SLA rules in evaluation order. */
|
|
1717
|
+
async list() {
|
|
1718
|
+
return this.client.request("GET", "/sla/rules");
|
|
1660
1719
|
}
|
|
1661
1720
|
/** Create an SLA rule. */
|
|
1662
1721
|
async create(params, options) {
|
|
@@ -1845,25 +1904,28 @@ var TagsResource = class {
|
|
|
1845
1904
|
constructor(client) {
|
|
1846
1905
|
this.client = client;
|
|
1847
1906
|
}
|
|
1848
|
-
/** List
|
|
1907
|
+
/** List tags with usage counts, one row per tag and entity type. */
|
|
1849
1908
|
async list(params) {
|
|
1850
1909
|
return this.client.request("GET", "/tags", {
|
|
1851
1910
|
query: params
|
|
1852
1911
|
});
|
|
1853
1912
|
}
|
|
1854
|
-
/**
|
|
1855
|
-
async
|
|
1913
|
+
/** Replace `fromTag` with `toTag` on every record that carries it. */
|
|
1914
|
+
async merge(params, options) {
|
|
1856
1915
|
return this.client.request("POST", "/tags", {
|
|
1857
|
-
body: {
|
|
1916
|
+
body: { action: "merge", ...params },
|
|
1858
1917
|
idempotencyKey: options?.idempotencyKey
|
|
1859
1918
|
});
|
|
1860
1919
|
}
|
|
1861
|
-
/**
|
|
1862
|
-
async
|
|
1863
|
-
return this.client.request(
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1920
|
+
/** Remove a tag from every record that carries it. */
|
|
1921
|
+
async remove(tag, params) {
|
|
1922
|
+
return this.client.request("DELETE", "/tags", {
|
|
1923
|
+
body: { tag, ...params }
|
|
1924
|
+
});
|
|
1925
|
+
}
|
|
1926
|
+
/** @deprecated Use `remove()`. */
|
|
1927
|
+
async del(tag, params) {
|
|
1928
|
+
return this.remove(tag, params);
|
|
1867
1929
|
}
|
|
1868
1930
|
};
|
|
1869
1931
|
|
|
@@ -2309,7 +2371,7 @@ var ViewsResource = class extends BaseResource {
|
|
|
2309
2371
|
constructor(client) {
|
|
2310
2372
|
super(client, "/views");
|
|
2311
2373
|
}
|
|
2312
|
-
/** Execute a view
|
|
2374
|
+
/** Execute a view and return the filtered results. The server uses the view's entity_type to determine which table to query. */
|
|
2313
2375
|
async execute(viewId, params) {
|
|
2314
2376
|
return this.client.request("GET", `/views/${viewId}/execute`, {
|
|
2315
2377
|
query: params
|
|
@@ -2545,7 +2607,7 @@ var WEBHOOK_EVENT_SAMPLES = {
|
|
|
2545
2607
|
"comment": {
|
|
2546
2608
|
"id": "cmt_01H8X9YZ7E0J3K4M5N6X",
|
|
2547
2609
|
"post_id": "post_01H8X9YZ7E0J3K4M5N6W",
|
|
2548
|
-
"content": "I would love this
|
|
2610
|
+
"content": "I would love this. Bumping for visibility.",
|
|
2549
2611
|
"author_id": "cus_01H8X9YZ7E0J3K4M5N6Q",
|
|
2550
2612
|
"created_at": "2026-04-27T18:55:02.000Z"
|
|
2551
2613
|
}
|
|
@@ -2765,15 +2827,14 @@ var WEBHOOK_EVENT_SAMPLES = {
|
|
|
2765
2827
|
"id": "tkt_01H8X9YZ7E0J3K4M5N6P",
|
|
2766
2828
|
"prefixed_id": "CST-1024",
|
|
2767
2829
|
"title": "Cannot reset password",
|
|
2768
|
-
"status": "
|
|
2830
|
+
"status": "closed",
|
|
2769
2831
|
"priority": "normal",
|
|
2770
|
-
"channel": "widget"
|
|
2771
|
-
"resolved_at": "2026-04-27T19:08:43.000Z"
|
|
2832
|
+
"channel": "widget"
|
|
2772
2833
|
},
|
|
2773
2834
|
"changes": {
|
|
2774
2835
|
"status": {
|
|
2775
2836
|
"from": "open",
|
|
2776
|
-
"to": "
|
|
2837
|
+
"to": "closed"
|
|
2777
2838
|
}
|
|
2778
2839
|
}
|
|
2779
2840
|
},
|
|
@@ -2806,7 +2867,7 @@ var WEBHOOK_EVENT_SAMPLES = {
|
|
|
2806
2867
|
"id": "msg_01H8X9YZ7E0J3K4M5N6R",
|
|
2807
2868
|
"sender": "agent",
|
|
2808
2869
|
"sender_name": "Riley",
|
|
2809
|
-
"content": "I just sent a reset link to your inbox
|
|
2870
|
+
"content": "I just sent a reset link to your inbox. Let me know if it lands.",
|
|
2810
2871
|
"created_at": "2026-04-27T18:55:02.000Z"
|
|
2811
2872
|
},
|
|
2812
2873
|
"customer": {
|
|
@@ -2872,7 +2933,7 @@ var WEBHOOK_EVENT_CATALOG = {
|
|
|
2872
2933
|
"survey.submitted": { label: "Customer Feedback Received", description: "Fired when a customer submits a CSAT survey response", category: "csat" },
|
|
2873
2934
|
"team.preferences_updated": { label: "Team Preferences Updated", description: "Fired when team-wide UX preferences change (e.g., bossBattlesEnabled toggled)", category: "team" },
|
|
2874
2935
|
"ticket.assigned": { label: "Ticket Assigned", description: "Fired when a ticket is assigned to an agent", category: "tickets" },
|
|
2875
|
-
"ticket.closed": { label: "Ticket Closed
|
|
2936
|
+
"ticket.closed": { label: "Ticket Closed", description: "Fired when a ticket's status changes to Closed. Moving a ticket to Resolved sends ticket.updated instead.", category: "tickets" },
|
|
2876
2937
|
"ticket.created": { label: "New Ticket Created", description: "Fired when a new ticket is created via app, widget, API, or email", category: "tickets" },
|
|
2877
2938
|
"ticket.deleted": { label: "Ticket Deleted", description: "Fired when a ticket is deleted via app or API", category: "tickets" },
|
|
2878
2939
|
"ticket.message_added": { label: "New Message on Ticket", description: "Fired when a new message is added to a ticket", category: "tickets" },
|
package/dist/proactive/index.cjs
CHANGED
|
@@ -23,51 +23,64 @@ __export(proactive_exports, {
|
|
|
23
23
|
ProactiveClient: () => ProactiveClient
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(proactive_exports);
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
|
|
27
|
+
// src/proactive/rageClick.ts
|
|
28
|
+
var INTERACTIVE_SELECTOR = 'a[href], button, input, select, textarea, label, summary, [role="button"], [role="link"], [role="checkbox"], [role="radio"], [role="switch"], [role="tab"], [role="menuitem"], [role="option"], [contenteditable="true"], [contenteditable=""]';
|
|
29
|
+
var RAGE_CLICK_THRESHOLD = 3;
|
|
30
|
+
var RAGE_CLICK_WINDOW_MS = 1e3;
|
|
31
|
+
var RAGE_CLICK_RADIUS_PX = 30;
|
|
32
|
+
var RAGE_CLICK_DEBOUNCE_MS = 2e3;
|
|
33
|
+
function describe(el) {
|
|
34
|
+
let context = el.tagName ? "on " + el.tagName.toLowerCase() : "";
|
|
35
|
+
if (typeof el.className === "string") {
|
|
36
|
+
const cls = el.className.split(" ")[0];
|
|
37
|
+
if (cls) context += "." + cls;
|
|
38
|
+
}
|
|
39
|
+
return context;
|
|
40
|
+
}
|
|
41
|
+
function createRageClickDetector(onSignal, doc) {
|
|
28
42
|
let clicks = [];
|
|
29
43
|
let lastFired = 0;
|
|
30
44
|
let handler = null;
|
|
45
|
+
function onClick(e) {
|
|
46
|
+
const now = Date.now();
|
|
47
|
+
const target = e.target;
|
|
48
|
+
if (!target || typeof target.closest !== "function" || target.closest(INTERACTIVE_SELECTOR)) {
|
|
49
|
+
clicks = [];
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
clicks = clicks.filter((c) => now - c.t < RAGE_CLICK_WINDOW_MS && c.target === target);
|
|
53
|
+
clicks.push({ x: e.clientX, y: e.clientY, t: now, target });
|
|
54
|
+
if (clicks.length < RAGE_CLICK_THRESHOLD || now - lastFired <= RAGE_CLICK_DEBOUNCE_MS) return;
|
|
55
|
+
const first = clicks[0];
|
|
56
|
+
const allClose = clicks.every(
|
|
57
|
+
(c) => Math.hypot(first.x - c.x, first.y - c.y) <= RAGE_CLICK_RADIUS_PX
|
|
58
|
+
);
|
|
59
|
+
if (!allClose) return;
|
|
60
|
+
lastFired = now;
|
|
61
|
+
onSignal({
|
|
62
|
+
type: "rage_click",
|
|
63
|
+
confidence: Math.min(1, clicks.length / 5),
|
|
64
|
+
context: `Clicked ${clicks.length} times rapidly ${describe(target)}`,
|
|
65
|
+
timestamp: now
|
|
66
|
+
});
|
|
67
|
+
clicks = [];
|
|
68
|
+
}
|
|
31
69
|
return {
|
|
32
70
|
start() {
|
|
33
|
-
handler =
|
|
34
|
-
|
|
35
|
-
clicks = clicks.filter((c) => now - c.t < 500);
|
|
36
|
-
clicks.push({ x: e.clientX, y: e.clientY, t: now });
|
|
37
|
-
if (clicks.length >= 3 && now - lastFired > 2e3) {
|
|
38
|
-
const first = clicks[0];
|
|
39
|
-
const allClose = clicks.every((c) => {
|
|
40
|
-
const dx = first.x - c.x;
|
|
41
|
-
const dy = first.y - c.y;
|
|
42
|
-
return Math.sqrt(dx * dx + dy * dy) <= 50;
|
|
43
|
-
});
|
|
44
|
-
if (allClose) {
|
|
45
|
-
lastFired = now;
|
|
46
|
-
const el = e.target;
|
|
47
|
-
let context = el?.tagName ? "on " + el.tagName.toLowerCase() : "";
|
|
48
|
-
if (el?.className && typeof el.className === "string") {
|
|
49
|
-
const cls = el.className.split(" ")[0];
|
|
50
|
-
if (cls) context += "." + cls;
|
|
51
|
-
}
|
|
52
|
-
onSignal({
|
|
53
|
-
type: "rage_click",
|
|
54
|
-
confidence: Math.min(1, clicks.length / 5),
|
|
55
|
-
context: `Clicked ${clicks.length} times rapidly ${context}`,
|
|
56
|
-
timestamp: now
|
|
57
|
-
});
|
|
58
|
-
clicks = [];
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
document.addEventListener("click", handler, true);
|
|
71
|
+
handler = onClick;
|
|
72
|
+
(doc ?? document).addEventListener("click", handler, true);
|
|
63
73
|
},
|
|
64
74
|
stop() {
|
|
65
|
-
if (handler) document.removeEventListener("click", handler, true);
|
|
75
|
+
if (handler) (doc ?? document).removeEventListener("click", handler, true);
|
|
66
76
|
handler = null;
|
|
67
77
|
clicks = [];
|
|
68
78
|
}
|
|
69
79
|
};
|
|
70
80
|
}
|
|
81
|
+
|
|
82
|
+
// src/proactive/index.ts
|
|
83
|
+
var DEFAULT_BASE_URL = "https://www.cstar.help";
|
|
71
84
|
function createExitIntentDetector(onSignal) {
|
|
72
85
|
let fired = false;
|
|
73
86
|
let handler = null;
|
package/dist/proactive/index.js
CHANGED
|
@@ -1,49 +1,60 @@
|
|
|
1
|
-
// src/proactive/
|
|
2
|
-
var
|
|
3
|
-
|
|
1
|
+
// src/proactive/rageClick.ts
|
|
2
|
+
var INTERACTIVE_SELECTOR = 'a[href], button, input, select, textarea, label, summary, [role="button"], [role="link"], [role="checkbox"], [role="radio"], [role="switch"], [role="tab"], [role="menuitem"], [role="option"], [contenteditable="true"], [contenteditable=""]';
|
|
3
|
+
var RAGE_CLICK_THRESHOLD = 3;
|
|
4
|
+
var RAGE_CLICK_WINDOW_MS = 1e3;
|
|
5
|
+
var RAGE_CLICK_RADIUS_PX = 30;
|
|
6
|
+
var RAGE_CLICK_DEBOUNCE_MS = 2e3;
|
|
7
|
+
function describe(el) {
|
|
8
|
+
let context = el.tagName ? "on " + el.tagName.toLowerCase() : "";
|
|
9
|
+
if (typeof el.className === "string") {
|
|
10
|
+
const cls = el.className.split(" ")[0];
|
|
11
|
+
if (cls) context += "." + cls;
|
|
12
|
+
}
|
|
13
|
+
return context;
|
|
14
|
+
}
|
|
15
|
+
function createRageClickDetector(onSignal, doc) {
|
|
4
16
|
let clicks = [];
|
|
5
17
|
let lastFired = 0;
|
|
6
18
|
let handler = null;
|
|
19
|
+
function onClick(e) {
|
|
20
|
+
const now = Date.now();
|
|
21
|
+
const target = e.target;
|
|
22
|
+
if (!target || typeof target.closest !== "function" || target.closest(INTERACTIVE_SELECTOR)) {
|
|
23
|
+
clicks = [];
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
clicks = clicks.filter((c) => now - c.t < RAGE_CLICK_WINDOW_MS && c.target === target);
|
|
27
|
+
clicks.push({ x: e.clientX, y: e.clientY, t: now, target });
|
|
28
|
+
if (clicks.length < RAGE_CLICK_THRESHOLD || now - lastFired <= RAGE_CLICK_DEBOUNCE_MS) return;
|
|
29
|
+
const first = clicks[0];
|
|
30
|
+
const allClose = clicks.every(
|
|
31
|
+
(c) => Math.hypot(first.x - c.x, first.y - c.y) <= RAGE_CLICK_RADIUS_PX
|
|
32
|
+
);
|
|
33
|
+
if (!allClose) return;
|
|
34
|
+
lastFired = now;
|
|
35
|
+
onSignal({
|
|
36
|
+
type: "rage_click",
|
|
37
|
+
confidence: Math.min(1, clicks.length / 5),
|
|
38
|
+
context: `Clicked ${clicks.length} times rapidly ${describe(target)}`,
|
|
39
|
+
timestamp: now
|
|
40
|
+
});
|
|
41
|
+
clicks = [];
|
|
42
|
+
}
|
|
7
43
|
return {
|
|
8
44
|
start() {
|
|
9
|
-
handler =
|
|
10
|
-
|
|
11
|
-
clicks = clicks.filter((c) => now - c.t < 500);
|
|
12
|
-
clicks.push({ x: e.clientX, y: e.clientY, t: now });
|
|
13
|
-
if (clicks.length >= 3 && now - lastFired > 2e3) {
|
|
14
|
-
const first = clicks[0];
|
|
15
|
-
const allClose = clicks.every((c) => {
|
|
16
|
-
const dx = first.x - c.x;
|
|
17
|
-
const dy = first.y - c.y;
|
|
18
|
-
return Math.sqrt(dx * dx + dy * dy) <= 50;
|
|
19
|
-
});
|
|
20
|
-
if (allClose) {
|
|
21
|
-
lastFired = now;
|
|
22
|
-
const el = e.target;
|
|
23
|
-
let context = el?.tagName ? "on " + el.tagName.toLowerCase() : "";
|
|
24
|
-
if (el?.className && typeof el.className === "string") {
|
|
25
|
-
const cls = el.className.split(" ")[0];
|
|
26
|
-
if (cls) context += "." + cls;
|
|
27
|
-
}
|
|
28
|
-
onSignal({
|
|
29
|
-
type: "rage_click",
|
|
30
|
-
confidence: Math.min(1, clicks.length / 5),
|
|
31
|
-
context: `Clicked ${clicks.length} times rapidly ${context}`,
|
|
32
|
-
timestamp: now
|
|
33
|
-
});
|
|
34
|
-
clicks = [];
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
document.addEventListener("click", handler, true);
|
|
45
|
+
handler = onClick;
|
|
46
|
+
(doc ?? document).addEventListener("click", handler, true);
|
|
39
47
|
},
|
|
40
48
|
stop() {
|
|
41
|
-
if (handler) document.removeEventListener("click", handler, true);
|
|
49
|
+
if (handler) (doc ?? document).removeEventListener("click", handler, true);
|
|
42
50
|
handler = null;
|
|
43
51
|
clicks = [];
|
|
44
52
|
}
|
|
45
53
|
};
|
|
46
54
|
}
|
|
55
|
+
|
|
56
|
+
// src/proactive/index.ts
|
|
57
|
+
var DEFAULT_BASE_URL = "https://www.cstar.help";
|
|
47
58
|
function createExitIntentDetector(onSignal) {
|
|
48
59
|
let fired = false;
|
|
49
60
|
let handler = null;
|
package/dist/webhook/index.d.cts
CHANGED
package/dist/webhook/index.d.ts
CHANGED
package/dist/webhook/node.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { d as WebhookEvent } from '../webhookEvents-
|
|
1
|
+
import { d as WebhookEvent } from '../webhookEvents-Lqpsj935.cjs';
|
|
2
2
|
import { VerifyOptions } from './index.cjs';
|
|
3
3
|
export { DEFAULT_TOLERANCE_SECONDS, WebhookSignatureVerificationError } from './index.cjs';
|
|
4
4
|
|
package/dist/webhook/node.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { d as WebhookEvent } from '../webhookEvents-
|
|
1
|
+
import { d as WebhookEvent } from '../webhookEvents-Lqpsj935.js';
|
|
2
2
|
import { VerifyOptions } from './index.js';
|
|
3
3
|
export { DEFAULT_TOLERANCE_SECONDS, WebhookSignatureVerificationError } from './index.js';
|
|
4
4
|
|