@posiwise/common-services 0.2.13 → 0.2.15

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.
@@ -984,10 +984,12 @@ class AuthService {
984
984
  this.integrationsApi = integrationsApi;
985
985
  this.document = document;
986
986
  this.platformSubject = new BehaviorSubject('');
987
+ this.userDataSubject = new BehaviorSubject({});
987
988
  this.twitterEndpoint = '/twitter';
988
989
  this.header_key = 'browser';
989
990
  this.platform = '';
990
991
  this.platform$ = this.platformSubject.asObservable();
992
+ this.userData$ = this.userDataSubject.asObservable();
991
993
  // Set Header Key
992
994
  this.setHeaderKey();
993
995
  }
@@ -1108,11 +1110,12 @@ class AuthService {
1108
1110
  phonegap: true
1109
1111
  };
1110
1112
  }
1111
- // Send DELETE request with token intact, then clear tokens after response
1112
- // Use tap to clear tokens without changing the response stream
1113
1113
  return this.http.delete('session', options).pipe(tap$1(() => {
1114
- // Clear tokens AFTER logout succeeds
1115
1114
  this.secureTokenStorage.clearTokens().subscribe();
1115
+ // Reset Crisp session on logout so next visitor is anonymous again
1116
+ if (window && window.$crisp && typeof window.$crisp.push === 'function') {
1117
+ window.$crisp.push(['do', 'session:reset']);
1118
+ }
1116
1119
  }));
1117
1120
  }
1118
1121
  /**
@@ -1130,6 +1133,23 @@ class AuthService {
1130
1133
  getToken$() {
1131
1134
  return this.secureTokenStorage.getToken$();
1132
1135
  }
1136
+ /**
1137
+ * Get user data (email, name, userId) after login
1138
+ * Similar to getToken$() but returns user information
1139
+ * @returns Observable with user data: { email?: string; name?: string; userId?: string }
1140
+ */
1141
+ getUserData$() {
1142
+ console.log(this.userData$);
1143
+ return this.userData$;
1144
+ }
1145
+ /**
1146
+ * Set user data in the BehaviorSubject
1147
+ * Called after user logs in to update user information across the app
1148
+ * @param userData User data object with email, name, and userId
1149
+ */
1150
+ setUserData(userData) {
1151
+ this.userDataSubject.next(userData);
1152
+ }
1133
1153
  getNewsletterSubscription(token) {
1134
1154
  return this.http.get(`${NEWSLETTER_CONFIRMATION_PATH}?token=${token}`);
1135
1155
  }
@@ -1195,7 +1215,33 @@ class AuthService {
1195
1215
  return this.userService.getUserInfo();
1196
1216
  }));
1197
1217
  sequence$.subscribe(resp => {
1198
- localStorage.setItem('name', resp.first_name);
1218
+ const firstName = resp.first_name ?? '';
1219
+ const email = resp.email ?? '';
1220
+ const userId = resp.id?.toString() ?? '';
1221
+ localStorage.setItem('name', firstName);
1222
+ localStorage.setItem('userEmail', email);
1223
+ localStorage.setItem('userId', userId);
1224
+ this.setUserData({
1225
+ name: firstName,
1226
+ email: email,
1227
+ userId: userId
1228
+ });
1229
+ // Identify logged-in user in Crisp if widget is already loaded
1230
+ if (window && window.$crisp && typeof window.$crisp.push === 'function') {
1231
+ if (email) {
1232
+ window.$crisp.push(['set', 'user:email', email]);
1233
+ }
1234
+ if (firstName) {
1235
+ window.$crisp.push(['set', 'user:nickname', firstName]);
1236
+ }
1237
+ if (userId) {
1238
+ window.$crisp.push([
1239
+ 'set',
1240
+ 'session:data',
1241
+ [['user_id', [userId]]]
1242
+ ]);
1243
+ }
1244
+ }
1199
1245
  this.router.navigate(['home']);
1200
1246
  if ($('.cc-dismiss').length > 0) {
1201
1247
  $('.cc-dismiss')[0].click();
@@ -2920,97 +2966,7 @@ class SentryErrorHandler {
2920
2966
  // NOTE: We intentionally do not enable Performance Tracing here.
2921
2967
  // The previous integration (`BrowserTracing` + `routingInstrumentation`) was part of
2922
2968
  // the legacy `@sentry/angular-ivy` API and isn't available in `@sentry/angular` v10.
2923
- beforeSend(event, hint) {
2924
- const initialExceptionValuesLength = Array.isArray(event?.exception?.values)
2925
- ? event.exception.values.length
2926
- : undefined;
2927
- const safeIncludes = (val, needle) => typeof val === 'string' && val.includes(needle);
2928
- // Check if the event is of type CloseEvent and ignore it
2929
- if (event?.exception?.values) {
2930
- event.exception.values = event.exception.values.filter(value => {
2931
- // Update the condition based on the actual structure of the event
2932
- return !(value.type === 'CloseEvent' ||
2933
- safeIncludes(value.value, 'CloseEvent'));
2934
- });
2935
- }
2936
- if (event?.exception?.values) {
2937
- event.exception.values = event.exception.values.filter(value => {
2938
- // Check for the specific error message and ignore it
2939
- return !(value.type === 'Error' &&
2940
- safeIncludes(value.value, 'ResizeObserver loop completed'));
2941
- });
2942
- }
2943
- // originates from vendor.js file
2944
- if (event?.exception?.values) {
2945
- const isJsonParsingError = event.exception.values.some(value => {
2946
- return (value.type === 'SyntaxError' &&
2947
- value?.value?.includes("expected ':' after property name in"));
2948
- });
2949
- if (isJsonParsingError) {
2950
- return null;
2951
- }
2952
- }
2953
- // Check if the event is a ChunkLoadError and ignore it (it could be because of poor network)
2954
- if (event?.exception?.values) {
2955
- event.exception.values = event.exception.values.filter(value => {
2956
- return !(value.type === 'ChunkLoadError' ||
2957
- safeIncludes(value.value, 'ChunkLoadError: Loading chunk'));
2958
- });
2959
- }
2960
- // Check if the event is a script loading error and ignore it
2961
- if (event?.exception?.values) {
2962
- event.exception.values = event.exception.values.filter(value => {
2963
- return !(value.type === 'Error' &&
2964
- safeIncludes(value.value, 'Could not load "util"'));
2965
- });
2966
- }
2967
- // Check if the event is a timeout error and ignore it
2968
- if (event?.exception?.values) {
2969
- event.exception.values = event.exception.values.filter(value => {
2970
- return !(value.type === 'Error' &&
2971
- safeIncludes(value.value, 'Error loading plotly.js library from'));
2972
- });
2973
- }
2974
- // Check if the event is related to cross-origin frame access
2975
- if (event?.exception?.values) {
2976
- const isCrossOriginFrameError = event.exception.values.some(value => {
2977
- return (value.type === 'SecurityError' &&
2978
- value?.value?.includes("Failed to read a named property 'navigator' from 'Window': Blocked a frame with origin"));
2979
- });
2980
- if (isCrossOriginFrameError) {
2981
- // Exclude cross-origin frame errors from being reported to Sentry
2982
- return null;
2983
- }
2984
- }
2985
- // Check if it's a non-error exception
2986
- const isNonErrorException = event?.exception?.values?.[0]?.value?.startsWith('Non-Error exception captured') ??
2987
- hint?.originalException?.message?.startsWith('Non-Error exception captured');
2988
- if (isNonErrorException) {
2989
- // We want to ignore those kinds of errors
2990
- return null;
2991
- }
2992
- // Filter out Edge browser (145.0.0+) synthetic isTrusted events
2993
- // These events spam Sentry as "unlabeled events"
2994
- const isEdge = /Edg\//.test(navigator.userAgent);
2995
- if (isEdge && hint?.originalException) {
2996
- const originalException = hint.originalException;
2997
- if (originalException &&
2998
- typeof originalException === 'object' &&
2999
- originalException['isTrusted'] === true) {
3000
- // Block Edge synthetic browser events from being sent to Sentry
3001
- return null;
3002
- }
3003
- }
3004
- // If we started with exception values but filtered them all out, drop the event.
3005
- // Otherwise Sentry will group it as an "unlabeled event" and spam the issue stream.
3006
- if (typeof initialExceptionValuesLength === 'number' &&
3007
- initialExceptionValuesLength > 0 &&
3008
- Array.isArray(event?.exception?.values) &&
3009
- event.exception.values.length === 0) {
3010
- return null;
3011
- }
3012
- return event;
3013
- },
2969
+ beforeSend: (event, hint) => SentryErrorHandler.beforeSendFilter(event, hint),
3014
2970
  denyUrls: [/drift.*\.js/i, /analytics.*\.js/i, /polyfills.*\.js/i, /vendor.*\.js/i],
3015
2971
  environment,
3016
2972
  // We ignore Server Errors. We have to define here since Angular
@@ -3193,15 +3149,41 @@ class SentryErrorHandler {
3193
3149
  return 'string_error';
3194
3150
  }
3195
3151
  if (error && typeof error === 'object') {
3152
+ return this.classifyObjectError(error);
3153
+ }
3154
+ return 'unknown';
3155
+ }
3156
+ /**
3157
+ * Classifies object-type errors
3158
+ */
3159
+ classifyObjectError(error) {
3160
+ if (typeof error === 'object' && error !== null) {
3196
3161
  if ('isTrusted' in error) {
3197
3162
  return 'browser_event';
3198
3163
  }
3199
3164
  if ('type' in error) {
3200
- return `event_${error.type}`.toLowerCase();
3165
+ return this.classifyErrorByType(error);
3201
3166
  }
3202
- return 'object_error';
3203
3167
  }
3204
- return 'unknown';
3168
+ return 'object_error';
3169
+ }
3170
+ /**
3171
+ * Determines error type classification based on the 'type' property
3172
+ */
3173
+ classifyErrorByType(error) {
3174
+ const typedError = error;
3175
+ const rawType = typedError['type'];
3176
+ let errorType;
3177
+ if (typeof rawType === 'string') {
3178
+ errorType = rawType;
3179
+ }
3180
+ else if (rawType != null) {
3181
+ errorType = JSON.stringify(rawType);
3182
+ }
3183
+ else {
3184
+ errorType = 'unknown';
3185
+ }
3186
+ return `event_${errorType}`.toLowerCase();
3205
3187
  }
3206
3188
  isEdgeSyntheticEvent(error) {
3207
3189
  // Check if running in Edge browser
@@ -3261,6 +3243,102 @@ class SentryErrorHandler {
3261
3243
  const enhancedError = new Error(enhancedErrorMessage);
3262
3244
  captureException(enhancedError);
3263
3245
  }
3246
+ /**
3247
+ * Filters Sentry events before sending — extracted from beforeSend config
3248
+ * to reduce cognitive complexity (S3776).
3249
+ *
3250
+ * Drops or filters exception values for known noisy/irrelevant error types:
3251
+ * CloseEvent, ResizeObserver, JSON parsing, ChunkLoadError, script loading,
3252
+ * plotly timeout, cross-origin frame, non-error exceptions, and Edge synthetic events.
3253
+ */
3254
+ static beforeSendFilter(event, hint) {
3255
+ const initialExceptionValuesLength = Array.isArray(event?.exception?.values)
3256
+ ? event.exception.values.length
3257
+ : undefined;
3258
+ // Apply all exception-value filters in a single pass
3259
+ if (event?.exception?.values) {
3260
+ event.exception.values = SentryErrorHandler.filterExceptionValues(event.exception.values);
3261
+ }
3262
+ // Check for early-return (drop) conditions
3263
+ const dropReason = SentryErrorHandler.shouldDropEvent(event, hint);
3264
+ if (dropReason) {
3265
+ return null;
3266
+ }
3267
+ // If we started with exception values but filtered them all out, drop the event.
3268
+ if (typeof initialExceptionValuesLength === 'number' &&
3269
+ initialExceptionValuesLength > 0 &&
3270
+ Array.isArray(event?.exception?.values) &&
3271
+ event.exception.values.length === 0) {
3272
+ return null;
3273
+ }
3274
+ return event;
3275
+ }
3276
+ /**
3277
+ * Filters out known noisy exception values in a single pass.
3278
+ */
3279
+ static filterExceptionValues(values) {
3280
+ const safeIncludes = (val, needle) => typeof val === 'string' && val.includes(needle);
3281
+ return values.filter(value => {
3282
+ // CloseEvent
3283
+ if (value.type === 'CloseEvent' || safeIncludes(value.value, 'CloseEvent')) {
3284
+ return false;
3285
+ }
3286
+ // ResizeObserver loop completed
3287
+ if (value.type === 'Error' &&
3288
+ safeIncludes(value.value, 'ResizeObserver loop completed')) {
3289
+ return false;
3290
+ }
3291
+ // ChunkLoadError
3292
+ if (value.type === 'ChunkLoadError' ||
3293
+ safeIncludes(value.value, 'ChunkLoadError: Loading chunk')) {
3294
+ return false;
3295
+ }
3296
+ // Script loading error
3297
+ if (value.type === 'Error' && safeIncludes(value.value, 'Could not load "util"')) {
3298
+ return false;
3299
+ }
3300
+ // Plotly timeout error
3301
+ if (value.type === 'Error' &&
3302
+ safeIncludes(value.value, 'Error loading plotly.js library from')) {
3303
+ return false;
3304
+ }
3305
+ return true;
3306
+ });
3307
+ }
3308
+ /**
3309
+ * Determines whether the entire event should be dropped (return truthy to drop).
3310
+ */
3311
+ static shouldDropEvent(event, hint) {
3312
+ // JSON parsing error from vendor.js
3313
+ const hasJsonParsingError = event?.exception?.values?.some(value => value.type === 'SyntaxError' &&
3314
+ value?.value?.includes("expected ':' after property name in"));
3315
+ if (hasJsonParsingError) {
3316
+ return true;
3317
+ }
3318
+ // Cross-origin frame access error
3319
+ const hasCrossOriginError = event?.exception?.values?.some(value => value.type === 'SecurityError' &&
3320
+ value?.value?.includes("Failed to read a named property 'navigator' from 'Window': Blocked a frame with origin"));
3321
+ if (hasCrossOriginError) {
3322
+ return true;
3323
+ }
3324
+ // Non-error exception
3325
+ const isNonErrorException = event?.exception?.values?.[0]?.value?.startsWith('Non-Error exception captured') ??
3326
+ hint?.originalException?.message?.startsWith('Non-Error exception captured');
3327
+ if (isNonErrorException) {
3328
+ return true;
3329
+ }
3330
+ // Edge browser synthetic isTrusted events
3331
+ const isEdge = /Edg\//.test(navigator.userAgent);
3332
+ if (isEdge && hint?.originalException) {
3333
+ const originalException = hint.originalException;
3334
+ if (originalException &&
3335
+ typeof originalException === 'object' &&
3336
+ originalException['isTrusted'] === true) {
3337
+ return true;
3338
+ }
3339
+ }
3340
+ return false;
3341
+ }
3264
3342
  logToConsole(error) {
3265
3343
  // Attempt to print in the console
3266
3344
  try {