스트리밍

wss wss://ws.xsignalfeed.com/v1/stream

연결하기

스트림은 REST와 같은 세 가지 방법으로 키를 받습니다. 서버에서는 헤더를 쓰고, URL 파라미터는 헤더를 설정할 수 없을 때만 쓰세요. 키가 도착하지 않거나 거부되면 연결이 열린 뒤 곧바로 4001로 닫히며, 닫힘 사유가 이유를 알려 줍니다.

키 전달 방법사용하는 경우
Authorization: Bearer xsf_…서버에 권장. REST와 같은 헤더입니다
X-API-Key: xsf_…위와 동일
?api_key=xsf_…브라우저, 그리고 핸드셰이크 헤더를 설정할 수 없는 클라이언트. 키가 URL의 일부가 되므로 그런 URL이 로그에 남지 않게 하세요
python — 헤더로 키 전달
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", ...}
쿼리 파라미터의미
api_key헤더를 설정할 수 없을 때 사용하는 키
last_id이 sid 이후부터 재개합니다. 키에 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())

프레임

세 가지 프레임은 X가 아닌 저희 것입니다:

프레임시점
system.hello첫 프레임입니다. targets는 이 시점에 이 키가 모니터링하는 계정 수입니다(나중에 추가한 계정은 다시 연결하지 않아도 합류합니다). replay_from은 다시 연결할 때 보낸 last_id를 그대로 돌려주며, 보내지 않았다면 null이고 이 경우 아무것도 재전송되지 않습니다
system.ping침묵 중에 킵얼라이브로 전송되어, 유휴 프록시가 소켓을 닫지 않게 합니다
system.error연결에 문제가 있습니다. 곧 종료가 뒤따릅니다

종료 코드

핸드셰이크는 항상 성공하고 종료가 즉시 뒤따르므로, 클라이언트 라이브러리는 단순한 HTTP 오류 대신 코드와 사유를 볼 수 있습니다.

종료 코드의미
1012점검 중 서비스 재시작입니다. 백오프로 다시 연결하면서 마지막으로 처리한 sid를 ?last_id=로 전달하세요. 끊긴 동안의 이벤트가 재전송되어 아무것도 잃지 않습니다
4001키가 받아들여지지 않았습니다. 닫힘 사유가 이유를 알려 줍니다: missing_api_key(키가 도착하지 않음: 헤더 이름, 또는 프록시나 클라이언트 라이브러리가 전달하는지 확인), invalid_api_key(그런 키가 없음: 오타 또는 로테이션으로 바뀐 시크릿), key_revoked(폐기된 키)
4002이 키의 동시 연결 수가 너무 많습니다. 종료 전에 system.error 프레임이 전송됩니다
4003키에 stream 스코프가 없습니다

제대로 재연결하기

단순한 버전 말고 이것을 한 번만 제대로 작성해 두십시오. 중요한 것은 두 가지, 지수 백오프와 프로세스 재시작 후에도 남는 곳에 sid를 보관하는 것입니다.

python — 프로덕션 형태
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)