-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
70 changed files
with
3,787 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# Change Log | ||
|
||
All notable changes to this project will be documented in this file. | ||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. | ||
|
||
# 0.1.0 (2024-08-14) | ||
|
||
### Features | ||
|
||
- **async:** add async tools ([946d5ba](https://github.com/rambler-digital-solutions/rambler-common/commit/946d5baf89b77fa07f9845ef68e3d8f5b6d7dd5f)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# Async | ||
|
||
Async tools | ||
|
||
## Install | ||
|
||
``` | ||
npm install -D @rambler-tech/async | ||
``` | ||
|
||
or | ||
|
||
``` | ||
yarn add -D @rambler-tech/async | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import {retry, wait} from '.' | ||
|
||
test('retry resolved promise', async () => { | ||
const fn = jest.fn((...args) => Promise.resolve(args)) | ||
const args = [1, 2, 3] | ||
const result = await retry(fn)(...args) | ||
|
||
expect(result).toEqual(args) | ||
expect(fn).toHaveBeenCalledTimes(1) | ||
}) | ||
|
||
test('retry rejected and last resolved promise', async () => { | ||
let counter = 0 | ||
const fn = jest.fn((...args) => | ||
counter++ > 1 ? Promise.resolve(args) : Promise.reject(new Error('error')) | ||
) | ||
const args = [1, 2, 3] | ||
const result = await retry(fn, {retries: 3, timeout: 10})(...args) | ||
|
||
expect(result).toEqual(args) | ||
expect(fn).toHaveBeenCalledTimes(3) | ||
}) | ||
|
||
test('retry rejected and ignore rejected with specific error', async () => { | ||
let counter = 0 | ||
const fn = jest.fn(() => | ||
Promise.reject( | ||
new Error(counter++ > 0 ? 'aborted by timeout' : 'yet another error') | ||
) | ||
) | ||
|
||
const error = await retry(fn, { | ||
retries: 3, | ||
timeout: 10, | ||
shouldRetry: (error) => !error.toString().match(/aborted/) | ||
})().catch((error) => error) | ||
|
||
expect(error.message).toBe('aborted by timeout') | ||
expect(fn).toHaveBeenCalledTimes(2) | ||
}) | ||
|
||
test('retry rejected promise', async () => { | ||
const fn = jest.fn(() => Promise.reject(new Error('failed'))) | ||
const error = await retry(fn, {retries: 3, timeout: 10})().catch( | ||
(error) => error | ||
) | ||
|
||
expect(error.message).toBe('failed') | ||
expect(fn).toHaveBeenCalledTimes(3) | ||
}) | ||
|
||
test('abort retry', async () => { | ||
const abortController = new AbortController() | ||
const fn = jest.fn(() => Promise.reject(new Error('failed'))) | ||
const promise = retry(fn, { | ||
retries: 3, | ||
timeout: 10, | ||
signal: abortController.signal | ||
})() | ||
|
||
// NOTE: simulate microtask to abort wait after first attempt | ||
await Promise.resolve().then(() => abortController.abort()) | ||
|
||
const error = await promise.catch((error) => error) | ||
|
||
expect(error.message).toBe('The user aborted a timeout.') | ||
expect(fn).toHaveBeenCalledTimes(1) | ||
}) | ||
|
||
test('wait timeout', () => { | ||
jest.useFakeTimers() | ||
jest.spyOn(global, 'setTimeout') | ||
|
||
const promise = wait(1000) | ||
|
||
jest.advanceTimersByTime(1000) | ||
|
||
expect(promise).resolves.toBeUndefined() | ||
}) | ||
|
||
test('aborted wait timeout', () => { | ||
const abortController = new AbortController() | ||
const promise = wait(1000, abortController.signal) | ||
|
||
abortController.abort() | ||
|
||
expect(promise).rejects.toThrow('The user aborted a timeout.') | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
/* eslint-disable import/no-unused-modules */ | ||
|
||
const RETRIES_LEFT = 3 | ||
const INTERVAL = 500 | ||
|
||
export type PromiseFactory<T> = (...args: any[]) => Promise<T> | ||
|
||
/** Retry options */ | ||
export interface RetryOptions { | ||
/** Maximum amount of times to retry the operation, default is 3 */ | ||
retries?: number | ||
/** Number of milliseconds before starting the retry, default is 500 */ | ||
timeout?: number | ||
/** Check an error to need retry, by default retry on every error */ | ||
shouldRetry?: (error: Error) => boolean | ||
/** AbortSignal instance to abort retry via an AbortController */ | ||
signal?: AbortSignal | ||
} | ||
|
||
/** Retry function call */ | ||
export function retry<T>( | ||
factory: PromiseFactory<T>, | ||
options: RetryOptions = {} | ||
): PromiseFactory<T> { | ||
let {retries = RETRIES_LEFT} = options | ||
const {timeout = INTERVAL, shouldRetry = () => true, signal} = options | ||
|
||
async function call(...args: any[]): Promise<T> { | ||
try { | ||
return await factory(...args) | ||
} catch (error: any) { | ||
if (--retries < 1 || !shouldRetry(error)) { | ||
throw error | ||
} | ||
|
||
await wait(timeout, signal) | ||
|
||
return call(...args) | ||
} | ||
} | ||
|
||
return (...args: any[]): Promise<T> => call(...args) | ||
} | ||
|
||
/** Wait function call */ | ||
export function wait(timeout: number, signal?: AbortSignal): Promise<void> { | ||
return new Promise<void>((resolve, reject) => { | ||
const timeoutId = window.setTimeout(resolve, timeout) | ||
|
||
signal?.addEventListener('abort', () => { | ||
if (timeoutId) { | ||
clearTimeout(timeoutId) | ||
} | ||
|
||
reject(new DOMException('The user aborted a timeout.', 'AbortError')) | ||
}) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"name": "@rambler-tech/async", | ||
"version": "0.1.0", | ||
"main": "dist", | ||
"module": "dist", | ||
"types": "dist/index.d.ts", | ||
"license": "MIT", | ||
"sideEffects": false, | ||
"publishConfig": { | ||
"access": "public" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../../tsconfig.package.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../../typedoc.package.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# Change Log | ||
|
||
All notable changes to this project will be documented in this file. | ||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. | ||
|
||
# 0.1.0 (2024-08-14) | ||
|
||
### Features | ||
|
||
- **crypto:** add crypto utils ([e6543a2](https://github.com/rambler-digital-solutions/rambler-common/commit/e6543a2d9b70b4b9d4ff4c19250c32aee2161c37)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# Crypto | ||
|
||
Browser crypto utils | ||
|
||
## Install | ||
|
||
``` | ||
npm install -D @rambler-tech/crypto | ||
``` | ||
|
||
or | ||
|
||
``` | ||
yarn add -D @rambler-tech/crypto | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import {getRandomValues, subtle} from './crypto' | ||
import { | ||
bufferFromString, | ||
bufferFromUnicode, | ||
stringFromBuffer, | ||
base64urlFromString | ||
} from './buffers' | ||
|
||
function generateAESKey() { | ||
return subtle.generateKey( | ||
{ | ||
name: 'AES-GCM', | ||
length: 256 | ||
}, | ||
true, | ||
['encrypt', 'decrypt'] | ||
) | ||
} | ||
|
||
async function encryptAES( | ||
key: CryptoKey, | ||
initVector: Uint8Array, | ||
body: string | ||
) { | ||
const encryptedBody = await subtle.encrypt( | ||
{name: 'AES-GCM', iv: initVector}, | ||
key, | ||
bufferFromUnicode(body) | ||
) | ||
|
||
return base64urlFromString(stringFromBuffer(encryptedBody)) | ||
} | ||
|
||
function importRSAKey(keyString: string) { | ||
return subtle.importKey( | ||
'spki', | ||
bufferFromString(window.atob(keyString)), | ||
{ | ||
name: 'RSA-OAEP', | ||
hash: {name: 'SHA-256'} | ||
}, | ||
false, | ||
['wrapKey'] | ||
) | ||
} | ||
|
||
async function encryptRSA(key: CryptoKey, body: CryptoKey) { | ||
const encryptedBody = await subtle.wrapKey('raw', body, key, { | ||
name: 'RSA-OAEP' | ||
} as RsaOaepParams) | ||
|
||
return base64urlFromString(stringFromBuffer(encryptedBody)) | ||
} | ||
|
||
/** Encrypt with AES and RSA keys */ | ||
export async function encryptAESRSA(keyString: string, body: string) { | ||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers | ||
const initVector = getRandomValues(new Uint8Array(12)) | ||
const initVectorString = base64urlFromString(stringFromBuffer(initVector)) | ||
|
||
const [aesKey, rsaKey] = await Promise.all([ | ||
generateAESKey(), | ||
importRSAKey(keyString) | ||
]) | ||
|
||
const [encryptedBody, encryptedKey] = await Promise.all([ | ||
encryptAES(aesKey, initVector, body), | ||
encryptRSA(rsaKey, aesKey) | ||
]) | ||
|
||
return `${encryptedBody}.${encryptedKey}.${initVectorString}` | ||
} |
Oops, something went wrong.