Cookbook
Production-ready recipes for common Gummble workflows.
Resilient paginated fetch
async function* iterApps(token: string) {
let cursor: string | undefined
while (true) {
const url = new URL('https://api.gummble.com/api/v1/apps')
url.searchParams.set('limit', '100')
if (cursor) url.searchParams.set('cursor', cursor)
const res = await fetch(url, {
headers: { Authorization: `Bearer ${token}` },
})
if (!res.ok) throw new Error(`HTTP ${res.status}`)
const { data, pagination } = await res.json()
for (const app of data) yield app
if (!pagination.hasMore) return
cursor = pagination.cursor
}
}Bulk download with rate limit awareness
import pLimit from 'p-limit'
const concurrency = pLimit(4) // stay under the backoff tier
async function downloadAll(screenIds: string[], token: string) {
await Promise.all(
screenIds.map(id =>
concurrency(() => downloadOne(id, token))
)
)
}Visual search from a clipboard image
async function searchFromClipboard(token: string) {
const items = await navigator.clipboard.read()
const item = items.find(i => i.types.some(t => t.startsWith('image/')))
if (!item) throw new Error('No image in clipboard')
const type = item.types.find(t => t.startsWith('image/'))!
const blob = await item.getType(type)
const form = new FormData()
form.set('image', blob, 'clipboard.png')
form.set('limit', '12')
const res = await fetch(
'https://api.gummble.com/api/v1/search/visual',
{
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
body: form,
}
)
return res.json()
}OAuth refresh helper (client-side)
async function refreshIfNeeded(t: {
access_token: string
refresh_token: string
client_id: string
expires_at: number
}) {
if (Date.now() < t.expires_at - 60_000) return t.access_token
const body = new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: t.refresh_token,
client_id: t.client_id,
resource: 'https://mcp.gummble.com',
})
const res = await fetch('https://api.gummble.com/oauth/token', {
method: 'POST',
headers: { 'content-type': 'application/x-www-form-urlencoded' },
body,
})
const data = await res.json()
t.access_token = data.access_token
t.refresh_token = data.refresh_token
t.expires_at = Date.now() + data.expires_in * 1000
return t.access_token
}