-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcursorCodec.ts
More file actions
67 lines (55 loc) · 1.93 KB
/
cursorCodec.ts
File metadata and controls
67 lines (55 loc) · 1.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
type Left<T> = {
error: T
result?: never
}
type Right<U> = {
error?: never
result: U
}
export type Either<T, U> = NonNullable<Left<T> | Right<U>>
export type ConversionMode = 'buffer' | 'atob-btoa'
const resolveConversionMode = (): ConversionMode =>
typeof Buffer !== 'undefined' ? 'buffer' : 'atob-btoa'
export const base64urlToString = (base64url: string, mode: ConversionMode): string => {
if (mode === 'buffer') {
return Buffer.from(base64url, 'base64url').toString('utf-8')
}
// Convert base64url to base64
const base64 = base64url.replace(/-/g, '+').replace(/_/g, '/')
const paddedBase64 = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), '=')
// Decode base64
return atob(paddedBase64)
}
export const stringToBase64url = (value: string, mode: ConversionMode): string => {
if (mode === 'buffer') {
return Buffer.from(value).toString('base64url')
}
// Encode
const base64 = btoa(value)
// Convert to base64url
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
}
/**
* Encodes JSON object to base64url
* Compatible with both browser and node envs
*/
export const encodeCursor = (cursor: Record<string, unknown> | number): string =>
stringToBase64url(JSON.stringify(cursor), resolveConversionMode())
/**
* Decodes base64url to JSON object
* Compatible with both browser and node envs
*/
export const decodeCursor = (value: string): Either<Error, Record<string, unknown> | number> => {
let error: unknown
try {
const result: unknown = JSON.parse(base64urlToString(value, resolveConversionMode()))
if (result && isObjectOrNumber(result)) return { result }
} catch (e) {
error = e
}
/* v8 ignore start */
return { error: error instanceof Error ? error : new Error('Invalid cursor') }
/* v8 ignore stop */
}
const isObjectOrNumber = (value: unknown): value is Record<string, unknown> | number =>
typeof value === 'object' || typeof value === 'number'