Next.js website for Rocky Mountain Vending company featuring: - Product catalog with Stripe integration - Service areas and parts pages - Admin dashboard with Clerk authentication - SEO optimized pages with JSON-LD structured data Co-authored-by: Cursor <cursoragent@cursor.com>
584 lines
15 KiB
Text
584 lines
15 KiB
Text
import { DEBUG_BUILD } from './debug-build.js';
|
|
import { updateSession } from './session.js';
|
|
import { debug } from './utils/debug-logger.js';
|
|
import { isPlainObject } from './utils/is.js';
|
|
import { merge } from './utils/merge.js';
|
|
import { uuid4 } from './utils/misc.js';
|
|
import { generateTraceId } from './utils/propagationContext.js';
|
|
import { _setSpanForScope, _getSpanForScope } from './utils/spanOnScope.js';
|
|
import { truncate } from './utils/string.js';
|
|
import { dateTimestampInSeconds } from './utils/time.js';
|
|
|
|
/**
|
|
* Default value for maximum number of breadcrumbs added to an event.
|
|
*/
|
|
const DEFAULT_MAX_BREADCRUMBS = 100;
|
|
|
|
/**
|
|
* A context to be used for capturing an event.
|
|
* This can either be a Scope, or a partial ScopeContext,
|
|
* or a callback that receives the current scope and returns a new scope to use.
|
|
*/
|
|
|
|
/**
|
|
* Holds additional event information.
|
|
*/
|
|
class Scope {
|
|
/** Flag if notifying is happening. */
|
|
|
|
/** Callback for client to receive scope changes. */
|
|
|
|
/** Callback list that will be called during event processing. */
|
|
|
|
/** Array of breadcrumbs. */
|
|
|
|
/** User */
|
|
|
|
/** Tags */
|
|
|
|
/** Extra */
|
|
|
|
/** Contexts */
|
|
|
|
/** Attachments */
|
|
|
|
/** Propagation Context for distributed tracing */
|
|
|
|
/**
|
|
* A place to stash data which is needed at some point in the SDK's event processing pipeline but which shouldn't get
|
|
* sent to Sentry
|
|
*/
|
|
|
|
/** Fingerprint */
|
|
|
|
/** Severity */
|
|
|
|
/**
|
|
* Transaction Name
|
|
*
|
|
* IMPORTANT: The transaction name on the scope has nothing to do with root spans/transaction objects.
|
|
* It's purpose is to assign a transaction to the scope that's added to non-transaction events.
|
|
*/
|
|
|
|
/** Session */
|
|
|
|
/** The client on this scope */
|
|
|
|
/** Contains the last event id of a captured event. */
|
|
|
|
// NOTE: Any field which gets added here should get added not only to the constructor but also to the `clone` method.
|
|
|
|
constructor() {
|
|
this._notifyingListeners = false;
|
|
this._scopeListeners = [];
|
|
this._eventProcessors = [];
|
|
this._breadcrumbs = [];
|
|
this._attachments = [];
|
|
this._user = {};
|
|
this._tags = {};
|
|
this._extra = {};
|
|
this._contexts = {};
|
|
this._sdkProcessingMetadata = {};
|
|
this._propagationContext = {
|
|
traceId: generateTraceId(),
|
|
sampleRand: Math.random(),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Clone all data from this scope into a new scope.
|
|
*/
|
|
clone() {
|
|
const newScope = new Scope();
|
|
newScope._breadcrumbs = [...this._breadcrumbs];
|
|
newScope._tags = { ...this._tags };
|
|
newScope._extra = { ...this._extra };
|
|
newScope._contexts = { ...this._contexts };
|
|
if (this._contexts.flags) {
|
|
// We need to copy the `values` array so insertions on a cloned scope
|
|
// won't affect the original array.
|
|
newScope._contexts.flags = {
|
|
values: [...this._contexts.flags.values],
|
|
};
|
|
}
|
|
|
|
newScope._user = this._user;
|
|
newScope._level = this._level;
|
|
newScope._session = this._session;
|
|
newScope._transactionName = this._transactionName;
|
|
newScope._fingerprint = this._fingerprint;
|
|
newScope._eventProcessors = [...this._eventProcessors];
|
|
newScope._attachments = [...this._attachments];
|
|
newScope._sdkProcessingMetadata = { ...this._sdkProcessingMetadata };
|
|
newScope._propagationContext = { ...this._propagationContext };
|
|
newScope._client = this._client;
|
|
newScope._lastEventId = this._lastEventId;
|
|
|
|
_setSpanForScope(newScope, _getSpanForScope(this));
|
|
|
|
return newScope;
|
|
}
|
|
|
|
/**
|
|
* Update the client assigned to this scope.
|
|
* Note that not every scope will have a client assigned - isolation scopes & the global scope will generally not have a client,
|
|
* as well as manually created scopes.
|
|
*/
|
|
setClient(client) {
|
|
this._client = client;
|
|
}
|
|
|
|
/**
|
|
* Set the ID of the last captured error event.
|
|
* This is generally only captured on the isolation scope.
|
|
*/
|
|
setLastEventId(lastEventId) {
|
|
this._lastEventId = lastEventId;
|
|
}
|
|
|
|
/**
|
|
* Get the client assigned to this scope.
|
|
*/
|
|
getClient() {
|
|
return this._client ;
|
|
}
|
|
|
|
/**
|
|
* Get the ID of the last captured error event.
|
|
* This is generally only available on the isolation scope.
|
|
*/
|
|
lastEventId() {
|
|
return this._lastEventId;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
addScopeListener(callback) {
|
|
this._scopeListeners.push(callback);
|
|
}
|
|
|
|
/**
|
|
* Add an event processor that will be called before an event is sent.
|
|
*/
|
|
addEventProcessor(callback) {
|
|
this._eventProcessors.push(callback);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Set the user for this scope.
|
|
* Set to `null` to unset the user.
|
|
*/
|
|
setUser(user) {
|
|
// If null is passed we want to unset everything, but still define keys,
|
|
// so that later down in the pipeline any existing values are cleared.
|
|
this._user = user || {
|
|
email: undefined,
|
|
id: undefined,
|
|
ip_address: undefined,
|
|
username: undefined,
|
|
};
|
|
|
|
if (this._session) {
|
|
updateSession(this._session, { user });
|
|
}
|
|
|
|
this._notifyScopeListeners();
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Get the user from this scope.
|
|
*/
|
|
getUser() {
|
|
return this._user;
|
|
}
|
|
|
|
/**
|
|
* Set an object that will be merged into existing tags on the scope,
|
|
* and will be sent as tags data with the event.
|
|
*/
|
|
setTags(tags) {
|
|
this._tags = {
|
|
...this._tags,
|
|
...tags,
|
|
};
|
|
this._notifyScopeListeners();
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Set a single tag that will be sent as tags data with the event.
|
|
*/
|
|
setTag(key, value) {
|
|
this._tags = { ...this._tags, [key]: value };
|
|
this._notifyScopeListeners();
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Set an object that will be merged into existing extra on the scope,
|
|
* and will be sent as extra data with the event.
|
|
*/
|
|
setExtras(extras) {
|
|
this._extra = {
|
|
...this._extra,
|
|
...extras,
|
|
};
|
|
this._notifyScopeListeners();
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Set a single key:value extra entry that will be sent as extra data with the event.
|
|
*/
|
|
setExtra(key, extra) {
|
|
this._extra = { ...this._extra, [key]: extra };
|
|
this._notifyScopeListeners();
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets the fingerprint on the scope to send with the events.
|
|
* @param {string[]} fingerprint Fingerprint to group events in Sentry.
|
|
*/
|
|
setFingerprint(fingerprint) {
|
|
this._fingerprint = fingerprint;
|
|
this._notifyScopeListeners();
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets the level on the scope for future events.
|
|
*/
|
|
setLevel(level) {
|
|
this._level = level;
|
|
this._notifyScopeListeners();
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets the transaction name on the scope so that the name of e.g. taken server route or
|
|
* the page location is attached to future events.
|
|
*
|
|
* IMPORTANT: Calling this function does NOT change the name of the currently active
|
|
* root span. If you want to change the name of the active root span, use
|
|
* `Sentry.updateSpanName(rootSpan, 'new name')` instead.
|
|
*
|
|
* By default, the SDK updates the scope's transaction name automatically on sensible
|
|
* occasions, such as a page navigation or when handling a new request on the server.
|
|
*/
|
|
setTransactionName(name) {
|
|
this._transactionName = name;
|
|
this._notifyScopeListeners();
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets context data with the given name.
|
|
* Data passed as context will be normalized. You can also pass `null` to unset the context.
|
|
* Note that context data will not be merged - calling `setContext` will overwrite an existing context with the same key.
|
|
*/
|
|
setContext(key, context) {
|
|
if (context === null) {
|
|
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
delete this._contexts[key];
|
|
} else {
|
|
this._contexts[key] = context;
|
|
}
|
|
|
|
this._notifyScopeListeners();
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Set the session for the scope.
|
|
*/
|
|
setSession(session) {
|
|
if (!session) {
|
|
delete this._session;
|
|
} else {
|
|
this._session = session;
|
|
}
|
|
this._notifyScopeListeners();
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Get the session from the scope.
|
|
*/
|
|
getSession() {
|
|
return this._session;
|
|
}
|
|
|
|
/**
|
|
* Updates the scope with provided data. Can work in three variations:
|
|
* - plain object containing updatable attributes
|
|
* - Scope instance that'll extract the attributes from
|
|
* - callback function that'll receive the current scope as an argument and allow for modifications
|
|
*/
|
|
update(captureContext) {
|
|
if (!captureContext) {
|
|
return this;
|
|
}
|
|
|
|
const scopeToMerge = typeof captureContext === 'function' ? captureContext(this) : captureContext;
|
|
|
|
const scopeInstance =
|
|
scopeToMerge instanceof Scope
|
|
? scopeToMerge.getScopeData()
|
|
: isPlainObject(scopeToMerge)
|
|
? (captureContext )
|
|
: undefined;
|
|
|
|
const { tags, extra, user, contexts, level, fingerprint = [], propagationContext } = scopeInstance || {};
|
|
|
|
this._tags = { ...this._tags, ...tags };
|
|
this._extra = { ...this._extra, ...extra };
|
|
this._contexts = { ...this._contexts, ...contexts };
|
|
|
|
if (user && Object.keys(user).length) {
|
|
this._user = user;
|
|
}
|
|
|
|
if (level) {
|
|
this._level = level;
|
|
}
|
|
|
|
if (fingerprint.length) {
|
|
this._fingerprint = fingerprint;
|
|
}
|
|
|
|
if (propagationContext) {
|
|
this._propagationContext = propagationContext;
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Clears the current scope and resets its properties.
|
|
* Note: The client will not be cleared.
|
|
*/
|
|
clear() {
|
|
// client is not cleared here on purpose!
|
|
this._breadcrumbs = [];
|
|
this._tags = {};
|
|
this._extra = {};
|
|
this._user = {};
|
|
this._contexts = {};
|
|
this._level = undefined;
|
|
this._transactionName = undefined;
|
|
this._fingerprint = undefined;
|
|
this._session = undefined;
|
|
_setSpanForScope(this, undefined);
|
|
this._attachments = [];
|
|
this.setPropagationContext({ traceId: generateTraceId(), sampleRand: Math.random() });
|
|
|
|
this._notifyScopeListeners();
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Adds a breadcrumb to the scope.
|
|
* By default, the last 100 breadcrumbs are kept.
|
|
*/
|
|
addBreadcrumb(breadcrumb, maxBreadcrumbs) {
|
|
const maxCrumbs = typeof maxBreadcrumbs === 'number' ? maxBreadcrumbs : DEFAULT_MAX_BREADCRUMBS;
|
|
|
|
// No data has been changed, so don't notify scope listeners
|
|
if (maxCrumbs <= 0) {
|
|
return this;
|
|
}
|
|
|
|
const mergedBreadcrumb = {
|
|
timestamp: dateTimestampInSeconds(),
|
|
...breadcrumb,
|
|
// Breadcrumb messages can theoretically be infinitely large and they're held in memory so we truncate them not to leak (too much) memory
|
|
message: breadcrumb.message ? truncate(breadcrumb.message, 2048) : breadcrumb.message,
|
|
};
|
|
|
|
this._breadcrumbs.push(mergedBreadcrumb);
|
|
if (this._breadcrumbs.length > maxCrumbs) {
|
|
this._breadcrumbs = this._breadcrumbs.slice(-maxCrumbs);
|
|
this._client?.recordDroppedEvent('buffer_overflow', 'log_item');
|
|
}
|
|
|
|
this._notifyScopeListeners();
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Get the last breadcrumb of the scope.
|
|
*/
|
|
getLastBreadcrumb() {
|
|
return this._breadcrumbs[this._breadcrumbs.length - 1];
|
|
}
|
|
|
|
/**
|
|
* Clear all breadcrumbs from the scope.
|
|
*/
|
|
clearBreadcrumbs() {
|
|
this._breadcrumbs = [];
|
|
this._notifyScopeListeners();
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Add an attachment to the scope.
|
|
*/
|
|
addAttachment(attachment) {
|
|
this._attachments.push(attachment);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Clear all attachments from the scope.
|
|
*/
|
|
clearAttachments() {
|
|
this._attachments = [];
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Get the data of this scope, which should be applied to an event during processing.
|
|
*/
|
|
getScopeData() {
|
|
return {
|
|
breadcrumbs: this._breadcrumbs,
|
|
attachments: this._attachments,
|
|
contexts: this._contexts,
|
|
tags: this._tags,
|
|
extra: this._extra,
|
|
user: this._user,
|
|
level: this._level,
|
|
fingerprint: this._fingerprint || [],
|
|
eventProcessors: this._eventProcessors,
|
|
propagationContext: this._propagationContext,
|
|
sdkProcessingMetadata: this._sdkProcessingMetadata,
|
|
transactionName: this._transactionName,
|
|
span: _getSpanForScope(this),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Add data which will be accessible during event processing but won't get sent to Sentry.
|
|
*/
|
|
setSDKProcessingMetadata(newData) {
|
|
this._sdkProcessingMetadata = merge(this._sdkProcessingMetadata, newData, 2);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Add propagation context to the scope, used for distributed tracing
|
|
*/
|
|
setPropagationContext(context) {
|
|
this._propagationContext = context;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Get propagation context from the scope, used for distributed tracing
|
|
*/
|
|
getPropagationContext() {
|
|
return this._propagationContext;
|
|
}
|
|
|
|
/**
|
|
* Capture an exception for this scope.
|
|
*
|
|
* @returns {string} The id of the captured Sentry event.
|
|
*/
|
|
captureException(exception, hint) {
|
|
const eventId = hint?.event_id || uuid4();
|
|
|
|
if (!this._client) {
|
|
DEBUG_BUILD && debug.warn('No client configured on scope - will not capture exception!');
|
|
return eventId;
|
|
}
|
|
|
|
const syntheticException = new Error('Sentry syntheticException');
|
|
|
|
this._client.captureException(
|
|
exception,
|
|
{
|
|
originalException: exception,
|
|
syntheticException,
|
|
...hint,
|
|
event_id: eventId,
|
|
},
|
|
this,
|
|
);
|
|
|
|
return eventId;
|
|
}
|
|
|
|
/**
|
|
* Capture a message for this scope.
|
|
*
|
|
* @returns {string} The id of the captured message.
|
|
*/
|
|
captureMessage(message, level, hint) {
|
|
const eventId = hint?.event_id || uuid4();
|
|
|
|
if (!this._client) {
|
|
DEBUG_BUILD && debug.warn('No client configured on scope - will not capture message!');
|
|
return eventId;
|
|
}
|
|
|
|
const syntheticException = new Error(message);
|
|
|
|
this._client.captureMessage(
|
|
message,
|
|
level,
|
|
{
|
|
originalException: message,
|
|
syntheticException,
|
|
...hint,
|
|
event_id: eventId,
|
|
},
|
|
this,
|
|
);
|
|
|
|
return eventId;
|
|
}
|
|
|
|
/**
|
|
* Capture a Sentry event for this scope.
|
|
*
|
|
* @returns {string} The id of the captured event.
|
|
*/
|
|
captureEvent(event, hint) {
|
|
const eventId = hint?.event_id || uuid4();
|
|
|
|
if (!this._client) {
|
|
DEBUG_BUILD && debug.warn('No client configured on scope - will not capture event!');
|
|
return eventId;
|
|
}
|
|
|
|
this._client.captureEvent(event, { ...hint, event_id: eventId }, this);
|
|
|
|
return eventId;
|
|
}
|
|
|
|
/**
|
|
* This will be called on every set call.
|
|
*/
|
|
_notifyScopeListeners() {
|
|
// We need this check for this._notifyingListeners to be able to work on scope during updates
|
|
// If this check is not here we'll produce endless recursion when something is done with the scope
|
|
// during the callback.
|
|
if (!this._notifyingListeners) {
|
|
this._notifyingListeners = true;
|
|
this._scopeListeners.forEach(callback => {
|
|
callback(this);
|
|
});
|
|
this._notifyingListeners = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
export { Scope };
|
|
//# sourceMappingURL=scope.js.map
|