Spaces:
Paused
Paused
yaGeey commited on
Commit ·
4170f15
1
Parent(s): c22439c
logmemory & token queue priority
Browse files- src/browser.ts +2 -0
- src/index.ts +58 -49
- src/utils.ts +11 -0
src/browser.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { Browser, BrowserContext, Page } from 'playwright'
|
|
| 2 |
import { chromium } from 'playwright-extra'
|
| 3 |
import StealthPlugin from 'puppeteer-extra-plugin-stealth'
|
| 4 |
import { store } from './storage.js'
|
|
|
|
| 5 |
|
| 6 |
chromium.use(StealthPlugin())
|
| 7 |
|
|
@@ -118,5 +119,6 @@ export async function withPage<T>(fn: (page: Page) => Promise<T>): Promise<T | n
|
|
| 118 |
.close()
|
| 119 |
.catch(() => {})
|
| 120 |
}
|
|
|
|
| 121 |
}
|
| 122 |
}
|
|
|
|
| 2 |
import { chromium } from 'playwright-extra'
|
| 3 |
import StealthPlugin from 'puppeteer-extra-plugin-stealth'
|
| 4 |
import { store } from './storage.js'
|
| 5 |
+
import { logMemory } from './utils.js'
|
| 6 |
|
| 7 |
chromium.use(StealthPlugin())
|
| 8 |
|
|
|
|
| 119 |
.close()
|
| 120 |
.catch(() => {})
|
| 121 |
}
|
| 122 |
+
logMemory('Context closed')
|
| 123 |
}
|
| 124 |
}
|
src/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ import 'dotenv/config'
|
|
| 3 |
import PQueue from 'p-queue'
|
| 4 |
import { store, type AccessTokenResponse, type TokenResponse } from './storage.js'
|
| 5 |
import { handleError, closeContexts, withPage } from './browser.js'
|
| 6 |
-
import { delay } from './utils.js'
|
| 7 |
import { updateAllHashes, operations, updateHash } from './hashHandlers.js'
|
| 8 |
import './cronjobs.js'
|
| 9 |
|
|
@@ -11,6 +11,12 @@ const app = express()
|
|
| 11 |
const PORT = process.env.PORT || 3000
|
| 12 |
export const queue: PQueue = new PQueue({ concurrency: 1 })
|
| 13 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
app.get('/', (req, res) => {
|
| 15 |
res.send('alive')
|
| 16 |
})
|
|
@@ -46,60 +52,63 @@ app.get('/token', async (req, res) => {
|
|
| 46 |
return res.json({ access: store.access!, client: store.client! } satisfies TokenResponse)
|
| 47 |
}
|
| 48 |
|
| 49 |
-
const result = await queue.add(
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
return await withPage<TokenResponse>(async (page) => {
|
| 57 |
-
// access token
|
| 58 |
-
const accessTokenPromise = page
|
| 59 |
-
.waitForResponse(async (res) => res.url().includes('https://open.spotify.com/api/token') && res.status() === 200)
|
| 60 |
-
.then(async (res) => res.json() as Promise<AccessTokenResponse>)
|
| 61 |
-
|
| 62 |
-
// client token
|
| 63 |
-
const clientTokenPromise = page
|
| 64 |
-
.waitForResponse(
|
| 65 |
-
async (res) => res.url().includes('https://clienttoken.spotify.com/v1/clienttoken') && res.status() === 200,
|
| 66 |
-
)
|
| 67 |
-
.then(async (res) => {
|
| 68 |
-
const json = await res.json().catch(() => null)
|
| 69 |
-
const req = res.request()
|
| 70 |
-
const payload = req.postDataJSON()
|
| 71 |
-
|
| 72 |
-
return {
|
| 73 |
-
...json.granted_token,
|
| 74 |
-
client_version: payload.client_data.client_version,
|
| 75 |
-
}
|
| 76 |
-
})
|
| 77 |
-
|
| 78 |
-
// get tokens
|
| 79 |
-
const [accessTokenRes, clientTokenRes] = await Promise.all([
|
| 80 |
-
accessTokenPromise,
|
| 81 |
-
clientTokenPromise,
|
| 82 |
-
page.goto('https://open.spotify.com/', { waitUntil: 'domcontentloaded', timeout: 60000 }),
|
| 83 |
-
])
|
| 84 |
-
console.log('Token obtained successfully')
|
| 85 |
-
|
| 86 |
-
// format data
|
| 87 |
-
store.access = accessTokenRes
|
| 88 |
-
store.client = {
|
| 89 |
-
expiresAt: Date.now() + clientTokenRes.refresh_after_seconds * 1000,
|
| 90 |
-
token: clientTokenRes.token,
|
| 91 |
-
version: clientTokenRes.client_version,
|
| 92 |
}
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
if (!result) throw new Error('Failed to obtain token')
|
| 97 |
|
| 98 |
res.json(result)
|
| 99 |
})
|
| 100 |
|
| 101 |
app.get('/hashes', (req, res) => {
|
| 102 |
-
const raw = req.query.names
|
| 103 |
if (raw) {
|
| 104 |
const names = raw.split(',')
|
| 105 |
const filtered = Object.fromEntries(Object.entries(store.hashes).filter(([key]) => names.includes(key)))
|
|
@@ -110,7 +119,7 @@ app.get('/hashes', (req, res) => {
|
|
| 110 |
})
|
| 111 |
|
| 112 |
app.put('/hashes', async (req, res) => {
|
| 113 |
-
const raw = req.query.names
|
| 114 |
let tempHash = store.tempHashes
|
| 115 |
|
| 116 |
const names = raw ? raw.split(',') : null
|
|
|
|
| 3 |
import PQueue from 'p-queue'
|
| 4 |
import { store, type AccessTokenResponse, type TokenResponse } from './storage.js'
|
| 5 |
import { handleError, closeContexts, withPage } from './browser.js'
|
| 6 |
+
import { delay, logMemory } from './utils.js'
|
| 7 |
import { updateAllHashes, operations, updateHash } from './hashHandlers.js'
|
| 8 |
import './cronjobs.js'
|
| 9 |
|
|
|
|
| 11 |
const PORT = process.env.PORT || 3000
|
| 12 |
export const queue: PQueue = new PQueue({ concurrency: 1 })
|
| 13 |
|
| 14 |
+
app.use((req, res, next) => {
|
| 15 |
+
logMemory(`--> Start ${req.method} ${req.url}`)
|
| 16 |
+
res.on('finish', () => logMemory(`<-- End ${req.method} ${req.url}`))
|
| 17 |
+
next()
|
| 18 |
+
})
|
| 19 |
+
|
| 20 |
app.get('/', (req, res) => {
|
| 21 |
res.send('alive')
|
| 22 |
})
|
|
|
|
| 52 |
return res.json({ access: store.access!, client: store.client! } satisfies TokenResponse)
|
| 53 |
}
|
| 54 |
|
| 55 |
+
const result = await queue.add(
|
| 56 |
+
async () => {
|
| 57 |
+
// if there was a request before, check it's result before making new one
|
| 58 |
+
if (isTokenValid()) {
|
| 59 |
+
console.log('Returning cached data')
|
| 60 |
+
return { access: store.access!, client: store.client! } satisfies TokenResponse
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
}
|
| 62 |
+
|
| 63 |
+
return await withPage<TokenResponse>(async (page) => {
|
| 64 |
+
// access token
|
| 65 |
+
const accessTokenPromise = page
|
| 66 |
+
.waitForResponse(async (res) => res.url().includes('https://open.spotify.com/api/token') && res.status() === 200)
|
| 67 |
+
.then(async (res) => res.json() as Promise<AccessTokenResponse>)
|
| 68 |
+
|
| 69 |
+
// client token
|
| 70 |
+
const clientTokenPromise = page
|
| 71 |
+
.waitForResponse(
|
| 72 |
+
async (res) => res.url().includes('https://clienttoken.spotify.com/v1/clienttoken') && res.status() === 200,
|
| 73 |
+
)
|
| 74 |
+
.then(async (res) => {
|
| 75 |
+
const json = await res.json().catch(() => null)
|
| 76 |
+
const req = res.request()
|
| 77 |
+
const payload = req.postDataJSON()
|
| 78 |
+
|
| 79 |
+
return {
|
| 80 |
+
...json.granted_token,
|
| 81 |
+
client_version: payload.client_data.client_version,
|
| 82 |
+
}
|
| 83 |
+
})
|
| 84 |
+
|
| 85 |
+
// get tokens
|
| 86 |
+
const [accessTokenRes, clientTokenRes] = await Promise.all([
|
| 87 |
+
accessTokenPromise,
|
| 88 |
+
clientTokenPromise,
|
| 89 |
+
page.goto('https://open.spotify.com/', { waitUntil: 'domcontentloaded', timeout: 60000 }),
|
| 90 |
+
])
|
| 91 |
+
console.log('Token obtained successfully')
|
| 92 |
+
|
| 93 |
+
// format data
|
| 94 |
+
store.access = accessTokenRes
|
| 95 |
+
store.client = {
|
| 96 |
+
expiresAt: Date.now() + clientTokenRes.refresh_after_seconds * 1000,
|
| 97 |
+
token: clientTokenRes.token,
|
| 98 |
+
version: clientTokenRes.client_version,
|
| 99 |
+
}
|
| 100 |
+
return { access: store.access!, client: store.client! } satisfies TokenResponse
|
| 101 |
+
})
|
| 102 |
+
},
|
| 103 |
+
{ priority: 1 },
|
| 104 |
+
)
|
| 105 |
if (!result) throw new Error('Failed to obtain token')
|
| 106 |
|
| 107 |
res.json(result)
|
| 108 |
})
|
| 109 |
|
| 110 |
app.get('/hashes', (req, res) => {
|
| 111 |
+
const raw = String(req.query.names || '')
|
| 112 |
if (raw) {
|
| 113 |
const names = raw.split(',')
|
| 114 |
const filtered = Object.fromEntries(Object.entries(store.hashes).filter(([key]) => names.includes(key)))
|
|
|
|
| 119 |
})
|
| 120 |
|
| 121 |
app.put('/hashes', async (req, res) => {
|
| 122 |
+
const raw = String(req.query.names || '')
|
| 123 |
let tempHash = store.tempHashes
|
| 124 |
|
| 125 |
const names = raw ? raw.split(',') : null
|
src/utils.ts
CHANGED
|
@@ -11,3 +11,14 @@ export function generateRandomString(maxLength: number): string {
|
|
| 11 |
}
|
| 12 |
|
| 13 |
export const delay = (ms: number, jitter = 500) => new Promise((resolve) => setTimeout(resolve, ms + Math.random() * jitter))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
}
|
| 12 |
|
| 13 |
export const delay = (ms: number, jitter = 500) => new Promise((resolve) => setTimeout(resolve, ms + Math.random() * jitter))
|
| 14 |
+
|
| 15 |
+
export function logMemory(label: string = '') {
|
| 16 |
+
const usage = process.memoryUsage()
|
| 17 |
+
// RSS (Resident Set Size) — це загальний обсяг пам'яті процесу,
|
| 18 |
+
// включаючи C++ об'єкти (Chromium), що критично для Playwright.
|
| 19 |
+
const rss = Math.round(usage.rss / 1024 / 1024)
|
| 20 |
+
const heap = Math.round(usage.heapUsed / 1024 / 1024)
|
| 21 |
+
const external = Math.round(usage.external / 1024 / 1024)
|
| 22 |
+
|
| 23 |
+
console.log(`[MEM] ${label} -> RSS: ${rss} MB | Heap: ${heap} MB | Ext: ${external} MB`)
|
| 24 |
+
}
|