11/* eslint-disable max-lines */
2+ import { Attributes , attributeValueToTypedAttributeValue , AttributeValueType , TypedAttributeValue } from './attributes' ;
23import type { Client } from './client' ;
34import { DEBUG_BUILD } from './debug-build' ;
45import { updateSession } from './session' ;
@@ -46,6 +47,7 @@ export interface ScopeContext {
4647 extra : Extras ;
4748 contexts : Contexts ;
4849 tags : { [ key : string ] : Primitive } ;
50+ attributes ?: Attributes ;
4951 fingerprint : string [ ] ;
5052 propagationContext : PropagationContext ;
5153}
@@ -71,6 +73,8 @@ export interface ScopeData {
7173 breadcrumbs : Breadcrumb [ ] ;
7274 user : User ;
7375 tags : { [ key : string ] : Primitive } ;
76+ // TODO(v11): Make this a required field (could be subtly breaking if we did it today)
77+ attributes ?: Attributes ;
7478 extra : Extras ;
7579 contexts : Contexts ;
7680 attachments : Attachment [ ] ;
@@ -104,6 +108,9 @@ export class Scope {
104108 /** Tags */
105109 protected _tags : { [ key : string ] : Primitive } ;
106110
111+ /** Attributes */
112+ protected _attributes : Attributes ;
113+
107114 /** Extra */
108115 protected _extra : Extras ;
109116
@@ -155,6 +162,7 @@ export class Scope {
155162 this . _attachments = [ ] ;
156163 this . _user = { } ;
157164 this . _tags = { } ;
165+ this . _attributes = { } ;
158166 this . _extra = { } ;
159167 this . _contexts = { } ;
160168 this . _sdkProcessingMetadata = { } ;
@@ -171,6 +179,7 @@ export class Scope {
171179 const newScope = new Scope ( ) ;
172180 newScope . _breadcrumbs = [ ...this . _breadcrumbs ] ;
173181 newScope . _tags = { ...this . _tags } ;
182+ newScope . _attributes = { ...this . _attributes } ;
174183 newScope . _extra = { ...this . _extra } ;
175184 newScope . _contexts = { ...this . _contexts } ;
176185 if ( this . _contexts . flags ) {
@@ -294,6 +303,59 @@ export class Scope {
294303 return this . setTags ( { [ key ] : value } ) ;
295304 }
296305
306+ /**
307+ * Sets attributes onto the scope.
308+ *
309+ * TODO:
310+ * Currently, these attributes are not applied to any telemetry data but they will be in the future.
311+ *
312+ * @param newAttributes - The attributes to set on the scope. You can either pass in key-value pairs, or
313+ * an object with a concrete type declaration and an optional unit (if applicable to your attribute).
314+ * You can only pass in primitive values or arrays of primitive values.
315+ *
316+ * @example
317+ * ```typescript
318+ * scope.setAttributes({
319+ * is_admin: true,
320+ * payment_selection: 'credit_card',
321+ * clicked_products: [130, 554, 292],
322+ * render_duration: { value: 'render_duration', type: 'float', unit: 'ms' },
323+ * });
324+ * ```
325+ */
326+ public setAttributes ( newAttributes : Record < string , AttributeValueType | TypedAttributeValue > ) : this {
327+ Object . entries ( newAttributes ) . forEach ( ( [ key , value ] ) => {
328+ if ( typeof value === 'object' && ! Array . isArray ( value ) ) {
329+ this . _attributes [ key ] = value ;
330+ } else {
331+ this . _attributes [ key ] = attributeValueToTypedAttributeValue ( value ) ;
332+ }
333+ } ) ;
334+ this . _notifyScopeListeners ( ) ;
335+ return this ;
336+ }
337+
338+ /**
339+ * Sets an attribute onto the scope.
340+ *
341+ * TODO:
342+ * Currently, these attributes are not applied to any telemetry data but they will be in the future.
343+ *
344+ * @param key - The attribute key.
345+ * @param value - the attribute value. You can either pass in a raw value (primitive or array of primitives), or
346+ * a typed attribute value object with a concrete type declaration and an optional unit (if applicable to your attribute).
347+ *
348+ * @example
349+ * ```typescript
350+ * scope.setAttribute('is_admin', true);
351+ * scope.setAttribute('clicked_products', [130, 554, 292]);
352+ * scope.setAttribute('render_duration', { value: 'render_duration', type: 'float', unit: 'ms' });
353+ * ```
354+ */
355+ public setAttribute ( key : string , value : AttributeValueType | TypedAttributeValue ) : this {
356+ return this . setAttributes ( { [ key ] : value } ) ;
357+ }
358+
297359 /**
298360 * Set an object that will be merged into existing extra on the scope,
299361 * and will be sent as extra data with the event.
@@ -409,9 +471,19 @@ export class Scope {
409471 ? ( captureContext as ScopeContext )
410472 : undefined ;
411473
412- const { tags, extra, user, contexts, level, fingerprint = [ ] , propagationContext } = scopeInstance || { } ;
474+ const {
475+ tags,
476+ attributes,
477+ extra,
478+ user,
479+ contexts,
480+ level,
481+ fingerprint = [ ] ,
482+ propagationContext,
483+ } = scopeInstance || { } ;
413484
414485 this . _tags = { ...this . _tags , ...tags } ;
486+ this . _attributes = { ...this . _attributes , ...attributes } ;
415487 this . _extra = { ...this . _extra , ...extra } ;
416488 this . _contexts = { ...this . _contexts , ...contexts } ;
417489
@@ -442,6 +514,7 @@ export class Scope {
442514 // client is not cleared here on purpose!
443515 this . _breadcrumbs = [ ] ;
444516 this . _tags = { } ;
517+ this . _attributes = { } ;
445518 this . _extra = { } ;
446519 this . _user = { } ;
447520 this . _contexts = { } ;
@@ -528,6 +601,7 @@ export class Scope {
528601 attachments : this . _attachments ,
529602 contexts : this . _contexts ,
530603 tags : this . _tags ,
604+ attributes : this . _attributes ,
531605 extra : this . _extra ,
532606 user : this . _user ,
533607 level : this . _level ,
0 commit comments