Skip to content

Commit c23e863

Browse files
Refactor tests and add onError callback
- Centralize test helpers in TestHelpers.ts - Unify test naming convention (Category: Description) - Add configurable onError callback to SignedRequestsManager and SignedRequester - Remove console.error from middleware, use optional callback instead
1 parent 523ab3e commit c23e863

18 files changed

Lines changed: 884 additions & 683 deletions

client/dist/SignedRequester.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type SignedRequestOptions = {
1717
declare class SignedRequester {
1818
private static readonly _primitives;
1919
private readonly _baseUrl;
20+
private readonly _onError?;
2021
private _sessionId;
2122
private _token;
2223
private _sequenceNumber;
@@ -26,7 +27,7 @@ declare class SignedRequester {
2627
private _semaphoreRelease;
2728
private _incrementSequenceNumber;
2829
private _loadFromStorage;
29-
constructor(baseUrl?: string);
30+
constructor(baseUrl?: string, onError?: (error: unknown) => void);
3031
setSession(config: SessionConfig): void;
3132
getSession(): boolean;
3233
clearSession(): void;

client/dist/SignedRequester.js

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
function _base64url_encode(value) {
1+
function _base64url_encode(value, onError) {
22
let returnValue = '';
33
if (0 < value?.length) {
44
try {
@@ -9,13 +9,12 @@ function _base64url_encode(value) {
99
.replace(/=+$/, '');
1010
}
1111
catch (error) {
12+
onError?.(error);
1213
}
1314
}
14-
else {
15-
}
1615
return returnValue;
1716
}
18-
function _base64url_decode(value) {
17+
function _base64url_decode(value, onError) {
1918
let returnValue;
2019
if (0 < value?.length && /^[A-Za-z0-9_-]*$/.test(value)) {
2120
const padding = value.length % 4;
@@ -26,10 +25,9 @@ function _base64url_decode(value) {
2625
returnValue = Uint8Array.from(binaryString, (char) => char.charCodeAt(0));
2726
}
2827
catch (error) {
28+
onError?.(error);
2929
}
3030
}
31-
else {
32-
}
3331
return returnValue;
3432
}
3533
class SignedRequester {
@@ -73,7 +71,7 @@ class SignedRequester {
7371
if (sessionIdStr && tokenStr && sequenceNumberStr) {
7472
const sessionId = parseInt(sessionIdStr);
7573
const sequenceNumber = parseInt(sequenceNumberStr);
76-
const token = _base64url_decode(tokenStr);
74+
const token = _base64url_decode(tokenStr, this._onError);
7775
if (!isNaN(sessionId) && !isNaN(sequenceNumber) && token) {
7876
this._sessionId = sessionId;
7977
this._token = token;
@@ -83,13 +81,14 @@ class SignedRequester {
8381
}
8482
return returnValue;
8583
}
86-
constructor(baseUrl) {
84+
constructor(baseUrl, onError) {
8785
this._semaphore = false;
8886
this._semaphoreQueue = [];
8987
this._baseUrl = baseUrl;
88+
this._onError = onError;
9089
}
9190
setSession(config) {
92-
const token = _base64url_decode(config.token);
91+
const token = _base64url_decode(config.token, this._onError);
9392
if (token) {
9493
this._sessionId = config.sessionId;
9594
this._token = token;
@@ -149,7 +148,7 @@ class SignedRequester {
149148
const cryptoKey = await crypto.subtle.importKey('raw', this._token, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
150149
const encoder = new TextEncoder();
151150
const signatureBuffer = await crypto.subtle.sign('HMAC', cryptoKey, encoder.encode(dataToSign));
152-
const signature = _base64url_encode(new Uint8Array(signatureBuffer));
151+
const signature = _base64url_encode(new Uint8Array(signatureBuffer), this._onError);
153152
const signedPayload = {
154153
sessionId: this._sessionId,
155154
timestamp: timestamp,

client/dist/SignedRequester.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/src/SignedRequester.ts

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ type SignedRequestOptions = {
2929
// Base64URL utilities
3030
// ============================================================================
3131

32-
function _base64url_encode( value: Uint8Array ): string {
32+
function _base64url_encode( value: Uint8Array, onError?: ( error: unknown ) => void ): string {
3333
let returnValue: string = '';
3434
if( 0 < value?.length ) {
3535
try {
@@ -39,15 +39,13 @@ function _base64url_encode( value: Uint8Array ): string {
3939
.replace( /\//g, '_' )
4040
.replace( /=+$/, '' );
4141
} catch( error ) {
42-
// console.error( `base64url_encode: failed to encode (${ error })` );
42+
onError?.( error );
4343
}
44-
} else {
45-
// console.warn( 'base64url_encode: empty value' );
4644
}
4745
return returnValue;
4846
}
4947

50-
function _base64url_decode( value: string ): Undefinedable<Uint8Array<ArrayBuffer>> {
48+
function _base64url_decode( value: string, onError?: ( error: unknown ) => void ): Undefinedable<Uint8Array<ArrayBuffer>> {
5149
let returnValue: Undefinedable<Uint8Array<ArrayBuffer>>;
5250
if( 0 < value?.length && /^[A-Za-z0-9_-]*$/.test( value ) ) {
5351
const padding = value.length % 4;
@@ -62,10 +60,8 @@ function _base64url_decode( value: string ): Undefinedable<Uint8Array<ArrayBuffe
6260
char.charCodeAt( 0 )
6361
);
6462
} catch( error ) {
65-
// console.error( `base64url_decode: failed to decode (${ error })` );
63+
onError?.( error );
6664
}
67-
} else {
68-
// console.warn( 'base64url_decode: empty or invalid characters' );
6965
}
7066
return returnValue;
7167
}
@@ -82,6 +78,7 @@ class SignedRequester {
8278
] );
8379

8480
private readonly _baseUrl: Undefinedable<string>;
81+
private readonly _onError?: ( error: unknown ) => void;
8582
private _sessionId: Undefinedable<number>;
8683
private _token: Undefinedable<Uint8Array<ArrayBuffer>>;
8784
private _sequenceNumber: Undefinedable<number>;
@@ -142,7 +139,7 @@ class SignedRequester {
142139
if( sessionIdStr && tokenStr && sequenceNumberStr ) {
143140
const sessionId: number = parseInt( sessionIdStr );
144141
const sequenceNumber: number = parseInt( sequenceNumberStr );
145-
const token: Undefinedable<Uint8Array<ArrayBuffer>> = _base64url_decode( tokenStr );
142+
const token: Undefinedable<Uint8Array<ArrayBuffer>> = _base64url_decode( tokenStr, this._onError );
146143

147144
if( !isNaN( sessionId ) && !isNaN( sequenceNumber ) && token ) {
148145
this._sessionId = sessionId;
@@ -155,18 +152,20 @@ class SignedRequester {
155152
}
156153

157154
/**
158-
* Initialize the session manager with optional base URL
155+
* Initialize the session manager with optional base URL and error handler
159156
* If baseUrl is not provided, fetch will use relative paths (current host)
157+
* If onError is provided, it will be called when encoding/decoding errors occur
160158
*/
161-
constructor( baseUrl?: string ) {
159+
constructor( baseUrl?: string, onError?: ( error: unknown ) => void ) {
162160
this._baseUrl = baseUrl;
161+
this._onError = onError;
163162
}
164163

165164
/**
166165
* Set session configuration (call after login)
167166
*/
168167
public setSession( config: SessionConfig ): void {
169-
const token: Undefinedable<Uint8Array<ArrayBuffer>> = _base64url_decode( config.token );
168+
const token: Undefinedable<Uint8Array<ArrayBuffer>> = _base64url_decode( config.token, this._onError );
170169
if( token ) {
171170
this._sessionId = config.sessionId;
172171
this._token = token;
@@ -274,7 +273,7 @@ class SignedRequester {
274273
encoder.encode( dataToSign )
275274
);
276275

277-
const signature: string = _base64url_encode( new Uint8Array( signatureBuffer ) );
276+
const signature: string = _base64url_encode( new Uint8Array( signatureBuffer ), this._onError );
278277

279278
// Build signed request
280279
const signedPayload: SignedRequest = {

dist/SignedRequestsManager.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@ type SignedRequestsManagerConfig = {
66
validitySignature: number;
77
validityToken: number;
88
tokenLength: number;
9+
onError?: (error: unknown) => void;
910
};
1011
export declare class SignedRequestsManager {
1112
private static readonly _primitives;
1213
private readonly _storage;
1314
private readonly _validitySignature;
1415
private readonly _validityToken;
1516
private readonly _tokenLength;
17+
private readonly _onError?;
1618
constructor(storage?: SessionsStorage, options?: Partial<SignedRequestsManagerConfig>);
1719
createSession(userId: number): Promise<Session>;
1820
validate(sessionId: number, timestamp: number, parameters: [string, any][], signature: Uint8Array<ArrayBuffer>): Promise<Undefinedable<Session>>;

dist/SignedRequestsManager.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ export class SignedRequestsManager {
66
_validitySignature;
77
_validityToken;
88
_tokenLength;
9+
_onError;
910
constructor(storage, options) {
1011
this._validitySignature = options?.validitySignature ?? 5000;
1112
this._validityToken = options?.validityToken ?? 60 * 60000;
1213
this._tokenLength = options?.tokenLength ?? 32;
14+
this._onError = options?.onError;
1315
if (!storage) {
1416
storage = new SessionsStorageLocal();
1517
}
@@ -81,7 +83,7 @@ export class SignedRequestsManager {
8183
}
8284
}
8385
catch (error) {
84-
console.error('Session validation error:', error);
86+
this._onError?.(error);
8587
}
8688
if (session) {
8789
context.set('session', session);

src/SignedRequestsManager.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ type SignedRequestsManagerConfig = {
88
validitySignature: number;
99
validityToken: number;
1010
tokenLength: number;
11+
onError?: ( error: unknown ) => void;
1112
};
1213

1314
export class SignedRequestsManager {
@@ -16,11 +17,13 @@ export class SignedRequestsManager {
1617
private readonly _validitySignature: number;
1718
private readonly _validityToken: number;
1819
private readonly _tokenLength: number;
20+
private readonly _onError?: ( error: unknown ) => void;
1921

2022
constructor( storage?: SessionsStorage, options?: Partial<SignedRequestsManagerConfig> ) {
2123
this._validitySignature = options?.validitySignature ?? 5000;
2224
this._validityToken = options?.validityToken ?? 60 * 60000;
2325
this._tokenLength = options?.tokenLength ?? 32;
26+
this._onError = options?.onError;
2427

2528
if( !storage ) {
2629
storage = new SessionsStorageLocal();
@@ -116,7 +119,7 @@ export class SignedRequestsManager {
116119
);
117120
}
118121
} catch( error ) {
119-
console.error( 'Session validation error:', error );
122+
this._onError?.( error );
120123
}
121124

122125
if( session ) {

tests/dist/Common.test.js

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,69 @@
11
import test from 'ava';
22
import { constantTimeEqual, fromBase64Url, hmacSha256, randomBytes, randomInt } from '../../dist/Common.js';
3-
test('fromBase64Url converts base64url to Uint8Array', (t) => {
3+
test('Common: fromBase64Url converts base64url to Uint8Array', (t) => {
44
const base64url = 'SGVsbG8gV29ybGQ';
55
const result = fromBase64Url(base64url);
66
t.true(result instanceof Uint8Array);
77
t.is(result.length, 11);
88
t.is(String.fromCharCode(...result), 'Hello World');
99
});
10-
test('fromBase64Url handles padding correctly', (t) => {
10+
test('Common: fromBase64Url handles padding correctly', (t) => {
1111
const base64url = 'dGVzdA';
1212
const result = fromBase64Url(base64url);
1313
t.is(String.fromCharCode(...result), 'test');
1414
});
15-
test('randomBytes generates correct length', (t) => {
15+
test('Common: randomBytes generates correct length', (t) => {
1616
const bytes = randomBytes(32);
1717
t.true(bytes instanceof Uint8Array);
1818
t.is(bytes.length, 32);
1919
});
20-
test('randomBytes generates different values', (t) => {
20+
test('Common: randomBytes generates different values', (t) => {
2121
const bytes1 = randomBytes(16);
2222
const bytes2 = randomBytes(16);
2323
t.false(constantTimeEqual(bytes1, bytes2));
2424
});
25-
test('randomInt generates number in range', (t) => {
25+
test('Common: randomInt generates number in range', (t) => {
2626
const min = 100;
2727
const max = 200;
2828
for (let i = 0; i < 100; i++) {
2929
const num = randomInt(min, max);
3030
t.true(num >= min && num < max);
3131
}
3232
});
33-
test('randomInt throws on invalid range', (t) => {
33+
test('Common: randomInt throws on invalid range', (t) => {
3434
t.throws(() => randomInt(100, 100), { message: 'max must be > min' });
3535
t.throws(() => randomInt(200, 100), { message: 'max must be > min' });
3636
});
37-
test('constantTimeEqual returns true for equal arrays', (t) => {
37+
test('Common: constantTimeEqual returns true for equal arrays', (t) => {
3838
const a = new Uint8Array([1, 2, 3, 4, 5]);
3939
const b = new Uint8Array([1, 2, 3, 4, 5]);
4040
t.true(constantTimeEqual(a, b));
4141
});
42-
test('constantTimeEqual returns false for different arrays', (t) => {
42+
test('Common: constantTimeEqual returns false for different arrays', (t) => {
4343
const a = new Uint8Array([1, 2, 3, 4, 5]);
4444
const b = new Uint8Array([1, 2, 3, 4, 6]);
4545
t.false(constantTimeEqual(a, b));
4646
});
47-
test('constantTimeEqual returns false for different lengths', (t) => {
47+
test('Common: constantTimeEqual returns false for different lengths', (t) => {
4848
const a = new Uint8Array([1, 2, 3]);
4949
const b = new Uint8Array([1, 2, 3, 4]);
5050
t.false(constantTimeEqual(a, b));
5151
});
52-
test('hmacSha256 generates correct signature', async (t) => {
52+
test('Common: hmacSha256 generates correct signature', async (t) => {
5353
const key = new Uint8Array(32);
5454
const data = 'test message';
5555
const signature = await hmacSha256(key, data);
5656
t.true(signature instanceof Uint8Array);
5757
t.is(signature.length, 32);
5858
});
59-
test('hmacSha256 produces consistent signatures', async (t) => {
59+
test('Common: hmacSha256 produces consistent signatures', async (t) => {
6060
const key = new Uint8Array([1, 2, 3, 4]);
6161
const data = 'consistent test';
6262
const sig1 = await hmacSha256(key, data);
6363
const sig2 = await hmacSha256(key, data);
6464
t.true(constantTimeEqual(sig1, sig2));
6565
});
66-
test('hmacSha256 produces different signatures for different keys', async (t) => {
66+
test('Common: hmacSha256 produces different signatures for different keys', async (t) => {
6767
const key1 = new Uint8Array([1, 2, 3, 4]);
6868
const key2 = new Uint8Array([5, 6, 7, 8]);
6969
const data = 'test';

0 commit comments

Comments
 (0)