Skip to content

Commit 37eecbd

Browse files
committed
refactor(core): UserManager abstraction added
1 parent db43ea3 commit 37eecbd

File tree

10 files changed

+160
-35
lines changed

10 files changed

+160
-35
lines changed

packages/core/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export type { HawkStorage } from './types/storage';
2+
export type { UserManager } from './types/user-manager';
3+
export { StorageUserManager } from './types/storage-user-manager';
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { AffectedUser } from "@hawk.so/types";
2+
import { id } from "../utils/id";
3+
import { HawkStorage } from "./storage";
4+
import { UserManager } from "./user-manager";
5+
6+
/**
7+
* Storage key used to persist the user identifier.
8+
*/
9+
const HAWK_USER_STORAGE_KEY = 'hawk-user-id';
10+
11+
/**
12+
* {@link UserManager} implementation that persists the affected user
13+
* via an injected {@link HawkStorage} backend.
14+
*/
15+
export class StorageUserManager implements UserManager {
16+
17+
/**
18+
* Underlying storage used to read and write the user identifier.
19+
*/
20+
private readonly storage: HawkStorage;
21+
22+
/**
23+
* @param storage - Storage backend to use for persistence.
24+
*/
25+
constructor(storage: HawkStorage) {
26+
this.storage = storage;
27+
}
28+
29+
/**
30+
* Returns the stored user if one exists. Otherwise, generates a new identifier,
31+
* saves it in storage under {@linkcode HAWK_USER_STORAGE_KEY}, and returns the new user.
32+
*/
33+
getUser(): AffectedUser {
34+
const storedId = this.storage.getItem(HAWK_USER_STORAGE_KEY);
35+
if (storedId) {
36+
return {
37+
id: storedId,
38+
};
39+
}
40+
41+
const userId = id()
42+
this.storage.setItem(HAWK_USER_STORAGE_KEY, userId);
43+
return {
44+
id: userId
45+
};
46+
}
47+
48+
/**
49+
* Persists the given user's identifier in storage.
50+
*
51+
* @param user - The affected user to store.
52+
*/
53+
setUser(user: AffectedUser): void {
54+
this.storage.setItem(HAWK_USER_STORAGE_KEY, user.id);
55+
}
56+
57+
/**
58+
* Removes the stored user identifier from storage.
59+
*/
60+
clear(): void {
61+
this.storage.removeItem(HAWK_USER_STORAGE_KEY)
62+
}
63+
}

