Skip to content

UserManager abstraction added#158

Open
Reversean wants to merge 2 commits intorefactor/hawk-storagefrom
refactor/user-manager
Open

UserManager abstraction added#158
Reversean wants to merge 2 commits intorefactor/hawk-storagefrom
refactor/user-manager

Conversation

@Reversean
Copy link
Member

@Reversean Reversean commented Feb 12, 2026

Related #151

Added UserManager interface which could be used as env-agnositc abstraction to persist AffectedUser's during catching errors.

Also StorageUserManager implementation based on env-agnostic HawkStorage added.

@Reversean Reversean changed the base branch from master to chore/core-workspace February 12, 2026 18:34
@Reversean Reversean changed the title Refactor/user manager UserManager abstraction added Feb 12, 2026
@Reversean Reversean marked this pull request as draft February 12, 2026 18:36
@Reversean Reversean force-pushed the refactor/user-manager branch 2 times, most recently from 37eecbd to 70760fd Compare February 12, 2026 19:44
@Reversean Reversean force-pushed the chore/core-workspace branch 2 times, most recently from 09b7782 to 39d9d88 Compare February 16, 2026 07:34
@Reversean Reversean force-pushed the refactor/user-manager branch from 70760fd to 3f9a33a Compare February 16, 2026 07:35
@Reversean Reversean marked this pull request as ready for review February 16, 2026 07:36
@Reversean Reversean force-pushed the refactor/user-manager branch 4 times, most recently from a0132ff to 9a60f23 Compare February 16, 2026 22:22
@Reversean Reversean changed the base branch from chore/core-workspace to chore/browser-workspace February 16, 2026 22:24
@Reversean Reversean changed the base branch from chore/browser-workspace to refactor/hawk-storage February 16, 2026 22:25
@Reversean Reversean force-pushed the refactor/hawk-storage branch from 9e471fc to 2d655c1 Compare February 16, 2026 22:26
@Reversean Reversean force-pushed the refactor/user-manager branch from 9a60f23 to eacefe1 Compare February 16, 2026 22:27
@Reversean Reversean force-pushed the refactor/hawk-storage branch from 2d655c1 to c3013b6 Compare February 17, 2026 13:18
@Reversean Reversean force-pushed the refactor/hawk-storage branch from c3013b6 to 223001d Compare February 17, 2026 13:27
* {@link UserManager} implementation that persists the affected user
* in the browser's {@linkcode localStorage}.
*/
export class LocalStorageUserManager extends UserManager {

This comment was marked as outdated.

* {@link UserManager} implementation that persists the affected user
* via an injected {@link HawkStorage} backend.
*/
export class HawkStorageUserManager implements UserManager {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its not clear for me why do we need this class? Maybe UserManager can do the same (accept HawkStorage as an argument and implement getUser/setUser etc)

if (storedId) {
userId = storedId;
} else {
userId = id();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where we create the default id now?

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a UserManager abstraction in @hawk.so/core to enable environment-agnostic user persistence, as part of the larger effort to extract shared logic from @hawk.so/javascript into a core package (related to issue #151).

Changes:

  • Added UserManager interface and HawkStorageUserManager implementation in @hawk.so/core for environment-agnostic user management
  • Refactored @hawk.so/javascript to use the new UserManager abstraction instead of directly accessing localStorage
  • Modified user initialization to be lazy (generated on first access) rather than eager (generated in constructor)

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
packages/core/src/users/user-manager.ts New interface defining the user management contract
packages/core/src/users/hawk-storage-user-manager.ts Implementation of UserManager using HawkStorage for persistence
packages/core/src/index.ts Exports new UserManager interface and implementation
packages/core/tests/users/hawk-storage-user-manager.test.ts Unit tests for HawkStorageUserManager
packages/core/package.json Added test scripts and dev dependencies for vitest
packages/core/vitest.config.ts New vitest configuration for core package
packages/core/tsconfig.test.json TypeScript config for test files
packages/javascript/src/catcher.ts Refactored to use UserManager, removed getGeneratedUser, changed user initialization to lazy
packages/javascript/src/storages/hawk-local-storage.ts Browser-specific implementation of HawkStorage
packages/javascript/package.json Added @hawk.so/core as dependency
packages/javascript/vite.config.ts Marked @hawk.so/core as external dependency
packages/javascript/vitest.config.ts Added source condition for workspace dependency resolution
yarn.lock Updated workspace dependency configurations
package.json Added test:all and test:modified scripts for workspace testing
.github/workflows/main.yml Updated test workflow to run tests on modified packages with fetch-depth: 0

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +28 to +38
public getUser(): AffectedUser | null {
const storedId = this.storage.getItem(HAWK_USER_STORAGE_KEY);

if (storedId) {
return {
id: storedId,
};
}

return null;
}
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method only reconstructs the user object with the id field from storage, losing any other AffectedUser properties that were originally set. This is inconsistent with the setUser method which accepts a full AffectedUser object. See also the comment on the setUser method.

Copilot uses AI. Check for mistakes.
try {
const decodedIntegrationToken: DecodedIntegrationToken = JSON.parse(atob(this.token));
const { integrationId } = decodedIntegrationToken;
const {integrationId} = decodedIntegrationToken;
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spacing in object destructuring was changed from const { integrationId } to const {integrationId} (no spaces). However, other parts of the codebase use spaces in destructuring (e.g., packages/javascript/src/addons/consoleCatcher.ts lines 258-259). This creates inconsistency. Consider keeping the original spacing to maintain consistency with the rest of the codebase.

Suggested change
const {integrationId} = decodedIntegrationToken;
const { integrationId } = decodedIntegrationToken;

Copilot uses AI. Check for mistakes.
Comment on lines +41 to +48
public setUser(user: AffectedUser): void {
this.storage.setItem(HAWK_USER_STORAGE_KEY, user.id);
}

/** @inheritDoc */
public clear(): void {
this.storage.removeItem(HAWK_USER_STORAGE_KEY);
}
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The setUser() and clear() methods don't handle potential errors from storage operations. If the storage is unavailable or throws an error (e.g., QuotaExceededError or SecurityError), this could crash the application. Consider wrapping the storage operations in a try-catch block and handling errors gracefully (e.g., logging them without crashing).

Copilot uses AI. Check for mistakes.
import { HawkStorageUserManager } from '../../src';
import type { HawkStorage } from '../../src';

describe('StorageUserManager', () => {
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test suite is named 'StorageUserManager' but the class being tested is named 'HawkStorageUserManager'. Update the describe block name to match the actual class name for clarity and consistency.

Suggested change
describe('StorageUserManager', () => {
describe('HawkStorageUserManager', () => {

Copilot uses AI. Check for mistakes.
Comment on lines +554 to 565
private getUser(): AffectedUser {
const user = this.userManager.getUser();

if (user) {
return user;
}
const newUser: AffectedUser = {id: id()};

this.userManager.setUser(newUser);

return newUser;
}
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getUser() method now always returns a non-null AffectedUser (by creating and persisting one if none exists), which is a semantic change from the previous implementation. While the HawkJavaScriptEvent type allows user to be AffectedUser | null, the user field will now never be null in practice. Consider updating the event type definition to reflect this guarantee (user: AffectedUser) since a user is always generated and present, or document this behavioral change clearly.

Copilot uses AI. Check for mistakes.
Comment on lines +29 to +42
const storedId = this.storage.getItem(HAWK_USER_STORAGE_KEY);

if (storedId) {
return {
id: storedId,
};
}

return null;
}

/** @inheritDoc */
public setUser(user: AffectedUser): void {
this.storage.setItem(HAWK_USER_STORAGE_KEY, user.id);
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HawkStorageUserManager only persists the user.id field and loses any other AffectedUser properties (such as name, email, url, image, or any custom fields that AffectedUser might support). When getUser() is called, it only reconstructs the id field. This means if a developer calls setUser with a full user object containing additional properties, those properties will be lost. Consider serializing the entire AffectedUser object as JSON, or document this limitation clearly if it's intentional.

Suggested change
const storedId = this.storage.getItem(HAWK_USER_STORAGE_KEY);
if (storedId) {
return {
id: storedId,
};
}
return null;
}
/** @inheritDoc */
public setUser(user: AffectedUser): void {
this.storage.setItem(HAWK_USER_STORAGE_KEY, user.id);
const storedValue = this.storage.getItem(HAWK_USER_STORAGE_KEY);
if (!storedValue) {
return null;
}
// Try to parse the stored value as a full AffectedUser object.
try {
const parsed = JSON.parse(storedValue) as AffectedUser | null;
if (parsed && typeof parsed === 'object' && 'id' in parsed) {
return parsed;
}
} catch {
// Ignore JSON parsing errors and fall back to legacy behavior below.
}
// Backward compatibility: prior versions stored only the user id as a string.
return {
id: storedValue,
};
}
/** @inheritDoc */
public setUser(user: AffectedUser): void {
// Persist the full AffectedUser object so that all properties are retained.
this.storage.setItem(HAWK_USER_STORAGE_KEY, JSON.stringify(user));

Copilot uses AI. Check for mistakes.
*/
private getAddons(error: Error | string): HawkJavaScriptEvent['addons'] {
const { innerWidth, innerHeight } = window;
const {innerWidth, innerHeight} = window;
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spacing in object destructuring was changed from const { innerWidth, innerHeight } to const {innerWidth, innerHeight} (no spaces). However, other parts of the codebase use spaces in destructuring (e.g., packages/javascript/src/addons/consoleCatcher.ts lines 258-259). This creates inconsistency. Consider keeping the original spacing to maintain consistency with the rest of the codebase.

Suggested change
const {innerWidth, innerHeight} = window;
const { innerWidth, innerHeight } = window;

Copilot uses AI. Check for mistakes.
Comment on lines +29 to +37
const storedId = this.storage.getItem(HAWK_USER_STORAGE_KEY);

if (storedId) {
return {
id: storedId,
};
}

return null;
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getUser() method doesn't handle potential errors from storage.getItem(). If the storage is unavailable or throws an error (e.g., SecurityError in private browsing mode), this could crash the application. Consider wrapping the storage access in a try-catch block and returning null on error.

Suggested change
const storedId = this.storage.getItem(HAWK_USER_STORAGE_KEY);
if (storedId) {
return {
id: storedId,
};
}
return null;
try {
const storedId = this.storage.getItem(HAWK_USER_STORAGE_KEY);
if (storedId) {
return {
id: storedId,
};
}
return null;
} catch {
return null;
}

Copilot uses AI. Check for mistakes.
Comment on lines +554 to 565
private getUser(): AffectedUser {
const user = this.userManager.getUser();

if (user) {
return user;
}
const newUser: AffectedUser = {id: id()};

this.userManager.setUser(newUser);

return newUser;
}
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new user management implementation changes behavior: users are now generated lazily on first getUser() call instead of eagerly in the constructor. However, there are no tests in packages/javascript/tests verifying this new integration behavior, such as verifying that a user is automatically created when an error is sent without explicitly setting one. Consider adding tests to verify the lazy user generation behavior and the integration with UserManager.

Copilot uses AI. Check for mistakes.
Comment on lines 39 to 44
"devDependencies": {
"@vitest/coverage-v8": "^4.0.18",
"vite": "^7.3.1",
"vite-plugin-dts": "^4.2.4"
"vite-plugin-dts": "^4.2.4",
"vitest": "^4.0.18"
}
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The @hawk.so/core package imports AffectedUser from '@hawk.so/types' but this dependency is not listed in the package.json. This will cause build and runtime errors. Add '@hawk.so/types': 'npm:0.5.8' to the dependencies or peerDependencies section of packages/core/package.json.

Copilot uses AI. Check for mistakes.
@Reversean Reversean force-pushed the refactor/hawk-storage branch 2 times, most recently from 8cbdac2 to cd47f1e Compare February 17, 2026 19:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants