// 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 #include // 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; }