@goodandready/dsh-subscriptions 0.5.26 → 0.5.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/accounts.js +4 -3
- package/lib/client.js +32 -3
- package/lib/config-schema.js +80 -0
- package/lib/http.js +16 -0
- package/lib/index.js +28 -1080
- package/lib/rotate.js +1 -0
- package/lib/routes.js +1014 -0
- package/lib/sse.js +40 -9
- package/lib/ui-i18n.js +256 -0
- package/lib/ui-styles.js +119 -0
- package/package.json +5 -5
- package/lib/coalesce-stream.js +0 -22
- package/lib/cookie-bridge.js +0 -8
- package/lib/credit-detect.js +0 -22
- package/lib/encrypted-bundle.js +0 -46
- package/lib/latency-heatmap.js +0 -26
- package/lib/latency-score.js +0 -36
- package/lib/multi-tenant.js +0 -16
- package/lib/network-benchmark.js +0 -23
- package/lib/pkce-store.js +0 -28
- package/lib/plan.js +0 -42
- package/lib/proactive-refresh.js +0 -37
- package/lib/prompt-cache-warmer.js +0 -44
- package/lib/reconnect-stream.js +0 -21
- package/lib/reset-toast.js +0 -38
- package/lib/savings-calculator.js +0 -14
- package/lib/state-recovery.js +0 -33
- package/lib/telemetry.js +0 -28
- package/lib/token-speedometer.js +0 -26
- package/lib/zero-trace-logger.js +0 -27
package/lib/latency-score.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
export class LatencyTracker {
|
|
2
|
-
constructor(sampleLimit = 10) {
|
|
3
|
-
this.samples = new Map()
|
|
4
|
-
this.limit = sampleLimit
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
record(slotRef, latencyMs) {
|
|
8
|
-
if (!slotRef || typeof latencyMs !== 'number' || latencyMs < 0) return
|
|
9
|
-
if (!this.samples.has(slotRef)) {
|
|
10
|
-
this.samples.set(slotRef, [])
|
|
11
|
-
}
|
|
12
|
-
const arr = this.samples.get(slotRef)
|
|
13
|
-
arr.push(latencyMs)
|
|
14
|
-
if (arr.length > this.limit) arr.shift()
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
getAverage(slotRef) {
|
|
18
|
-
const arr = this.samples.get(slotRef)
|
|
19
|
-
if (!arr || !arr.length) return null
|
|
20
|
-
const sum = arr.reduce((acc, v) => acc + v, 0)
|
|
21
|
-
return Math.round(sum / arr.length)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
getHealthScore(slotRef) {
|
|
25
|
-
const avg = this.getAverage(slotRef)
|
|
26
|
-
if (avg == null) return 100
|
|
27
|
-
if (avg < 300) return 100
|
|
28
|
-
if (avg < 800) return 85
|
|
29
|
-
if (avg < 2000) return 60
|
|
30
|
-
return 30
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
clear() {
|
|
34
|
-
this.samples.clear()
|
|
35
|
-
}
|
|
36
|
-
}
|
package/lib/multi-tenant.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export function filterAccountsForUser(accounts, userContext) {
|
|
2
|
-
const list = Array.isArray(accounts) ? accounts : []
|
|
3
|
-
if (!userContext || userContext.isAdmin) {
|
|
4
|
-
return list // Admin sees all
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
const username = userContext.username || userContext.userId || userContext.sub
|
|
8
|
-
if (!username) return []
|
|
9
|
-
|
|
10
|
-
return list.filter((acc) => {
|
|
11
|
-
if (!acc.allowedUsers || !Array.isArray(acc.allowedUsers)) {
|
|
12
|
-
return true // Public in pool
|
|
13
|
-
}
|
|
14
|
-
return acc.allowedUsers.includes(username)
|
|
15
|
-
})
|
|
16
|
-
}
|
package/lib/network-benchmark.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
export async function benchmarkEndpoint(url, fetchImpl) {
|
|
2
|
-
const impl = fetchImpl || fetch
|
|
3
|
-
const start = Date.now()
|
|
4
|
-
try {
|
|
5
|
-
const res = await impl(url, { method: 'HEAD', signal: AbortSignal.timeout(4000) })
|
|
6
|
-
const ttfb = Date.now() - start
|
|
7
|
-
return {
|
|
8
|
-
url,
|
|
9
|
-
ok: res.ok,
|
|
10
|
-
status: res.status,
|
|
11
|
-
latencyMs: ttfb,
|
|
12
|
-
grade: ttfb < 300 ? 'A' : (ttfb < 800 ? 'B' : 'C')
|
|
13
|
-
}
|
|
14
|
-
} catch (err) {
|
|
15
|
-
return {
|
|
16
|
-
url,
|
|
17
|
-
ok: false,
|
|
18
|
-
error: err.message,
|
|
19
|
-
latencyMs: Date.now() - start,
|
|
20
|
-
grade: 'F'
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
package/lib/pkce-store.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
const pkceStates = new Map()
|
|
2
|
-
const STATE_TTL_MS = 10 * 60 * 1000 // 10 minutes
|
|
3
|
-
|
|
4
|
-
export function storePkceState(state, verifier) {
|
|
5
|
-
if (!state || !verifier) return
|
|
6
|
-
pkceStates.set(state, {
|
|
7
|
-
verifier,
|
|
8
|
-
createdAt: Date.now()
|
|
9
|
-
})
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function consumePkceState(state) {
|
|
13
|
-
if (!state) return null
|
|
14
|
-
const entry = pkceStates.get(state)
|
|
15
|
-
if (!entry) return null
|
|
16
|
-
|
|
17
|
-
// Single-use consumption (prevent INVALID_REPLAY_STATE)
|
|
18
|
-
pkceStates.delete(state)
|
|
19
|
-
|
|
20
|
-
if (Date.now() - entry.createdAt > STATE_TTL_MS) {
|
|
21
|
-
return null // expired
|
|
22
|
-
}
|
|
23
|
-
return entry.verifier
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function clearPkceStates() {
|
|
27
|
-
pkceStates.clear()
|
|
28
|
-
}
|
package/lib/plan.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Normalize and pretty-print subscription plans.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export function normalizePlanName(provider, rawPlan) {
|
|
6
|
-
if (!rawPlan || typeof rawPlan !== 'string') {
|
|
7
|
-
if (provider === 'kimi') return 'Coding Plan'
|
|
8
|
-
if (provider === 'glm') return 'Coding Plan 150%'
|
|
9
|
-
return ''
|
|
10
|
-
}
|
|
11
|
-
const key = rawPlan.trim().toLowerCase().replace(/[^a-z0-9]/g, '')
|
|
12
|
-
if (provider === 'codex') {
|
|
13
|
-
if (key.includes('pro20') || key === 'pro') return 'Pro 20x'
|
|
14
|
-
if (key.includes('pro5') || key.includes('prolite')) return 'Pro 5x'
|
|
15
|
-
if (key.includes('team')) return 'Team'
|
|
16
|
-
if (key.includes('plus')) return 'Plus'
|
|
17
|
-
if (key.includes('enterp')) return 'Enterprise'
|
|
18
|
-
if (key.includes('edu')) return 'Edu'
|
|
19
|
-
if (key.includes('free')) return 'Free'
|
|
20
|
-
}
|
|
21
|
-
if (provider === 'grok') {
|
|
22
|
-
if (key.includes('super')) return 'SuperGrok'
|
|
23
|
-
if (key.includes('plus') || key.includes('premiumplus')) return 'X Premium+'
|
|
24
|
-
if (key.includes('premium')) return 'X Premium'
|
|
25
|
-
if (key.includes('basic')) return 'X Basic'
|
|
26
|
-
if (key.includes('free')) return 'Free'
|
|
27
|
-
}
|
|
28
|
-
if (provider === 'claude') {
|
|
29
|
-
if (key.includes('team')) return 'Team'
|
|
30
|
-
if (key.includes('enterp')) return 'Enterprise'
|
|
31
|
-
if (key.includes('pro')) return 'Pro'
|
|
32
|
-
if (key.includes('free')) return 'Free'
|
|
33
|
-
}
|
|
34
|
-
if (provider === 'antigravity') {
|
|
35
|
-
if (key.includes('ultra')) return 'Ultra'
|
|
36
|
-
if (key.includes('pro')) return 'Pro'
|
|
37
|
-
if (key.includes('free')) return 'Free'
|
|
38
|
-
}
|
|
39
|
-
if (provider === 'kimi') return 'Coding Plan'
|
|
40
|
-
if (provider === 'glm') return 'Coding Plan 150%'
|
|
41
|
-
return rawPlan
|
|
42
|
-
}
|
package/lib/proactive-refresh.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
export class ProactiveTokenRefreshDaemon {
|
|
2
|
-
constructor({ refreshLeadMs = 15 * 60 * 1000, checkIntervalMs = 60 * 1000 } = {}) {
|
|
3
|
-
this.refreshLeadMs = refreshLeadMs // refresh 15 min before token expires
|
|
4
|
-
this.checkIntervalMs = checkIntervalMs
|
|
5
|
-
this.timer = null
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
start(getAccountsFn, refreshFn) {
|
|
9
|
-
if (this.timer) return
|
|
10
|
-
this.timer = setInterval(async () => {
|
|
11
|
-
try {
|
|
12
|
-
const accounts = (typeof getAccountsFn === 'function' && await getAccountsFn()) || []
|
|
13
|
-
const now = Date.now()
|
|
14
|
-
for (const acc of accounts) {
|
|
15
|
-
if (!acc || !acc.expiresAt || !acc.refreshToken) continue
|
|
16
|
-
const expiresAt = Number(acc.expiresAt)
|
|
17
|
-
if (expiresAt - now <= this.refreshLeadMs && expiresAt > now) {
|
|
18
|
-
if (typeof refreshFn === 'function') {
|
|
19
|
-
await refreshFn(acc)
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
} catch {
|
|
24
|
-
// silent catch on daemon tick
|
|
25
|
-
}
|
|
26
|
-
}, this.checkIntervalMs)
|
|
27
|
-
|
|
28
|
-
if (this.timer.unref) this.timer.unref()
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
stop() {
|
|
32
|
-
if (this.timer) {
|
|
33
|
-
clearInterval(this.timer)
|
|
34
|
-
this.timer = null
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
const warmedSlots = new Map()
|
|
2
|
-
|
|
3
|
-
export class PromptCacheWarmer {
|
|
4
|
-
constructor(ttlMs = 4.5 * 60 * 1000) {
|
|
5
|
-
this.ttlMs = ttlMs // default warm-up every 4.5 min before 5 min Anthropic cache expires
|
|
6
|
-
this.activeTimers = new Map()
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
touchSession(sessionId, slotRef, warmFn) {
|
|
10
|
-
if (!sessionId || !slotRef || typeof warmFn !== 'function') return
|
|
11
|
-
if (this.activeTimers.has(sessionId)) {
|
|
12
|
-
clearTimeout(this.activeTimers.get(sessionId))
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const timer = setTimeout(async () => {
|
|
16
|
-
try {
|
|
17
|
-
await warmFn(slotRef, sessionId)
|
|
18
|
-
warmedSlots.set(sessionId, Date.now())
|
|
19
|
-
} catch {
|
|
20
|
-
// quiet error on background warm-up
|
|
21
|
-
}
|
|
22
|
-
}, this.ttlMs)
|
|
23
|
-
|
|
24
|
-
if (timer.unref) timer.unref()
|
|
25
|
-
this.activeTimers.set(sessionId, timer)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
cancelSession(sessionId) {
|
|
29
|
-
if (this.activeTimers.has(sessionId)) {
|
|
30
|
-
clearTimeout(this.activeTimers.get(sessionId))
|
|
31
|
-
this.activeTimers.delete(sessionId)
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
isWarmed(sessionId) {
|
|
36
|
-
return warmedSlots.has(sessionId)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
clear() {
|
|
40
|
-
for (const t of this.activeTimers.values()) clearTimeout(t)
|
|
41
|
-
this.activeTimers.clear()
|
|
42
|
-
warmedSlots.clear()
|
|
43
|
-
}
|
|
44
|
-
}
|
package/lib/reconnect-stream.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export async function* streamWithReconnect(streamProducer, { maxReconnects = 2 } = {}) {
|
|
2
|
-
let reconnects = 0
|
|
3
|
-
let emittedCount = 0
|
|
4
|
-
|
|
5
|
-
while (true) {
|
|
6
|
-
try {
|
|
7
|
-
for await (const chunk of streamProducer(emittedCount)) {
|
|
8
|
-
emittedCount++
|
|
9
|
-
yield chunk
|
|
10
|
-
}
|
|
11
|
-
return
|
|
12
|
-
} catch (err) {
|
|
13
|
-
reconnects++
|
|
14
|
-
if (reconnects > maxReconnects) {
|
|
15
|
-
throw err
|
|
16
|
-
}
|
|
17
|
-
// Brief pause before reconnect
|
|
18
|
-
await new Promise((r) => setTimeout(r, 250))
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
package/lib/reset-toast.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
export class ResetNotificationManager {
|
|
2
|
-
constructor() {
|
|
3
|
-
this.scheduledAlerts = new Map()
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
scheduleResetNotice(accountRef, resetAt, onResetAlert) {
|
|
7
|
-
if (!accountRef || !resetAt || resetAt <= Date.now()) return
|
|
8
|
-
if (this.scheduledAlerts.has(accountRef)) {
|
|
9
|
-
clearTimeout(this.scheduledAlerts.get(accountRef))
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const delay = Math.max(0, resetAt - Date.now())
|
|
13
|
-
const timer = setTimeout(() => {
|
|
14
|
-
this.scheduledAlerts.delete(accountRef)
|
|
15
|
-
if (typeof onResetAlert === 'function') {
|
|
16
|
-
onResetAlert({
|
|
17
|
-
accountRef,
|
|
18
|
-
message: `Subscription quota reset! Slot ${accountRef} is ready to use.`
|
|
19
|
-
})
|
|
20
|
-
}
|
|
21
|
-
}, delay)
|
|
22
|
-
|
|
23
|
-
if (timer.unref) timer.unref()
|
|
24
|
-
this.scheduledAlerts.set(accountRef, timer)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
cancel(accountRef) {
|
|
28
|
-
if (this.scheduledAlerts.has(accountRef)) {
|
|
29
|
-
clearTimeout(this.scheduledAlerts.get(accountRef))
|
|
30
|
-
this.scheduledAlerts.delete(accountRef)
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
clear() {
|
|
35
|
-
for (const t of this.scheduledAlerts.values()) clearTimeout(t)
|
|
36
|
-
this.scheduledAlerts.clear()
|
|
37
|
-
}
|
|
38
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export function calculateCacheSavings({ cachedTokens = 0, totalInputTokens = 0, ratePerMillion = 3.0 } = {}) {
|
|
2
|
-
const hitRate = totalInputTokens > 0 ? Math.round((cachedTokens / totalInputTokens) * 100) : 0
|
|
3
|
-
// Anthropic / OpenAI prompt cache discount is typically 90% (cached tokens cost 10% of standard input)
|
|
4
|
-
const fullCost = (cachedTokens / 1_000_000) * ratePerMillion
|
|
5
|
-
const discountedCost = fullCost * 0.1
|
|
6
|
-
const savedDollars = Math.round((fullCost - discountedCost) * 100) / 100
|
|
7
|
-
|
|
8
|
-
return {
|
|
9
|
-
cachedTokens,
|
|
10
|
-
totalInputTokens,
|
|
11
|
-
hitRatePercent: hitRate,
|
|
12
|
-
estimatedSavedDollars: savedDollars
|
|
13
|
-
}
|
|
14
|
-
}
|
package/lib/state-recovery.js
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { readFile, writeFile } from 'node:fs/promises'
|
|
2
|
-
import { join } from 'node:path'
|
|
3
|
-
import { homedir } from 'node:os'
|
|
4
|
-
|
|
5
|
-
const SNAPSHOT_FILE = join(homedir(), '.dsh', 'subscriptions-snapshot.json')
|
|
6
|
-
|
|
7
|
-
export async function persistPoolSnapshot(accounts) {
|
|
8
|
-
try {
|
|
9
|
-
const data = JSON.stringify({
|
|
10
|
-
savedAt: Date.now(),
|
|
11
|
-
accounts: (accounts || []).map((a) => ({
|
|
12
|
-
ref: a.ref || a.id,
|
|
13
|
-
cooldownUntil: a.cooldownUntil || 0,
|
|
14
|
-
quota: a.quota || null,
|
|
15
|
-
status: a.status || 'active'
|
|
16
|
-
}))
|
|
17
|
-
}, null, 2)
|
|
18
|
-
await writeFile(SNAPSHOT_FILE, data, 'utf8')
|
|
19
|
-
return true
|
|
20
|
-
} catch {
|
|
21
|
-
return false
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export async function recoverPoolSnapshot() {
|
|
26
|
-
try {
|
|
27
|
-
const text = await readFile(SNAPSHOT_FILE, 'utf8')
|
|
28
|
-
const json = JSON.parse(text)
|
|
29
|
-
return json && json.accounts ? json.accounts : []
|
|
30
|
-
} catch {
|
|
31
|
-
return []
|
|
32
|
-
}
|
|
33
|
-
}
|
package/lib/telemetry.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto'
|
|
2
|
-
|
|
3
|
-
const USER_AGENTS = [
|
|
4
|
-
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
|
|
5
|
-
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
|
|
6
|
-
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
|
|
7
|
-
'VSCode/1.98.0 (Windows_NT x64)',
|
|
8
|
-
'Cursor/0.45.6 (Darwin x64)'
|
|
9
|
-
]
|
|
10
|
-
|
|
11
|
-
export function getRandomUserAgent(seed) {
|
|
12
|
-
if (!seed) return USER_AGENTS[0]
|
|
13
|
-
const hash = createHash('md5').update(String(seed)).digest('hex')
|
|
14
|
-
const idx = parseInt(hash.slice(0, 4), 16) % USER_AGENTS.length
|
|
15
|
-
return USER_AGENTS[idx]
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function generateIdeTelemetryHeaders(sessionId, clientType = 'vscode') {
|
|
19
|
-
const sid = sessionId || 'session-' + Math.random().toString(36).slice(2, 10)
|
|
20
|
-
const machineHash = createHash('sha256').update(sid).digest('hex').slice(0, 32)
|
|
21
|
-
return {
|
|
22
|
-
'vscode-sessionid': sid,
|
|
23
|
-
'vscode-machineid': machineHash,
|
|
24
|
-
'editor-version': clientType === 'cursor' ? 'cursor/0.45.6' : 'vscode/1.98.0',
|
|
25
|
-
'editor-plugin-version': 'dsh-sub-client/0.5.8',
|
|
26
|
-
'User-Agent': getRandomUserAgent(sid)
|
|
27
|
-
}
|
|
28
|
-
}
|
package/lib/token-speedometer.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
export class TokenSpeedometer {
|
|
2
|
-
constructor() {
|
|
3
|
-
this.startTime = null
|
|
4
|
-
this.tokenCount = 0
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
recordChunk(chunkText) {
|
|
8
|
-
if (!this.startTime) this.startTime = Date.now()
|
|
9
|
-
// Approximation: 1 token ~ 4 chars
|
|
10
|
-
const tokens = Math.max(1, Math.ceil((chunkText || '').length / 4))
|
|
11
|
-
this.tokenCount += tokens
|
|
12
|
-
return this.getCurrentSpeed()
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
getCurrentSpeed() {
|
|
16
|
-
if (!this.startTime) return 0
|
|
17
|
-
const elapsedSec = (Date.now() - this.startTime) / 1000
|
|
18
|
-
if (elapsedSec <= 0) return 0
|
|
19
|
-
return Math.round((this.tokenCount / elapsedSec) * 10) / 10
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
reset() {
|
|
23
|
-
this.startTime = null
|
|
24
|
-
this.tokenCount = 0
|
|
25
|
-
}
|
|
26
|
-
}
|
package/lib/zero-trace-logger.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
const SENSITIVE_PATTERNS = [
|
|
2
|
-
/Bearer\s+([^\s,"'}]+)/gi,
|
|
3
|
-
/(access_token|refresh_token|token|api_key|secret|password|authorization)=([^&\s]+)/gi,
|
|
4
|
-
/("?(?:accessToken|refreshToken|apiKey|secretKey|token)"?\s*:\s*")([^"]+)(")/gi
|
|
5
|
-
]
|
|
6
|
-
|
|
7
|
-
export function sanitizeLogText(text) {
|
|
8
|
-
if (typeof text !== 'string') {
|
|
9
|
-
try {
|
|
10
|
-
text = JSON.stringify(text)
|
|
11
|
-
} catch {
|
|
12
|
-
return '[Unserializable]'
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
let sanitized = text
|
|
17
|
-
// 1. Bearer
|
|
18
|
-
sanitized = sanitized.replace(/Bearer\s+([^\s,"'}]+)/gi, 'Bearer ***MASKED***')
|
|
19
|
-
|
|
20
|
-
// 2. Query param style
|
|
21
|
-
sanitized = sanitized.replace(/(access_token|refresh_token|token|api_key|secret|password|authorization)=([^&\s]+)/gi, '$1=***MASKED***')
|
|
22
|
-
|
|
23
|
-
// 3. JSON key style
|
|
24
|
-
sanitized = sanitized.replace(/("?(?:accessToken|refreshToken|apiKey|secretKey|token)"?\s*:\s*")([^"]+)(")/gi, '$1***MASKED***$3')
|
|
25
|
-
|
|
26
|
-
return sanitized
|
|
27
|
-
}
|