protexa / space_worker /supabase.py
admincybers2's picture
Upload folder using huggingface_hub
c190d85 verified
Raw
History Blame Contribute Delete
7.56 kB
import json
import logging
import time
from urllib import parse, request
import websocket
logger = logging.getLogger("celery-space.supabase")
def json_bytes(payload):
return json.dumps(payload, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
def realtime_topic(topic):
if topic.startswith("realtime:"):
return topic
return f"realtime:{topic}"
def extract_broadcast(message):
if isinstance(message, (bytes, bytearray)):
message = decode_binary_broadcast(message)
if message is None:
return None
if message.get("event") != "broadcast":
payload = message.get("payload")
if isinstance(payload, dict) and ("user_id" in payload or "message" in payload):
return message.get("event"), payload
return None
payload = message.get("payload") or {}
event = payload.get("event") or payload.get("type")
body = payload.get("payload", payload)
return event, body
def decode_binary_broadcast(frame):
try:
if not frame or frame[0] != 4 or len(frame) < 6:
return None
topic_size = frame[1]
event_size = frame[2]
metadata_size = frame[3]
payload_encoding = frame[4]
cursor = 5
topic = frame[cursor : cursor + topic_size].decode("utf-8")
cursor += topic_size
event = frame[cursor : cursor + event_size].decode("utf-8")
cursor += event_size
metadata = frame[cursor : cursor + metadata_size]
cursor += metadata_size
raw_payload = frame[cursor:]
if payload_encoding == 1:
payload = json.loads(raw_payload.decode("utf-8"))
else:
payload = raw_payload.decode("utf-8", errors="replace")
return {
"topic": topic,
"event": "broadcast",
"payload": {
"event": event,
"metadata": metadata.decode("utf-8", errors="replace"),
"payload": payload,
},
}
except Exception as exc:
logger.warning("failed to decode binary realtime frame: %s", exc)
return None
def normalize_realtime_message(message):
if isinstance(message, list) and len(message) >= 5:
join_ref, ref, topic, event, payload = message[:5]
return {
"join_ref": join_ref,
"ref": ref,
"topic": topic,
"event": event,
"payload": payload,
}
if isinstance(message, dict):
return message
return {"event": "unknown", "payload": message}
class RefCounter:
def __init__(self):
self.value = 0
def next(self):
self.value += 1
return str(self.value)
class SupabaseBroadcaster:
def __init__(self, settings):
self.settings = settings
def send(self, topic, event, payload, private=True):
self.settings.require_supabase()
encoded_topic = parse.quote(topic, safe="")
encoded_event = parse.quote(event, safe="")
private_query = "true" if private else "false"
url = (
f"{self.settings.supabase_url}/realtime/v1/api/broadcast/"
f"{encoded_topic}/events/{encoded_event}?private={private_query}"
)
req = request.Request(
url,
data=json_bytes(payload),
method="POST",
headers={
"apikey": self.settings.supabase_key,
"Authorization": f"Bearer {self.settings.supabase_key}",
"Content-Type": "application/json",
},
)
with request.urlopen(req, timeout=10) as response:
logger.info("broadcast delivered topic=%s event=%s status=%s", topic, event, response.status)
class RealtimeConnection:
def __init__(self, settings):
self.settings = settings
self.refs = RefCounter()
self.ws = None
def __enter__(self):
self.settings.require_supabase()
self.ws = websocket.create_connection(self.websocket_url(), timeout=30)
self.ws.settimeout(1)
self.join_inbox()
return self
def __exit__(self, exc_type, exc, traceback):
if self.ws is not None:
self.ws.close()
def websocket_url(self):
parsed = parse.urlparse(self.settings.supabase_url)
if parsed.scheme == "https":
scheme = "wss"
elif parsed.scheme == "http":
scheme = "ws"
else:
raise RuntimeError("SUPABASE_URL must start with http:// or https://")
query = parse.urlencode({"apikey": self.settings.supabase_key, "vsn": self.settings.realtime_vsn})
return parse.urlunparse((scheme, parsed.netloc, "/realtime/v1/websocket", "", query, ""))
def send(self, topic, event, payload, join_ref=None, ref=None):
if ref is None:
ref = self.refs.next()
if self.settings.realtime_vsn == "2.0.0":
message = [join_ref, ref, topic, event, payload]
else:
message = {
"topic": topic,
"event": event,
"payload": payload,
"ref": ref,
"join_ref": join_ref,
}
self.ws.send(json.dumps(message, separators=(",", ":"), ensure_ascii=False))
return ref
def join_inbox(self):
topic = realtime_topic(self.settings.inbox_topic)
join_ref = self.refs.next()
payload = {
"config": {
"broadcast": {"ack": True, "self": self.settings.broadcast_self},
"presence": {"enabled": False},
"postgres_changes": [],
"private": self.settings.inbox_private,
},
"access_token": self.settings.supabase_key,
}
self.send(topic, "phx_join", payload, join_ref=join_ref, ref=join_ref)
logger.info(
"joining supabase topic=%s private=%s vsn=%s self=%s",
self.settings.inbox_topic,
self.settings.inbox_private,
self.settings.realtime_vsn,
self.settings.broadcast_self,
)
def send_heartbeat(self):
self.send("phoenix", "heartbeat", {})
def iter_messages(self):
last_heartbeat = 0
while True:
now = time.monotonic()
if now - last_heartbeat >= self.settings.heartbeat_seconds:
self.send_heartbeat()
last_heartbeat = now
try:
raw_message = self.ws.recv()
except websocket.WebSocketTimeoutException:
continue
if isinstance(raw_message, (bytes, bytearray)):
logger.info("received binary realtime frame bytes=%s", len(raw_message))
yield raw_message
continue
message = normalize_realtime_message(json.loads(raw_message))
if message.get("event") == "phx_reply":
payload = message.get("payload") or {}
topic = message.get("topic")
status = payload.get("status")
if not (topic == "phoenix" and status == "ok"):
logger.info(
"received realtime reply topic=%s status=%s ref=%s",
topic,
status,
message.get("ref"),
)
else:
logger.info("received realtime event=%s topic=%s", message.get("event"), message.get("topic"))
yield message