turn into a cell package
This commit is contained in:
248
net/http_playdate.c
Normal file
248
net/http_playdate.c
Normal file
@@ -0,0 +1,248 @@
|
||||
// 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;
|
||||
}
|
||||
Reference in New Issue
Block a user