-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathutils.ts
More file actions
136 lines (121 loc) · 3.71 KB
/
utils.ts
File metadata and controls
136 lines (121 loc) · 3.71 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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import type { PartialBy } from 'viem'
import type { ZeroDevWalletSession } from '../types/session.js'
/**
* Parses a session from a JWT.
*
* @param token - The JWT to parse.
* @returns {PartialBy<ZeroDevWalletSession, "createdAt" | "id" | "stamperType">} - The parsed session.
*/
export function parseSession(
token: string | ZeroDevWalletSession,
): PartialBy<ZeroDevWalletSession, 'createdAt' | 'id' | 'stamperType'> {
if (typeof token !== 'string') {
return token
}
const [, payload] = token.split('.')
if (!payload) {
throw new Error('Invalid JWT: Missing payload')
}
const base64 = payload.replace(/-/g, '+').replace(/_/g, '/')
const decoded = JSON.parse(atob(base64))
const {
exp,
public_key: publicKey,
session_type: sessionType,
user_id: userId,
organization_id: organizationId,
} = decoded
if (!exp || !publicKey || !sessionType || !userId || !organizationId) {
throw new Error('JWT payload missing required fields')
}
return {
sessionType,
userId,
organizationId,
expiry: exp,
token: publicKey,
publicKey,
}
}
/**
* Normalizes a timestamp to milliseconds.
*
* @param timestamp - The timestamp to normalize.
* @returns {number} - The normalized timestamp.
*/
export function normalizeTimestamp(timestamp: number): number {
return timestamp < 1e10 ? timestamp * 1_000 : timestamp
}
/**
* Generates a random buffer of 32 bytes.
*
* @returns {ArrayBuffer} - The random buffer.
*/
export const generateRandomBuffer = (): ArrayBuffer => {
const arr = new Uint8Array(32)
crypto.getRandomValues(arr)
return arr.buffer
}
/**
* Encodes a challenge in base64url format.
*
* @param challenge - The challenge to encode.
* @returns {string} - The encoded challenge.
*/
export const base64UrlEncode = (challenge: ArrayBuffer): string => {
const bytes = new Uint8Array(challenge)
const binary = String.fromCharCode(...bytes)
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')
}
/**
* Compresses an uncompressed P-256 public key into its 33-byte compressed form.
*
* @param {Uint8Array} raw - The uncompressed public key (65 bytes, starting with 0x04).
* @returns {Uint8Array} - The compressed public key (33 bytes, starting with 0x02 or 0x03).
* @throws {Error} - If the input key is not a valid uncompressed P-256 key.
*/
export function pointEncode(raw: Uint8Array): Uint8Array {
if (raw.length !== 65 || raw[0] !== 0x04) {
throw new Error('Invalid uncompressed P-256 key')
}
const x = raw.slice(1, 33)
const y = raw.slice(33, 65)
if (x.length !== 32 || y.length !== 32) {
throw new Error('Invalid x or y length')
}
const prefix = (y[31]! & 1) === 0 ? 0x02 : 0x03
const compressed = new Uint8Array(33)
compressed[0] = prefix
compressed.set(x, 1)
return compressed
}
/**
* Converts a Uint8Array into a lowercase hex string.
*
* @param {Uint8Array} input - The input byte array.
* @returns {string} - The resulting hex string.
*/
export function uint8ArrayToHexString(input: Uint8Array): string {
return input.reduce(
(result, x) => result + x.toString(16).padStart(2, '0'),
'',
)
}
/**
* Generates a compressed public key from a key pair.
*
* @returns {Promise<string>} - The compressed public key.
*/
export async function generateCompressedPublicKeyFromKeyPair(
keyPair: CryptoKeyPair,
): Promise<string> {
const rawPubKey = new Uint8Array(
await crypto.subtle.exportKey('raw', keyPair.publicKey),
)
const compressedPubKey = pointEncode(rawPubKey)
const compressedHex = uint8ArrayToHexString(compressedPubKey)
return compressedHex
}
export const humanReadableDateTime = (): string => {
return new Date().toLocaleString().replaceAll('/', '-').replaceAll(':', '.')
}