packages/core/src/types/storage.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/**
2+
* Abstract key–value storage contract used by Hawk internals
3+
* (e.g. {@link StorageUserManager}) to persist data across sessions.
4+
*/
5+
export interface HawkStorage {
6+
/**
7+
* Returns the value associated with the given key, or `null` if none exists.
8+
*
9+
* @param key - Storage key to look up.
10+
*/
11+
getItem(key: string): string | null
12+
13+
/**
14+
* Persists a value under the given key.
15+
*
16+
* @param key - Storage key.
17+
* @param value - Value to store.
18+
*/
19+
setItem(key: string, value: string): void
20+
21+
/**
22+
* Removes the entry for the given key.
23+
*
24+
* @param key - Storage key to remove.
25+
*/
26+
removeItem(key: string): void
27+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { AffectedUser } from "@hawk.so/types";
2+
3+
/**
4+
* Contract for user identity managers.
5+
*
6+
* Implementations are responsible for persisting and retrieving the
7+
* {@link AffectedUser} that is attached to every error report sent by the catcher.
8+
*/
9+
export interface UserManager {
10+
/**
11+
* Returns the current affected user, creating one if none exists yet.
12+
*/
13+
getUser(): AffectedUser
14+
15+
/**
16+
* Replaces the stored user with the provided one.
17+
*
18+
* @param user - The affected user to persist.
19+
*/
20+
setUser(user: AffectedUser): void
21+
22+
/**
23+
* Removes any previously stored user data.
24+
*/
25+
clear(): void
26+
}

packages/javascript/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
},
4040
"homepage": "https://github.com/codex-team/hawk.javascript#readme",
4141
"dependencies": {
42+
"@hawk.so/core": "workspace:^",
4243
"error-stack-parser": "^2.1.4"
4344
},
4445
"devDependencies": {

packages/javascript/src/catcher.ts

Lines changed: 16 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import log from './utils/log';
44
import StackParser from './modules/stackParser';
55
import type { CatcherMessage, HawkInitialSettings, BreadcrumbsAPI, Transport } from './types';
66
import { VueIntegration } from './integrations/vue';
7-
import { id } from './utils/id';
87
import type {
98
AffectedUser,
109
EventContext,
@@ -19,6 +18,8 @@ import { isErrorProcessed, markErrorAsProcessed } from './utils/event';
1918
import { ConsoleCatcher } from './addons/consoleCatcher';
2019
import { BreadcrumbManager } from './addons/breadcrumbs';
2120
import { validateUser, validateContext, isValidEventPayload } from './utils/validation';
21+
import { StorageUserManager, UserManager } from "@hawk.so/core";
22+
import { HawkLocalStorage } from "./modules/local-storage";
2223

2324
/**
2425
* Allow to use global VERSION, that will be overwritten by Webpack
@@ -62,11 +63,6 @@ export default class Catcher {
6263
*/
6364
private readonly release: string | undefined;
6465

65-
/**
66-
* Current authenticated user
67-
*/
68-
private user: AffectedUser;
69-
7066
/**
7167
* Any additional data passed by user for sending with all messages
7268
*/
@@ -111,6 +107,11 @@ export default class Catcher {
111107
*/
112108
private readonly breadcrumbManager: BreadcrumbManager | null;
113109

110+
/**
111+
* Current authenticated user manager instance
112+
*/
113+
private readonly userManager: UserManager = new StorageUserManager(new HawkLocalStorage());
114+
114115
/**
115116
* Catcher constructor
116117
*
@@ -126,7 +127,9 @@ export default class Catcher {
126127
this.token = settings.token;
127128
this.debug = settings.debug || false;
128129
this.release = settings.release !== undefined ? String(settings.release) : undefined;
129-
this.setUser(settings.user || Catcher.getGeneratedUser());
130+
if (settings.user) {
131+
this.setUser(settings.user);
132+
}
130133
this.setContext(settings.context || undefined);
131134
this.beforeSend = settings.beforeSend;
132135
this.disableVueErrorHandler =
@@ -189,27 +192,6 @@ export default class Catcher {
189192
}
190193
}
191194

192-
/**
193-
* Generates user if no one provided via HawkCatcher settings
194-
* After generating, stores user for feature requests
195-
*/
196-
private static getGeneratedUser(): AffectedUser {
197-
let userId: string;
198-
const LOCAL_STORAGE_KEY = 'hawk-user-id';
199-
const storedId = localStorage.getItem(LOCAL_STORAGE_KEY);
200-
201-
if (storedId) {
202-
userId = storedId;
203-
} else {
204-
userId = id();
205-
localStorage.setItem(LOCAL_STORAGE_KEY, userId);
206-
}
207-
208-
return {
209-
id: userId,
210-
};
211-
}
212-
213195
/**
214196
* Send test event from client
215197
*/
@@ -272,14 +254,14 @@ export default class Catcher {
272254
return;
273255
}
274256

275-
this.user = user;
257+
this.userManager.setUser(user);
276258
}
277259

278260
/**
279-
* Clear current user information (revert to generated user)
261+
* Clear current user information
280262
*/
281263
public clearUser(): void {
282-
this.user = Catcher.getGeneratedUser();
264+
this.userManager.clear()
283265
}
284266

285267
/**
@@ -533,7 +515,7 @@ export default class Catcher {
533515
private getIntegrationId(): string {
534516
try {
535517
const decodedIntegrationToken: DecodedIntegrationToken = JSON.parse(atob(this.token));
536-
const { integrationId } = decodedIntegrationToken;
518+
const {integrationId} = decodedIntegrationToken;
537519

538520
if (!integrationId || integrationId === '') {
539521
throw new Error();
@@ -568,7 +550,7 @@ export default class Catcher {
568550
* Current authenticated user
569551
*/
570552
private getUser(): HawkJavaScriptEvent['user'] {
571-
return this.user || null;
553+
return this.userManager.getUser();
572554
}
573555

574556
/**
@@ -635,7 +617,7 @@ export default class Catcher {
635617
* @param {Error|string} error — caught error
636618
*/
637619
private getAddons(error: Error | string): HawkJavaScriptEvent['addons'] {
638-
const { innerWidth, innerHeight } = window;
620+
const {innerWidth, innerHeight} = window;
639621
const userAgent = window.navigator.userAgent;
640622
const location = window.location.href;
641623
const getParams = this.getGetParams();
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { HawkStorage } from "@hawk.so/core";
2+
3+
/**
4+
* {@link HawkStorage} implementation backed by the browser's {@linkcode localStorage}.
5+
*/
6+
export class HawkLocalStorage implements HawkStorage {
7+
/** @inheritDoc */
8+
public getItem(key: string): string | null {
9+
return localStorage.getItem(key);
10+
}
11+
12+
/** @inheritDoc */
13+
public setItem(key: string, value: string): void {
14+
localStorage.setItem(key, value);
15+
}
16+
17+
/** @inheritDoc */
18+
public removeItem(key: string): void {
19+
localStorage.removeItem(key);
20+
}
21+
}

packages/javascript/vite.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export default defineConfig(() => {
2626
fileName: 'hawk',
2727
},
2828
rollupOptions: {
29+
external: ['@hawk.so/core'],
2930
plugins: [
3031
license({
3132
thirdParty: {

yarn.lock

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,7 @@ __metadata:
576576
languageName: node
577577
linkType: hard
578578

579-
"@hawk.so/core@workspace:packages/core":
579+
"@hawk.so/core@workspace:^, @hawk.so/core@workspace:packages/core":
580580
version: 0.0.0-use.local
581581
resolution: "@hawk.so/core@workspace:packages/core"
582582
dependencies:
@@ -589,6 +589,7 @@ __metadata:
589589
version: 0.0.0-use.local
590590
resolution: "@hawk.so/javascript@workspace:packages/javascript"
591591
dependencies:
592+
"@hawk.so/core": "workspace:^"
592593
"@hawk.so/types": "npm:0.5.8"
593594
error-stack-parser: "npm:^2.1.4"
594595
jsdom: "npm:^28.0.0"

0 commit comments

Comments
 (0)