Streaming
wss wss://ws.xsignalfeed.com/v1/stream
Menyambung
Stream menerima kunci Anda dengan tiga cara yang sama seperti REST. Dari server, gunakan header; gunakan parameter URL hanya jika header tidak bisa diatur. Jika kunci tidak sampai atau tidak diterima, koneksi terbuka lalu langsung ditutup dengan 4001, dan alasan penutupan menyebutkan sebabnya.
| Cara mengirim kunci | Gunakan saat |
|---|---|
Authorization: Bearer xsf_… | Disarankan untuk server; header yang sama dengan REST |
X-API-Key: xsf_… | Setara dengan yang di atas |
?api_key=xsf_… | Browser, dan klien yang tidak bisa mengatur header handshake. Kunci menjadi bagian dari URL, jadi jauhkan URL semacam itu dari log |
python — kunci di header
import websockets async with websockets.connect("wss://ws.xsignalfeed.com/v1/stream", additional_headers={"Authorization": "Bearer xsf_your_key"}) as ws: print(await ws.recv()) # {"t": "system.hello", ...}
| Parameter kueri | Arti |
|---|---|
| api_key | Kunci Anda, saat Anda tidak bisa menyetel header |
| last_id | Lanjutkan setelah sid ini. Diabaikan diam-diam kecuali kunci memiliki cakupan replay |
import asyncio, json, websockets async def main(): url = "wss://ws.xsignalfeed.com/v1/stream?api_key=xsf_your_key" async with websockets.connect(url) as ws: async for raw in ws: ev = json.loads(raw) if ev["t"].startswith("system."): continue print(ev["t"], ev["target"]["screen_name"], ev["d"].get("text", "")[:60]) asyncio.run(main())
import WebSocket from "ws"; const ws = new WebSocket("wss://ws.xsignalfeed.com/v1/stream?api_key=xsf_your_key"); ws.on("message", (raw) => { const ev = JSON.parse(raw); if (ev.t.startsWith("system.")) return; console.log(ev.t, ev.target.screen_name, (ev.d.text ?? "").slice(0, 60)); });
c, _, err := websocket.DefaultDialer.Dial("wss://ws.xsignalfeed.com/v1/stream?api_key=xsf_your_key", nil) for { _, raw, err := c.ReadMessage() if err != nil { break } var ev Event json.Unmarshal(raw, &ev) if strings.HasPrefix(ev.T, "system.") { continue } log.Println(ev.T, ev.Target.ScreenName) }
Frame
Tiga frame berasal dari kami, bukan dari X:
| Frame | Kapan |
|---|---|
system.hello | Frame pertama. targets adalah jumlah akun yang dipantau kunci ini saat ini (akun yang ditambahkan kemudian ikut tanpa menyambung ulang); replay_from mengembalikan last_id yang Anda kirim saat menyambung ulang dan bernilai null bila tidak ada, sehingga tidak ada yang diputar ulang |
system.ping | Dikirim saat hening sebagai keepalive, agar proxy yang menganggur tidak menutup soket |
system.error | Ada yang salah dengan koneksi; penutupan menyusul |
Kode penutupan
Handshake selalu berhasil dan penutupan langsung menyusul, sehingga pustaka klien Anda melihat kode dan alasannya, bukan sekadar error HTTP polos.
| Kode penutupan | Arti |
|---|---|
1012 | Layanan dimulai ulang saat pemeliharaan. Sambungkan ulang dengan backoff dan kirim sid terakhir yang Anda proses sebagai ?last_id=: peristiwa selama jeda diputar ulang, tidak ada yang hilang |
4001 | Kunci tidak diterima. Alasan penutupan menyebutkan sebabnya: missing_api_key (tidak ada kunci yang sampai ke kami: periksa nama header, atau apakah proxy atau pustaka klien Anda meneruskannya), invalid_api_key (kunci itu tidak ada: salah ketik, atau secret yang diganti oleh rotasi), key_revoked (kunci telah dicabut) |
4002 | Terlalu banyak koneksi bersamaan untuk kunci ini; frame system.error mendahului penutupan |
4003 | Kunci tidak memiliki cakupan stream |
Menyambung ulang dengan benar
Tulis ini sekali saja, bukan versi naifnya. Dua hal yang penting adalah backoff eksponensial dan menyimpan sid di tempat yang bertahan setelah proses dimulai ulang.
python — bentuk produksi
import asyncio, json, pathlib, random, websockets STATE = pathlib.Path("xsf.cursor") # survives restarts; a DB row is better still async def run(key, handle): backoff = 1 while True: last = STATE.read_text().strip() if STATE.exists() else None url = f"wss://ws.xsignalfeed.com/v1/stream?api_key={key}" + (f"&last_id={last}" if last else "") try: async with websockets.connect(url, ping_interval=20, max_size=2**22) as ws: backoff = 1 async for raw in ws: ev = json.loads(raw) if ev["t"].startswith("system."): continue handle(ev) # must be idempotent: dedupe on ev["id"] if ev.get("sid"): STATE.write_text(ev["sid"]) # only after handling except Exception as e: print("disconnected:", e) await asyncio.sleep(backoff + random.random()) # jitter, or every client returns at once backoff = min(60, backoff * 2)