Files
cell/scripts/http_playdate.c
2025-12-07 04:04:11 -06:00

249 lines
7.2 KiB
C

// http_playdate.c - HTTP module for Playdate using Playdate Network API
// Note: Playdate HTTP does not support SSL/HTTPS
#include "cell.h"
#include "pd_api.h"
#include <string.h>
#include <stdlib.h>
// Global Playdate API pointers - defined in main_playdate.c
extern const struct playdate_network *pd_network;
extern const struct playdate_sys *pd_sys;
#if TARGET_EXTENSION
// Context for async HTTP fetch
typedef struct {
JSContext *js;
HTTPConnection *conn;
uint8_t *data;
size_t data_len;
size_t data_cap;
int complete;
int success;
int status_code;
} http_fetch_ctx;
static void http_response_callback(HTTPConnection *conn) {
http_fetch_ctx *ctx = (http_fetch_ctx *)pd_network->http->getUserdata(conn);
if (!ctx) return;
ctx->status_code = pd_network->http->getResponseStatus(conn);
// Read all available data
while (1) {
size_t avail = pd_network->http->getBytesAvailable(conn);
if (avail == 0) break;
// Grow buffer if needed
if (ctx->data_len + avail > ctx->data_cap) {
size_t new_cap = ctx->data_cap * 2;
if (new_cap < ctx->data_len + avail) new_cap = ctx->data_len + avail + 4096;
uint8_t *new_data = realloc(ctx->data, new_cap);
if (!new_data) {
ctx->success = 0;
ctx->complete = 1;
return;
}
ctx->data = new_data;
ctx->data_cap = new_cap;
}
int read = pd_network->http->read(conn, ctx->data + ctx->data_len, (unsigned int)avail);
if (read < 0) {
ctx->success = 0;
ctx->complete = 1;
return;
}
ctx->data_len += read;
}
}
static void http_complete_callback(HTTPConnection *conn) {
http_fetch_ctx *ctx = (http_fetch_ctx *)pd_network->http->getUserdata(conn);
if (!ctx) return;
// Read any remaining data
http_response_callback(conn);
ctx->success = (ctx->status_code >= 200 && ctx->status_code < 400);
ctx->complete = 1;
}
static void http_closed_callback(HTTPConnection *conn) {
http_fetch_ctx *ctx = (http_fetch_ctx *)pd_network->http->getUserdata(conn);
if (!ctx) return;
ctx->complete = 1;
}
// Parse URL into host, port, path, and check if HTTPS
static int parse_url(const char *url, char **host, int *port, char **path, int *is_https) {
*host = NULL;
*path = NULL;
*port = 80;
*is_https = 0;
const char *p = url;
// Check scheme
if (strncmp(p, "https://", 8) == 0) {
*is_https = 1;
*port = 443;
p += 8;
} else if (strncmp(p, "http://", 7) == 0) {
p += 7;
} else {
return -1; // Invalid scheme
}
// Find end of host (either :, /, or end of string)
const char *host_start = p;
const char *host_end = p;
while (*host_end && *host_end != ':' && *host_end != '/') host_end++;
size_t host_len = host_end - host_start;
*host = malloc(host_len + 1);
if (!*host) return -1;
memcpy(*host, host_start, host_len);
(*host)[host_len] = '\0';
p = host_end;
// Check for port
if (*p == ':') {
p++;
*port = atoi(p);
while (*p && *p != '/') p++;
}
// Get path (default to "/" if none)
if (*p == '/') {
*path = strdup(p);
} else {
*path = strdup("/");
}
if (!*path) {
free(*host);
*host = NULL;
return -1;
}
return 0;
}
// Performs a blocking HTTP GET and returns a QuickJS Blob of the body
static JSValue js_fetch_playdate(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
if (argc < 1 || !JS_IsString(argv[0]))
return JS_ThrowTypeError(ctx, "fetch: URL string required");
if (!pd_network || !pd_network->http) {
return JS_ThrowInternalError(ctx, "fetch: Playdate network API not available");
}
const char *url = JS_ToCString(ctx, argv[0]);
if (!url) return JS_ThrowTypeError(ctx, "fetch: invalid URL");
char *host = NULL;
char *path = NULL;
int port = 80;
int is_https = 0;
if (parse_url(url, &host, &port, &path, &is_https) < 0) {
JS_FreeCString(ctx, url);
return JS_ThrowTypeError(ctx, "fetch: failed to parse URL");
}
JS_FreeCString(ctx, url);
// Playdate doesn't support HTTPS
if (is_https) {
free(host);
free(path);
return JS_ThrowTypeError(ctx, "fetch: HTTPS not supported on Playdate");
}
// Create HTTP connection
HTTPConnection *conn = pd_network->http->newConnection(host, port, 0);
free(host);
if (!conn) {
free(path);
return JS_ThrowInternalError(ctx, "fetch: failed to create connection");
}
// Set up context
http_fetch_ctx fetch_ctx = {0};
fetch_ctx.js = ctx;
fetch_ctx.conn = conn;
fetch_ctx.data = malloc(4096);
fetch_ctx.data_cap = 4096;
fetch_ctx.data_len = 0;
fetch_ctx.complete = 0;
fetch_ctx.success = 0;
if (!fetch_ctx.data) {
pd_network->http->release(conn);
free(path);
return JS_ThrowInternalError(ctx, "fetch: malloc failed");
}
pd_network->http->setUserdata(conn, &fetch_ctx);
pd_network->http->setResponseCallback(conn, http_response_callback);
pd_network->http->setRequestCompleteCallback(conn, http_complete_callback);
pd_network->http->setConnectionClosedCallback(conn, http_closed_callback);
pd_network->http->setConnectTimeout(conn, 30000); // 30 second timeout
pd_network->http->setReadTimeout(conn, 30000);
// Start the GET request
PDNetErr err = pd_network->http->get(conn, path, NULL, 0);
free(path);
if (err != NET_OK) {
free(fetch_ctx.data);
pd_network->http->release(conn);
return JS_ThrowInternalError(ctx, "fetch: request failed with error %d", err);
}
// Poll until complete (blocking)
// Note: This is a simple blocking implementation. In a real game,
// you'd want to use async callbacks instead.
while (!fetch_ctx.complete) {
// Small delay to avoid busy-waiting
pd_sys->delay(10);
}
pd_network->http->close(conn);
pd_network->http->release(conn);
if (!fetch_ctx.success) {
free(fetch_ctx.data);
return JS_ThrowTypeError(ctx, "fetch: request failed (status %d)", fetch_ctx.status_code);
}
// Return a Blob wrapping the data
JSValue blob = js_new_blob_stoned_copy(ctx, fetch_ctx.data, fetch_ctx.data_len);
free(fetch_ctx.data);
return blob;
}
#else
// Simulator/non-extension build - provide stub
static JSValue js_fetch_playdate(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
return JS_ThrowInternalError(ctx, "fetch: not available in simulator");
}
#endif
static const JSCFunctionListEntry js_http_funcs[] = {
JS_CFUNC_DEF("fetch", 2, js_fetch_playdate),
};
JSValue js_http_use(JSContext *js) {
JSValue obj = JS_NewObject(js);
JS_SetPropertyFunctionList(js, obj, js_http_funcs,
sizeof(js_http_funcs)/sizeof(js_http_funcs[0]));
return obj;
}