sdk-garden-js 0.1.1 → 0.2.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/README.md +22 -1
- package/dist/index.d.mts +27 -1
- package/dist/index.d.ts +27 -1
- package/dist/index.js +97 -3
- package/dist/index.mjs +97 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,14 +61,35 @@ Requires Node.js 18+ (for the global `fetch`) or any modern browser.
|
|
|
61
61
|
| `batchSize` | `number` | `10` | Maximum events per request, from 1 to 100; a full batch is sent right away |
|
|
62
62
|
| `flushInterval` | `number` | `5000` | Milliseconds between sends of whatever is queued, at least 100 |
|
|
63
63
|
| `startedAt` | `number` | module load time | `Date.now()` when your SDK began initializing; reported as `time_to_init_ms` for the dashboard's init timing |
|
|
64
|
+
| `methods` | `string[]` | `[]` | Public methods of your SDK that are not wrapped with `trackMethod()`, so the dashboard's API Surface panel can show them when nobody calls them |
|
|
65
|
+
| `customer` | `{ id: string; name?: string }` | _(none)_ | The app or customer account this session runs for (see [Customers](#customers)) |
|
|
66
|
+
|
|
67
|
+
### API surface
|
|
68
|
+
|
|
69
|
+
Every name passed to `trackMethod()` is declared as part of your SDK's API surface, once per session (in the `sdk_initialized` event, or in one `api_surface` event for methods wrapped after `init()`). The dashboard lists those methods next to the ones people call, so the methods nobody uses show up with zero calls. Add methods you don't wrap with the `methods` option. Up to 500 names are declared.
|
|
70
|
+
|
|
71
|
+
### Customers
|
|
72
|
+
|
|
73
|
+
Tell SDK Garden which of your customers a session belongs to, and the dashboard's Customers page shows who is on an old version, who still calls a deprecated method and who hits errors, so you know who to contact.
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
// When your SDK knows which account it runs for, for example after resolving its API key:
|
|
77
|
+
SdkGarden.setCustomer({ id: account.id, name: account.displayName });
|
|
78
|
+
|
|
79
|
+
// Or at init, if you already know it:
|
|
80
|
+
SdkGarden.init({ apiKey: 'sgk_...', sdkName: 'my-sdk', sdkVersion: '1.0.0', customer: { id: 'acct_123' } });
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Use an ID from your own system, such as an account, project or app ID, and optionally a display name. Nothing is attributed unless you call this. Don't pass email addresses or other personal data: an ID that contains an email address is ignored, and so is a name that contains one. IDs are cut to 128 characters and names to 200. `setCustomer(null)` stops attributing; the customer lasts until the next `init()` or `shutdown()`.
|
|
64
84
|
|
|
65
85
|
## What gets sent
|
|
66
86
|
|
|
67
87
|
Each event carries your SDK's name and version, an event type, a random session ID created when `init()` runs, and a timestamp. Depending on the event:
|
|
68
88
|
|
|
69
|
-
- **`sdk_initialized`**: runtime (`node` + Node version, or `browser` + screen/viewport size, pixel ratio, timezone and language) and
|
|
89
|
+
- **`sdk_initialized`**: runtime (`node` + Node version, or `browser` + screen/viewport size, pixel ratio, timezone and language), `time_to_init_ms`, and the names of your SDK's methods (see [API surface](#api-surface)).
|
|
70
90
|
- **`method_called` / `method_success`**: method name, duration, sync/async, and the *number* of arguments.
|
|
71
91
|
- **`sdk_error`**: error type and message (email addresses are redacted) plus the method and duration.
|
|
92
|
+
- **If you set a customer**, every event carries its ID and name (see [Customers](#customers)).
|
|
72
93
|
- **In browsers**, every event also carries the page's origin (for example `https://shop.example.com`), never the full URL, path, query string or referrer.
|
|
73
94
|
|
|
74
95
|
Never sent: argument or return values, request or response bodies, cookies or stack traces. Error messages go out as they are apart from email addresses, so anything your SDK puts in an error message, such as a hostname, a URL or an ID, goes with it. Nothing is sent when `disabled: true`.
|
package/dist/index.d.mts
CHANGED
|
@@ -46,6 +46,27 @@ interface SdkGardenConfig {
|
|
|
46
46
|
* Default: when sdk-garden-js was first loaded.
|
|
47
47
|
*/
|
|
48
48
|
startedAt?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Names of your SDK's public methods that are not wrapped with
|
|
51
|
+
* trackMethod(). Sent once per session with every name passed to
|
|
52
|
+
* trackMethod(), so the dashboard can list the methods nobody calls.
|
|
53
|
+
*/
|
|
54
|
+
methods?: string[];
|
|
55
|
+
/**
|
|
56
|
+
* The app or customer account this session runs for, so the dashboard can
|
|
57
|
+
* show which customers are on old versions, call deprecated methods or hit
|
|
58
|
+
* errors. Use an ID from your own system (an account or project ID), never
|
|
59
|
+
* an email address or other personal data. Can also be set later with
|
|
60
|
+
* setCustomer(). Default: none.
|
|
61
|
+
*/
|
|
62
|
+
customer?: SdkGardenCustomer;
|
|
63
|
+
}
|
|
64
|
+
/** The app or customer account a session is attributed to (see setCustomer()). */
|
|
65
|
+
interface SdkGardenCustomer {
|
|
66
|
+
/** An opaque ID from your system, up to 128 characters, e.g. "acct_123". */
|
|
67
|
+
id: string;
|
|
68
|
+
/** A display name for the dashboard, up to 200 characters, e.g. "Acme Corp". */
|
|
69
|
+
name?: string;
|
|
49
70
|
}
|
|
50
71
|
/**
|
|
51
72
|
* Class method decorator returned by SdkGarden.trackMethod(name), for Stage 3
|
|
@@ -74,6 +95,11 @@ interface SdkGardenObject {
|
|
|
74
95
|
/** Wrapper form — pass an existing function, receive the instrumented version. */
|
|
75
96
|
trackMethod<T extends (...args: any[]) => any>(methodName: string, fn: T): T;
|
|
76
97
|
trackError(error: Error, context?: Record<string, unknown>): void;
|
|
98
|
+
/**
|
|
99
|
+
* Attribute this session's events from now on to a customer, or pass null
|
|
100
|
+
* to stop. No-op before init().
|
|
101
|
+
*/
|
|
102
|
+
setCustomer(customer: SdkGardenCustomer | null): void;
|
|
77
103
|
/**
|
|
78
104
|
* Send every queued event now. Resolves once they (and any requests already
|
|
79
105
|
* in flight) have finished, or after `timeoutMs` (default 2000), whichever
|
|
@@ -114,4 +140,4 @@ interface SdkGardenObject {
|
|
|
114
140
|
*/
|
|
115
141
|
declare const SdkGarden: SdkGardenObject;
|
|
116
142
|
|
|
117
|
-
export { type SdkGardenConfig, type SdkMethodDecorator, SdkGarden as default };
|
|
143
|
+
export { type SdkGardenConfig, type SdkGardenCustomer, type SdkMethodDecorator, SdkGarden as default };
|
package/dist/index.d.ts
CHANGED
|
@@ -46,6 +46,27 @@ interface SdkGardenConfig {
|
|
|
46
46
|
* Default: when sdk-garden-js was first loaded.
|
|
47
47
|
*/
|
|
48
48
|
startedAt?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Names of your SDK's public methods that are not wrapped with
|
|
51
|
+
* trackMethod(). Sent once per session with every name passed to
|
|
52
|
+
* trackMethod(), so the dashboard can list the methods nobody calls.
|
|
53
|
+
*/
|
|
54
|
+
methods?: string[];
|
|
55
|
+
/**
|
|
56
|
+
* The app or customer account this session runs for, so the dashboard can
|
|
57
|
+
* show which customers are on old versions, call deprecated methods or hit
|
|
58
|
+
* errors. Use an ID from your own system (an account or project ID), never
|
|
59
|
+
* an email address or other personal data. Can also be set later with
|
|
60
|
+
* setCustomer(). Default: none.
|
|
61
|
+
*/
|
|
62
|
+
customer?: SdkGardenCustomer;
|
|
63
|
+
}
|
|
64
|
+
/** The app or customer account a session is attributed to (see setCustomer()). */
|
|
65
|
+
interface SdkGardenCustomer {
|
|
66
|
+
/** An opaque ID from your system, up to 128 characters, e.g. "acct_123". */
|
|
67
|
+
id: string;
|
|
68
|
+
/** A display name for the dashboard, up to 200 characters, e.g. "Acme Corp". */
|
|
69
|
+
name?: string;
|
|
49
70
|
}
|
|
50
71
|
/**
|
|
51
72
|
* Class method decorator returned by SdkGarden.trackMethod(name), for Stage 3
|
|
@@ -74,6 +95,11 @@ interface SdkGardenObject {
|
|
|
74
95
|
/** Wrapper form — pass an existing function, receive the instrumented version. */
|
|
75
96
|
trackMethod<T extends (...args: any[]) => any>(methodName: string, fn: T): T;
|
|
76
97
|
trackError(error: Error, context?: Record<string, unknown>): void;
|
|
98
|
+
/**
|
|
99
|
+
* Attribute this session's events from now on to a customer, or pass null
|
|
100
|
+
* to stop. No-op before init().
|
|
101
|
+
*/
|
|
102
|
+
setCustomer(customer: SdkGardenCustomer | null): void;
|
|
77
103
|
/**
|
|
78
104
|
* Send every queued event now. Resolves once they (and any requests already
|
|
79
105
|
* in flight) have finished, or after `timeoutMs` (default 2000), whichever
|
|
@@ -114,4 +140,4 @@ interface SdkGardenObject {
|
|
|
114
140
|
*/
|
|
115
141
|
declare const SdkGarden: SdkGardenObject;
|
|
116
142
|
|
|
117
|
-
export { type SdkGardenConfig, type SdkMethodDecorator, SdkGarden as default };
|
|
143
|
+
export { type SdkGardenConfig, type SdkGardenCustomer, type SdkMethodDecorator, SdkGarden as default };
|
package/dist/index.js
CHANGED
|
@@ -26,6 +26,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
26
26
|
var _config = null;
|
|
27
27
|
var _queue = [];
|
|
28
28
|
var _sessionId = "";
|
|
29
|
+
var _customer = null;
|
|
29
30
|
var _flushTimer = null;
|
|
30
31
|
var _exiting = false;
|
|
31
32
|
var _exitDrains = 0;
|
|
@@ -37,6 +38,9 @@ var _exitTurnCount = 0;
|
|
|
37
38
|
var _exitProbe = null;
|
|
38
39
|
var _exitProbeDueAt = Infinity;
|
|
39
40
|
var _pending = /* @__PURE__ */ new Set();
|
|
41
|
+
var _registered = /* @__PURE__ */ new Set();
|
|
42
|
+
var _undeclared = [];
|
|
43
|
+
var _declareScheduled = false;
|
|
40
44
|
var DEFAULT_ENDPOINT = "https://ingest.sdkgarden.com/track";
|
|
41
45
|
var MAX_BATCH_SIZE = 100;
|
|
42
46
|
var MAX_QUEUE_SIZE = 1e3;
|
|
@@ -46,6 +50,8 @@ var MAX_REQUEST_BYTES = 512 * 1024;
|
|
|
46
50
|
var MAX_ERROR_MESSAGE_BYTES = 1e3;
|
|
47
51
|
var MAX_EMAIL_LENGTH = 254;
|
|
48
52
|
var MAX_EVENT_NAME_LENGTH = 100;
|
|
53
|
+
var MAX_CUSTOMER_ID_LENGTH = 128;
|
|
54
|
+
var MAX_CUSTOMER_NAME_LENGTH = 200;
|
|
49
55
|
var MAX_EXIT_DRAINS = 3;
|
|
50
56
|
var NEXT_BATCH_DELAY_MS = 10;
|
|
51
57
|
var EXIT_PROBE_MS = 100;
|
|
@@ -55,6 +61,9 @@ var KEEPALIVE_BUDGET_BYTES = 32 * 1024;
|
|
|
55
61
|
var WAIT_TIMEOUT_MS = 2e3;
|
|
56
62
|
var MAX_TIMER_MS = 2147483647;
|
|
57
63
|
var MODULE_LOADED_AT = Date.now();
|
|
64
|
+
var MAX_SURFACE_METHODS = 500;
|
|
65
|
+
var MAX_METHOD_NAME_LENGTH = 255;
|
|
66
|
+
var MAX_SURFACE_CHARS = 32 * 1024;
|
|
58
67
|
function _clamp(value, min, max, fallback) {
|
|
59
68
|
if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
|
|
60
69
|
return Math.min(max, Math.max(min, Math.floor(value)));
|
|
@@ -115,6 +124,29 @@ function _getMetadata() {
|
|
|
115
124
|
return meta;
|
|
116
125
|
}
|
|
117
126
|
var EMAIL_PATTERN = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/;
|
|
127
|
+
function _customerField(value, maxLength) {
|
|
128
|
+
if (typeof value !== "string") return "";
|
|
129
|
+
return value.replace(/[\u0000-\u001f\u007f-\u009f]/g, "").trim().slice(0, maxLength).trim();
|
|
130
|
+
}
|
|
131
|
+
function _cleanCustomer(customer) {
|
|
132
|
+
if (customer === null || typeof customer !== "object") return null;
|
|
133
|
+
const c = customer;
|
|
134
|
+
const id = _customerField(c["id"], MAX_CUSTOMER_ID_LENGTH);
|
|
135
|
+
if (id === "") {
|
|
136
|
+
_log("Ignored a customer without an id");
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
if (EMAIL_PATTERN.test(id)) {
|
|
140
|
+
_log("Ignored a customer id that contains an email address");
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
const name = _customerField(c["name"], MAX_CUSTOMER_NAME_LENGTH);
|
|
144
|
+
if (name !== "" && EMAIL_PATTERN.test(name)) {
|
|
145
|
+
_log("Left out a customer name that contains an email address");
|
|
146
|
+
return { id };
|
|
147
|
+
}
|
|
148
|
+
return name !== "" ? { id, name } : { id };
|
|
149
|
+
}
|
|
118
150
|
function _truncateUtf8(str, maxBytes) {
|
|
119
151
|
let bytes = 0;
|
|
120
152
|
for (let i = 0; i < str.length; i++) {
|
|
@@ -208,6 +240,7 @@ function _enqueue(event) {
|
|
|
208
240
|
_log("Queue full; dropped event", event.event);
|
|
209
241
|
return;
|
|
210
242
|
}
|
|
243
|
+
if (_customer) event.customer = _customer;
|
|
211
244
|
let json;
|
|
212
245
|
try {
|
|
213
246
|
json = JSON.stringify(event);
|
|
@@ -305,6 +338,37 @@ function _createWrapper(methodName, fn) {
|
|
|
305
338
|
});
|
|
306
339
|
return wrapped;
|
|
307
340
|
}
|
|
341
|
+
function _isMethodName(name) {
|
|
342
|
+
return typeof name === "string" && name.trim() !== "" && name.length <= MAX_METHOD_NAME_LENGTH;
|
|
343
|
+
}
|
|
344
|
+
function _surfaceList(names) {
|
|
345
|
+
const out = /* @__PURE__ */ new Set();
|
|
346
|
+
let chars = 0;
|
|
347
|
+
for (const name of names) {
|
|
348
|
+
if (out.size >= MAX_SURFACE_METHODS) break;
|
|
349
|
+
if (!_isMethodName(name) || out.has(name)) continue;
|
|
350
|
+
if (chars + name.length > MAX_SURFACE_CHARS) break;
|
|
351
|
+
out.add(name);
|
|
352
|
+
chars += name.length;
|
|
353
|
+
}
|
|
354
|
+
return Array.from(out);
|
|
355
|
+
}
|
|
356
|
+
function _register(methodName) {
|
|
357
|
+
if (!_isMethodName(methodName) || _registered.has(methodName) || _registered.size >= MAX_SURFACE_METHODS) return;
|
|
358
|
+
_registered.add(methodName);
|
|
359
|
+
if (!_config) return;
|
|
360
|
+
_undeclared.push(methodName);
|
|
361
|
+
if (_declareScheduled) return;
|
|
362
|
+
_declareScheduled = true;
|
|
363
|
+
Promise.resolve().then(
|
|
364
|
+
() => _safely(() => {
|
|
365
|
+
_declareScheduled = false;
|
|
366
|
+
const methods = _surfaceList(_undeclared);
|
|
367
|
+
_undeclared = [];
|
|
368
|
+
if (methods.length > 0) SdkGarden.track("api_surface", { api_surface: methods });
|
|
369
|
+
})
|
|
370
|
+
);
|
|
371
|
+
}
|
|
308
372
|
function _keepaliveBytesInFlight() {
|
|
309
373
|
let total = 0;
|
|
310
374
|
_pending.forEach((request) => {
|
|
@@ -586,6 +650,7 @@ function _teardown() {
|
|
|
586
650
|
_config = null;
|
|
587
651
|
_queue = [];
|
|
588
652
|
_sessionId = "";
|
|
653
|
+
_customer = null;
|
|
589
654
|
_endExit();
|
|
590
655
|
}
|
|
591
656
|
function _start(config) {
|
|
@@ -607,6 +672,12 @@ function _start(config) {
|
|
|
607
672
|
_config = _mergeDefaults(config);
|
|
608
673
|
_queue = carried;
|
|
609
674
|
_sessionId = _generateSessionId();
|
|
675
|
+
_customer = config.customer === void 0 ? null : _cleanCustomer(config.customer);
|
|
676
|
+
_undeclared = [];
|
|
677
|
+
const apiSurface = _surfaceList([
|
|
678
|
+
...Array.isArray(config.methods) ? config.methods : [],
|
|
679
|
+
..._registered
|
|
680
|
+
]);
|
|
610
681
|
_log("Initialised", _sanitizeConfig(_config));
|
|
611
682
|
_enqueue({
|
|
612
683
|
event: "sdk_initialized",
|
|
@@ -615,7 +686,8 @@ function _start(config) {
|
|
|
615
686
|
timestamp: Date.now(),
|
|
616
687
|
properties: {
|
|
617
688
|
environment: _getEnvironment(),
|
|
618
|
-
time_to_init_ms: Math.max(0, Date.now() - initStartedAt)
|
|
689
|
+
time_to_init_ms: Math.max(0, Date.now() - initStartedAt),
|
|
690
|
+
...apiSurface.length > 0 ? { api_surface: apiSurface } : {}
|
|
619
691
|
},
|
|
620
692
|
meta: _getMetadata()
|
|
621
693
|
});
|
|
@@ -678,6 +750,7 @@ var SdkGarden = {
|
|
|
678
750
|
* ```
|
|
679
751
|
*/
|
|
680
752
|
trackMethod(methodName, fn) {
|
|
753
|
+
_safely(() => _register(methodName));
|
|
681
754
|
if (fn !== void 0) {
|
|
682
755
|
return _createWrapper(methodName, fn);
|
|
683
756
|
}
|
|
@@ -712,6 +785,25 @@ var SdkGarden = {
|
|
|
712
785
|
})
|
|
713
786
|
);
|
|
714
787
|
},
|
|
788
|
+
/**
|
|
789
|
+
* Attribute the session to one of your customers: an app or account, by an
|
|
790
|
+
* ID from your own system. Every event tracked afterwards carries it, so the
|
|
791
|
+
* dashboard can show which customers are on old versions, still call
|
|
792
|
+
* deprecated methods or hit errors. Pass null to stop. Lasts until the next
|
|
793
|
+
* init() or shutdown(); no-op before init().
|
|
794
|
+
*
|
|
795
|
+
* Never pass an email address or other personal data: an ID containing an
|
|
796
|
+
* email address is ignored.
|
|
797
|
+
*
|
|
798
|
+
* @example
|
|
799
|
+
* SdkGarden.setCustomer({ id: account.id, name: account.displayName });
|
|
800
|
+
*/
|
|
801
|
+
setCustomer(customer) {
|
|
802
|
+
if (!_config) return;
|
|
803
|
+
_safely(() => {
|
|
804
|
+
_customer = customer === null ? null : _cleanCustomer(customer);
|
|
805
|
+
});
|
|
806
|
+
},
|
|
715
807
|
/**
|
|
716
808
|
* Send every queued event now and wait for the requests to finish.
|
|
717
809
|
* Useful at the end of a serverless handler or a test.
|
|
@@ -746,12 +838,14 @@ var SdkGarden = {
|
|
|
746
838
|
/**
|
|
747
839
|
* Reset all internal state.
|
|
748
840
|
* FOR UNIT TESTS ONLY — not documented in README.
|
|
749
|
-
* Clears queue, config, session ID, flush timer, listeners
|
|
750
|
-
* of requests in flight, without sending or cancelling anything.
|
|
841
|
+
* Clears queue, config, session ID, flush timer, listeners, registered
|
|
842
|
+
* method names and the record of requests in flight, without sending or cancelling anything.
|
|
751
843
|
*/
|
|
752
844
|
_reset() {
|
|
753
845
|
_teardown();
|
|
754
846
|
_pending.clear();
|
|
847
|
+
_registered.clear();
|
|
848
|
+
_undeclared = [];
|
|
755
849
|
}
|
|
756
850
|
};
|
|
757
851
|
var index_default = SdkGarden;
|
package/dist/index.mjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
var _config = null;
|
|
3
3
|
var _queue = [];
|
|
4
4
|
var _sessionId = "";
|
|
5
|
+
var _customer = null;
|
|
5
6
|
var _flushTimer = null;
|
|
6
7
|
var _exiting = false;
|
|
7
8
|
var _exitDrains = 0;
|
|
@@ -13,6 +14,9 @@ var _exitTurnCount = 0;
|
|
|
13
14
|
var _exitProbe = null;
|
|
14
15
|
var _exitProbeDueAt = Infinity;
|
|
15
16
|
var _pending = /* @__PURE__ */ new Set();
|
|
17
|
+
var _registered = /* @__PURE__ */ new Set();
|
|
18
|
+
var _undeclared = [];
|
|
19
|
+
var _declareScheduled = false;
|
|
16
20
|
var DEFAULT_ENDPOINT = "https://ingest.sdkgarden.com/track";
|
|
17
21
|
var MAX_BATCH_SIZE = 100;
|
|
18
22
|
var MAX_QUEUE_SIZE = 1e3;
|
|
@@ -22,6 +26,8 @@ var MAX_REQUEST_BYTES = 512 * 1024;
|
|
|
22
26
|
var MAX_ERROR_MESSAGE_BYTES = 1e3;
|
|
23
27
|
var MAX_EMAIL_LENGTH = 254;
|
|
24
28
|
var MAX_EVENT_NAME_LENGTH = 100;
|
|
29
|
+
var MAX_CUSTOMER_ID_LENGTH = 128;
|
|
30
|
+
var MAX_CUSTOMER_NAME_LENGTH = 200;
|
|
25
31
|
var MAX_EXIT_DRAINS = 3;
|
|
26
32
|
var NEXT_BATCH_DELAY_MS = 10;
|
|
27
33
|
var EXIT_PROBE_MS = 100;
|
|
@@ -31,6 +37,9 @@ var KEEPALIVE_BUDGET_BYTES = 32 * 1024;
|
|
|
31
37
|
var WAIT_TIMEOUT_MS = 2e3;
|
|
32
38
|
var MAX_TIMER_MS = 2147483647;
|
|
33
39
|
var MODULE_LOADED_AT = Date.now();
|
|
40
|
+
var MAX_SURFACE_METHODS = 500;
|
|
41
|
+
var MAX_METHOD_NAME_LENGTH = 255;
|
|
42
|
+
var MAX_SURFACE_CHARS = 32 * 1024;
|
|
34
43
|
function _clamp(value, min, max, fallback) {
|
|
35
44
|
if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
|
|
36
45
|
return Math.min(max, Math.max(min, Math.floor(value)));
|
|
@@ -91,6 +100,29 @@ function _getMetadata() {
|
|
|
91
100
|
return meta;
|
|
92
101
|
}
|
|
93
102
|
var EMAIL_PATTERN = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/;
|
|
103
|
+
function _customerField(value, maxLength) {
|
|
104
|
+
if (typeof value !== "string") return "";
|
|
105
|
+
return value.replace(/[\u0000-\u001f\u007f-\u009f]/g, "").trim().slice(0, maxLength).trim();
|
|
106
|
+
}
|
|
107
|
+
function _cleanCustomer(customer) {
|
|
108
|
+
if (customer === null || typeof customer !== "object") return null;
|
|
109
|
+
const c = customer;
|
|
110
|
+
const id = _customerField(c["id"], MAX_CUSTOMER_ID_LENGTH);
|
|
111
|
+
if (id === "") {
|
|
112
|
+
_log("Ignored a customer without an id");
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
if (EMAIL_PATTERN.test(id)) {
|
|
116
|
+
_log("Ignored a customer id that contains an email address");
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
const name = _customerField(c["name"], MAX_CUSTOMER_NAME_LENGTH);
|
|
120
|
+
if (name !== "" && EMAIL_PATTERN.test(name)) {
|
|
121
|
+
_log("Left out a customer name that contains an email address");
|
|
122
|
+
return { id };
|
|
123
|
+
}
|
|
124
|
+
return name !== "" ? { id, name } : { id };
|
|
125
|
+
}
|
|
94
126
|
function _truncateUtf8(str, maxBytes) {
|
|
95
127
|
let bytes = 0;
|
|
96
128
|
for (let i = 0; i < str.length; i++) {
|
|
@@ -184,6 +216,7 @@ function _enqueue(event) {
|
|
|
184
216
|
_log("Queue full; dropped event", event.event);
|
|
185
217
|
return;
|
|
186
218
|
}
|
|
219
|
+
if (_customer) event.customer = _customer;
|
|
187
220
|
let json;
|
|
188
221
|
try {
|
|
189
222
|
json = JSON.stringify(event);
|
|
@@ -281,6 +314,37 @@ function _createWrapper(methodName, fn) {
|
|
|
281
314
|
});
|
|
282
315
|
return wrapped;
|
|
283
316
|
}
|
|
317
|
+
function _isMethodName(name) {
|
|
318
|
+
return typeof name === "string" && name.trim() !== "" && name.length <= MAX_METHOD_NAME_LENGTH;
|
|
319
|
+
}
|
|
320
|
+
function _surfaceList(names) {
|
|
321
|
+
const out = /* @__PURE__ */ new Set();
|
|
322
|
+
let chars = 0;
|
|
323
|
+
for (const name of names) {
|
|
324
|
+
if (out.size >= MAX_SURFACE_METHODS) break;
|
|
325
|
+
if (!_isMethodName(name) || out.has(name)) continue;
|
|
326
|
+
if (chars + name.length > MAX_SURFACE_CHARS) break;
|
|
327
|
+
out.add(name);
|
|
328
|
+
chars += name.length;
|
|
329
|
+
}
|
|
330
|
+
return Array.from(out);
|
|
331
|
+
}
|
|
332
|
+
function _register(methodName) {
|
|
333
|
+
if (!_isMethodName(methodName) || _registered.has(methodName) || _registered.size >= MAX_SURFACE_METHODS) return;
|
|
334
|
+
_registered.add(methodName);
|
|
335
|
+
if (!_config) return;
|
|
336
|
+
_undeclared.push(methodName);
|
|
337
|
+
if (_declareScheduled) return;
|
|
338
|
+
_declareScheduled = true;
|
|
339
|
+
Promise.resolve().then(
|
|
340
|
+
() => _safely(() => {
|
|
341
|
+
_declareScheduled = false;
|
|
342
|
+
const methods = _surfaceList(_undeclared);
|
|
343
|
+
_undeclared = [];
|
|
344
|
+
if (methods.length > 0) SdkGarden.track("api_surface", { api_surface: methods });
|
|
345
|
+
})
|
|
346
|
+
);
|
|
347
|
+
}
|
|
284
348
|
function _keepaliveBytesInFlight() {
|
|
285
349
|
let total = 0;
|
|
286
350
|
_pending.forEach((request) => {
|
|
@@ -562,6 +626,7 @@ function _teardown() {
|
|
|
562
626
|
_config = null;
|
|
563
627
|
_queue = [];
|
|
564
628
|
_sessionId = "";
|
|
629
|
+
_customer = null;
|
|
565
630
|
_endExit();
|
|
566
631
|
}
|
|
567
632
|
function _start(config) {
|
|
@@ -583,6 +648,12 @@ function _start(config) {
|
|
|
583
648
|
_config = _mergeDefaults(config);
|
|
584
649
|
_queue = carried;
|
|
585
650
|
_sessionId = _generateSessionId();
|
|
651
|
+
_customer = config.customer === void 0 ? null : _cleanCustomer(config.customer);
|
|
652
|
+
_undeclared = [];
|
|
653
|
+
const apiSurface = _surfaceList([
|
|
654
|
+
...Array.isArray(config.methods) ? config.methods : [],
|
|
655
|
+
..._registered
|
|
656
|
+
]);
|
|
586
657
|
_log("Initialised", _sanitizeConfig(_config));
|
|
587
658
|
_enqueue({
|
|
588
659
|
event: "sdk_initialized",
|
|
@@ -591,7 +662,8 @@ function _start(config) {
|
|
|
591
662
|
timestamp: Date.now(),
|
|
592
663
|
properties: {
|
|
593
664
|
environment: _getEnvironment(),
|
|
594
|
-
time_to_init_ms: Math.max(0, Date.now() - initStartedAt)
|
|
665
|
+
time_to_init_ms: Math.max(0, Date.now() - initStartedAt),
|
|
666
|
+
...apiSurface.length > 0 ? { api_surface: apiSurface } : {}
|
|
595
667
|
},
|
|
596
668
|
meta: _getMetadata()
|
|
597
669
|
});
|
|
@@ -654,6 +726,7 @@ var SdkGarden = {
|
|
|
654
726
|
* ```
|
|
655
727
|
*/
|
|
656
728
|
trackMethod(methodName, fn) {
|
|
729
|
+
_safely(() => _register(methodName));
|
|
657
730
|
if (fn !== void 0) {
|
|
658
731
|
return _createWrapper(methodName, fn);
|
|
659
732
|
}
|
|
@@ -688,6 +761,25 @@ var SdkGarden = {
|
|
|
688
761
|
})
|
|
689
762
|
);
|
|
690
763
|
},
|
|
764
|
+
/**
|
|
765
|
+
* Attribute the session to one of your customers: an app or account, by an
|
|
766
|
+
* ID from your own system. Every event tracked afterwards carries it, so the
|
|
767
|
+
* dashboard can show which customers are on old versions, still call
|
|
768
|
+
* deprecated methods or hit errors. Pass null to stop. Lasts until the next
|
|
769
|
+
* init() or shutdown(); no-op before init().
|
|
770
|
+
*
|
|
771
|
+
* Never pass an email address or other personal data: an ID containing an
|
|
772
|
+
* email address is ignored.
|
|
773
|
+
*
|
|
774
|
+
* @example
|
|
775
|
+
* SdkGarden.setCustomer({ id: account.id, name: account.displayName });
|
|
776
|
+
*/
|
|
777
|
+
setCustomer(customer) {
|
|
778
|
+
if (!_config) return;
|
|
779
|
+
_safely(() => {
|
|
780
|
+
_customer = customer === null ? null : _cleanCustomer(customer);
|
|
781
|
+
});
|
|
782
|
+
},
|
|
691
783
|
/**
|
|
692
784
|
* Send every queued event now and wait for the requests to finish.
|
|
693
785
|
* Useful at the end of a serverless handler or a test.
|
|
@@ -722,12 +814,14 @@ var SdkGarden = {
|
|
|
722
814
|
/**
|
|
723
815
|
* Reset all internal state.
|
|
724
816
|
* FOR UNIT TESTS ONLY — not documented in README.
|
|
725
|
-
* Clears queue, config, session ID, flush timer, listeners
|
|
726
|
-
* of requests in flight, without sending or cancelling anything.
|
|
817
|
+
* Clears queue, config, session ID, flush timer, listeners, registered
|
|
818
|
+
* method names and the record of requests in flight, without sending or cancelling anything.
|
|
727
819
|
*/
|
|
728
820
|
_reset() {
|
|
729
821
|
_teardown();
|
|
730
822
|
_pending.clear();
|
|
823
|
+
_registered.clear();
|
|
824
|
+
_undeclared = [];
|
|
731
825
|
}
|
|
732
826
|
};
|
|
733
827
|
var index_default = SdkGarden;
|
package/package.json
CHANGED