# Examples ```tsx import type { FrontendPlugin, PluginSlotProps } from '@/lib/plugins/types'; const HelperModal = ({ ctx }: PluginSlotProps) => { return
Helper UI
; }; const plugin: FrontendPlugin = { name: 'checkout-helper', slots: { global: HelperModal }, bootstrap: (ctx) => { return ctx.checkout.registerGuard(async () => { return true; }); } }; export default plugin; ``` Remember to add the plugin to `app/plugins/registry.ts` so the frontend runtime loads it.
```tsx const plugin: FrontendPlugin = { name: 'summary-note', mounts: [ { selector: '[data-checkout-summary]', component: () =>
Note from plugin
} ] }; ```
```js module.exports = { name: 'audit-log', register(ctx) { ctx.routers.api.post('/plugins/audit', (req, res) => { ctx.logger.info('audit event', req.body); res.json({ status_overview: 'success' }); }); } }; ``` ```js module.exports = { name: 'purchase-sync', register(ctx) { ctx.hooks.onPurchaseFulfilled(async (payload) => { for (const item of payload.items) { ctx.logger.info('syncing fulfilled item', { eventId: payload.eventId, provider: payload.provider, user: payload.user.email, product: item.product.slug, plan: item.plan }); } }); } }; ``` Use this hook for provisioning and post-purchase sync work. It runs after Moonbase has already granted access.
# Getting Started Quick start [#quick-start] Create a file in `api/plugins` , for example `api/plugins/hello.plugin.js` . Export a `register` function or a plugin object. Restart the API ( `npm run dev` from repo root). ```js // api/plugins/hello.plugin.js module.exports = { name: 'hello', enabled: true, version: '0.1.0', register(ctx) { ctx.routers.api.get('/plugins/hello', (_req, res) => { res.json({ status_overview: 'success', status_message: 'HELLO' }); }); } }; ``` Use `.js`, `.cjs`, `.mjs`, or a folder with `index.js`. Create `app/plugins/hello.tsx` . Export a `FrontendPlugin` . Register it in `app/plugins/registry.ts` (import it and add it to `plugins` ). Ensure the app runs with `PluginHost` (already wired in `app/app/layout.tsx` ). ```tsx // app/plugins/hello.tsx import type { FrontendPlugin } from '@/lib/plugins/types'; const HelloPlugin: FrontendPlugin = { name: 'hello', enabled: true, version: '0.1.0', slots: { global: () =>
Hello
} }; export default HelloPlugin; ``` ```ts // app/plugins/registry.ts import helloPlugin from './hello'; export const plugins = [helloPlugin]; ``` Add your plugin import to `app/plugins/registry.ts` and include it in the exported `plugins` array. Add `version` to label plugin releases.
Project paths [#project-paths] Maintenance [#maintenance] * Backend: delete the plugin file from `api/plugins`. * Frontend: delete the plugin file from `app/plugins` and remove it from `app/plugins/registry.ts`. Add `enabled: false` to backend or frontend plugins to skip loading without deleting the file. The API logs that the plugin is disabled and does not run it. # Moonbase Plugin System import { Eye, Link2 } from 'lucide-react'; Moonbase ships with two plugin runtimes: a backend loader for the API and a frontend runtime for the Next.js app. Backend plugins are loaded from disk at runtime; frontend plugins are compiled into the app and registered explicitly. At a glance [#-at-a-glance] Two runtimes [#two-runtimes] * Backend plugins run inside the API and can register routes, patch services, or listen to purchase flows. * Frontend plugins run inside the app and can render UI, open modals, and block checkout via guards. Choose the backend for data and workflows, and the frontend for UI and flow control. What a plugin can do [#what-a-plugin-can-do] Quick links [#-quick-links] # Troubleshooting Confirm the API is running, the plugin file exists, and the plugin is registered where required. * Ensure the file is in `api/plugins` and ends with `.js`, `.cjs`, or `.mjs`. * Check API logs for "Failed to load plugin". * Make sure the plugin exports `register` or is a function. * Confirm the plugin file lives in `app/plugins` and exports a default plugin object with a `name`. * Ensure the plugin is registered in `app/plugins/registry.ts`. * Make sure `PluginHost` wraps the app in `app/app/layout.tsx`. * If using `mounts`, verify the selector exists in the DOM. * Use `api.createCheckoutSession` or `api.createCartCheckout`, which call guards. * If you call the API directly, call `runCheckoutGuards` first. # Backend Plugin Context Every backend plugin gets a `PluginContext` object. Context map [#context-map] * `app`: Express app instance. * `env`: `process.env`. * `logger`: Moonbase logger. * `hooks`: backend hooks including `onRoutesReady`, `onPurchaseFulfilled`, and `onShutdown`. * `paths`: `rootDir`, `pluginsDir`. * `routers.api` * `routers.dashboard` * `routers.purchase` * `services.API` (database access) * `services.stripe` * `services.coinbaseCommerce` * `services.paypalCheckout` * `services.emailService` * `modules.purchase` (route module; can still be wrapped or patched) * `core.controllers`, `core.models`, `core.helpers`, `core.utils` If your plugin only needs to run after a checkout is fulfilled, use `ctx.hooks.onPurchaseFulfilled(...)`. Reach for `ctx.modules.purchase` only when you truly need to wrap provider-specific behavior. Examples [#examples] ```js module.exports = { name: 'status-endpoint', register(ctx) { ctx.routers.api.get('/plugins/status', (_req, res) => { res.json({ status_overview: 'success', status_message: 'OK' }); }); } }; ``` ```js module.exports = { name: 'provisioner', register(ctx) { ctx.hooks.onPurchaseFulfilled(async (payload) => { for (const item of payload.items) { if (item.product.slug !== 'game-server-basic') continue; ctx.logger.info('provisioning server', { user: payload.user.email, product: item.product.slug, plan: item.plan, quantity: item.quantity }); } }); } }; ``` ```js module.exports = { name: 'stripe-tagger', register(ctx) { const original = ctx.services.stripe.checkout.sessions.create.bind( ctx.services.stripe.checkout.sessions ); ctx.services.stripe.checkout.sessions.create = async (params, options) => { return original({ ...params, metadata: { ...params.metadata, tag: 'plugin' } }, options); }; } }; ``` # Backend Hooks The backend loader exposes three hook points: void', description: 'Runs after routes are registered.', required: true }, 'hooks.onPurchaseFulfilled(callback)': { type: '(callback) => void', description: 'Runs after Moonbase finishes fulfilling a successful checkout, including free checkouts.', required: true }, 'hooks.onShutdown(callback)': { type: '(callback) => void', description: 'Runs when the API is shutting down.', required: true } }} /> Use `onPurchaseFulfilled` for post-purchase integrations like provisioning, syncing licenses, or notifying third-party services. Prefer it over wrapping payment-provider webhooks when you only need logic after Moonbase has already granted access. Purchase fulfilled payload [#purchase-fulfilled-payload] The `onPurchaseFulfilled` callback receives a single `payload` object: ', description: 'Products that were fulfilled in the order.', required: true } }} /> Each item in `payload.items` includes: ```js module.exports = { name: 'fulfillment-example', register(ctx) { ctx.hooks.onRoutesReady(() => { ctx.logger.info('routes ready'); }); ctx.hooks.onPurchaseFulfilled(async (payload) => { for (const item of payload.items) { ctx.logger.info('fulfilled purchase item', { eventId: payload.eventId, provider: payload.provider, userId: payload.userId, product: item.product.slug, plan: item.plan, quantity: item.quantity }); } }); ctx.hooks.onShutdown(() => { ctx.logger.info('api shutting down'); }); } }; ``` # Backend Runtime Overview The backend plugin loader scans for JavaScript plugins at startup and loads them in order. Plugin locations [#plugin-locations] The loader checks these directories (first match wins): `plugins` `api/plugins` the packaged plugins directory next to the loader (internal fallback) In most cases you should place plugins in `api/plugins`. Plugins load in alphabetical order by filename. Supported files [#supported-files] Plugin shape [#plugin-shape] ```js module.exports = { name: 'my-plugin', enabled: true, version: '0.1.0', register(ctx) { ctx.logger.info('plugin loaded'); } }; ``` ```js module.exports = function register(ctx) { // ... }; ``` If you omit `name`, the filename becomes the plugin name. `init(ctx)` is treated the same as `register(ctx)`. The loader logs successful and failed plugins at startup. If `enabled` is `false`, the plugin is skipped and the API logs that it is disabled. # Plugin Context API Each plugin receives a `ctx` object through slots and bootstrap. `ctx.api` sends requests to `/api/proxy`, adds the current auth token as a `Token` header, and includes credentials. Use `ctx.api` to call backend endpoints through the proxy. Use localStorage, sessionStorage, and cookies safely. ```ts ctx.storage.set('myKey', 'value'); ctx.storage.setCookie('myCookie', 'value', { maxAge: 3600 }); ``` Plugins can communicate using a simple event bus. ```ts const unsubscribe = ctx.events.on('route:change', (route) => { ctx.logger.info('route changed', route); }); ``` ```ts const { pathname, search } = ctx.route.get(); ``` ```ts ctx.checkout.registerGuard(async (context) => { return true; }); ``` # Checkout Guards Checkout guards run inside the API helper methods: * `app/lib/api.ts:createCheckoutSession` * `app/lib/api.ts:createCartCheckout` If you trigger checkout by calling the API directly, call `runCheckoutGuards` first. ```ts type CheckoutGuardContext = { type: 'cart' | 'product'; items?: Array<{ productId: string; plan?: string; quantity?: number }>; productId?: string; plan?: string; paymentMethod?: string; }; ``` ```tsx const plugin: FrontendPlugin = { name: 'license-guard', bootstrap: (ctx) => { return ctx.checkout.registerGuard(async (context) => { if (context?.type === 'product') { const response = await ctx.api.post('/api/plugins/license/check', { productId: context.productId }); return response.ok; } return true; }); } }; ``` Return `false` to cancel checkout. # Frontend Runtime Overview The frontend plugin runtime lives in `app/lib/plugins` and runs in the browser. Plugin implementations live in `app/plugins` and are registered in `app/plugins/registry.ts`. Lifecycle [#lifecycle] `PluginHost` in `app/lib/plugins/PluginHost.tsx` wraps the application. `PluginProvider` creates the plugin context (API, storage, events, route). Each plugin may run `bootstrap(ctx)` once and return a cleanup function. The registry ( `app/plugins/registry.ts` ) exports the plugin list used by the runtime. Registration [#registration] Add new frontend plugins to the registry so the runtime can pick them up. ```ts // app/plugins/registry.ts import myPlugin from './my-plugin'; export const plugins = [myPlugin]; ``` The list order is the execution order for slots and bootstrap hooks. Frontend plugins run in the browser. Add `use client` to plugin files that use hooks or browser APIs. Plugin shape [#plugin-shape] ```tsx import type { FrontendPlugin } from '@/lib/plugins/types'; const plugin: FrontendPlugin = { name: 'my-plugin', version: '0.1.0', enabled: true, bootstrap: (ctx) => { ctx.logger.info('plugin booted'); return () => ctx.logger.info('plugin cleaned up'); }, slots: { global: ({ ctx }) =>
Global UI
}, mounts: [ { selector: '[data-plugin-anchor="checkout-summary"]', component: ({ ctx }) =>
Injected UI
, order: 10 } ] }; export default plugin; ``` `enabled` can be a boolean or a function. When `enabled` returns false, the plugin is skipped. # Slots and Portals Frontend plugins can render UI in two ways: Slots render wherever the app includes ``. The default host already renders the `global` slot in `app/lib/plugins/PluginHost.tsx`. ```tsx const plugin: FrontendPlugin = { name: 'banner', slots: { global: () => (
New plugin loaded
) } }; ```
Portals let plugins mount components into any DOM selector via `mounts`. The runtime uses a `MutationObserver` to wait for the element. ```tsx const plugin: FrontendPlugin = { name: 'checkout-badge', mounts: [ { selector: '[data-checkout-summary]', component: () => Verified, once: true, order: 20 } ] }; ```
* Prefer stable `data-` attributes for selectors. * `once: true` stops observing after the first successful mount. * `order` controls rendering order when multiple plugins target the same element. # Authentication Only call the authentication endpoint from trusted environments. Avoid storing raw passwords in client code. Login flow [#login-flow] Copy your public API key from the console output on first startup. Send credentials to `POST /api/auth/user` . Store the JWT from `status_data.jwt` securely. Attach `Authorization` and `Token` headers to user scoped requests. Request body [#request-body] ```json { "username": "your_username", "password": "your_password", "hwid": "optional_hwid" } ``` ```json { "status_message": "USER_LOGGED_IN", "status_overview": "success", "status_code": 200, "status_data": { "user": "your_username", "jwt": "" } } ``` Example request [#example-request] cURL JavaScript Python ```bash curl -X POST "https://your-moonbase-host/api/auth/user" \ -H "Content-Type: application/json" \ -d '{ "username": "your_username", "password": "your_password", "hwid": "optional_hwid" }' ``` ```javascript const res = await fetch("https://your-moonbase-host/api/auth/user", { method: "POST", headers: { "Content-Type": "application/json", "Accept": "application/json" }, body: JSON.stringify({ username: "your_username", password: "your_password", hwid: "optional_hwid" }) }); const data = await res.json(); console.log(data); ``` ```python import requests res = requests.post( "https://your-moonbase-host/api/auth/user", json={ "username": "your_username", "password": "your_password", "hwid": "optional_hwid" }, headers={"Content-Type": "application/json"}, timeout=10, ) print(res.json()) ``` # Errors and status codes Most errors follow the same envelope so clients can handle them consistently. Example ```json { "status_message": "ACCESS_DENIED", "status_overview": "failed", "status_code": 403, "status_data": { "reason": "Token does not have access to this resource." } } ``` * 401 UNAUTHORIZED: Credentials missing or invalid. * 403 FORBIDDEN: Token exists but is not allowed. * 403 ACCESS\_DENIED: The user does not own the resource. * 404 NOT\_FOUND: The resource does not exist or is hidden. * 429 RATE\_LIMITED: Too many requests in a short window. When you receive a 429, back off and retry with exponential delay. Avoid running parallel retries. # User files This endpoint returns binary data. Handle the response as a stream or blob. Request flow [#request-flow] Copy your public API key from the console output on first startup. Authenticate with `POST /api/auth/user` to get a JWT. Call `GET /api/user/files/:file` with headers and save the file. Parameters and headers [#parameters-and-headers] ```http GET /api/user/files/:file Authorization: Token: ``` ```text Content-Disposition: attachment; filename="original_filename.ext" Content-Type: application/octet-stream Content-Length: ``` Example request [#example-request] cURL JavaScript Python ```bash curl -L "https://your-moonbase-host/api/user/files/your_file_name" \ -H "Authorization: " \ -H "Token: " \ -o downloaded_file.ext ``` ```javascript const res = await fetch( "https://your-moonbase-host/api/user/files/your_file_name", { method: "GET", headers: { Authorization: "", Token: "" } } ); const blob = await res.blob(); const url = window.URL.createObjectURL(blob); ``` ```python import requests with requests.get( "https://your-moonbase-host/api/user/files/your_file_name", headers={ "Authorization": "", "Token": "" }, stream=True, timeout=30, ) as res: res.raise_for_status() with open("downloaded_file.ext", "wb") as handle: for chunk in res.iter_content(chunk_size=8192): handle.write(chunk) ``` # Moonbase API import { Settings, Zap } from 'lucide-react'; Moonbase API is the backend surface for Moonbase apps and plugins. Use it to authenticate users, retrieve profile data, and fetch user files. All requests include your public API key. After login, include the JWT in the Token header for user scoped endpoints. Quick start [#-quick-start] Copy your public API key from the console output on first startup. Authenticate with `POST /api/auth/user` to receive a JWT. Use the JWT and API key for profile and file requests. Environment setup [#-environment-setup] ```bash # Replace with your Moonbase API host. NEXT_PUBLIC_API_URL=https://your-moonbase-host # Printed once in the server console on first startup. NEXT_PUBLIC_API_KEY=your_public_key_here ``` Jump in [#jump-in] # API Reference All endpoints accept JSON and return JSON unless noted. Use `Authorization` for the public API key and `Token` for the user JWT. Your public API key is printed to the server console on first startup. Authenticates a user and returns a JWT for subsequent requests. ```json { "username": "your_username", "password": "your_password", "hwid": "optional_hwid" } ``` ```json { "status_message": "USER_LOGGED_IN", "status_overview": "success", "status_code": 200, "status_data": { "user": "your_username", "jwt": "" } } ``` * 200 SUCCESS * 401 UNAUTHORIZED * 403 FORBIDDEN * 429 RATE\_LIMITED cURL JavaScript ```bash curl -X POST "https://your-moonbase-host/api/auth/user" \ -H "Content-Type: application/json" \ -d '{ "username": "your_username", "password": "your_password", "hwid": "optional_hwid" }' ``` ```javascript const res = await fetch("https://your-moonbase-host/api/auth/user", { method: "POST", headers: { "Content-Type": "application/json", "Accept": "application/json" }, body: JSON.stringify({ username: "your_username", password: "your_password", hwid: "optional_hwid" }) }); const data = await res.json(); ``` Returns profile data for the authenticated user. ```http GET /api/user/info Authorization: Token: ``` ```json { "status_message": "USER_INFO_RETRIEVED", "status_overview": "success", "status_code": 200, "status_data": { "user": { ... } } } ``` * 200 SUCCESS * 401 UNAUTHORIZED * 403 FORBIDDEN * 404 NOT\_FOUND * 429 RATE\_LIMITED cURL JavaScript ```bash curl -X GET "https://your-moonbase-host/api/user/info" \ -H "Authorization: " \ -H "Token: " ``` ```javascript const res = await fetch("https://your-moonbase-host/api/user/info", { method: "GET", headers: { Authorization: "", Token: "" } }); const data = await res.json(); ``` Downloads a user file. This endpoint returns binary data on success. ```http GET /api/user/files/:file Authorization: Token: ``` ```text Content-Disposition: attachment; filename="original_filename.ext" Content-Type: application/octet-stream Content-Length: ``` * 200 SUCCESS * 401 UNAUTHORIZED * 403 ACCESS\_DENIED * 404 NOT\_FOUND * 429 RATE\_LIMITED On success, the body is raw file data. Handle it as a stream or blob depending on your client. cURL JavaScript ```bash curl -L "https://your-moonbase-host/api/user/files/your_file_name" \ -H "Authorization: " \ -H "Token: " \ -o downloaded_file.ext ``` ```javascript const res = await fetch( "https://your-moonbase-host/api/user/files/your_file_name", { method: "GET", headers: { Authorization: "", Token: "" } } ); const blob = await res.blob(); const url = window.URL.createObjectURL(blob); ``` # User profile This endpoint requires your public API key and the user's JWT. Request flow [#request-flow] Copy your public API key from the console output on first startup. Authenticate with `POST /api/auth/user` to get a JWT. Call `GET /api/user/info` with `Authorization` and `Token` headers. Headers [#headers] ```http GET /api/user/info Authorization: Token: ``` ```json { "status_message": "USER_INFO_RETRIEVED", "status_overview": "success", "status_code": 200, "status_data": { "user": { "id": "user_123", "name": "your_username" } } } ``` Example request [#example-request] cURL JavaScript Python ```bash curl -X GET "https://your-moonbase-host/api/user/info" \ -H "Authorization: " \ -H "Token: " ``` ```javascript const res = await fetch("https://your-moonbase-host/api/user/info", { method: "GET", headers: { Authorization: "", Token: "" } }); const data = await res.json(); ``` ```python import requests res = requests.get( "https://your-moonbase-host/api/user/info", headers={ "Authorization": "", "Token": "" }, timeout=10, ) print(res.json()) ``` # Authentication examples Use the API base URL Use these examples to request a JWT for user-scoped endpoints. JavaScript TypeScript Python Java C# C++ Ruby Go Rust PHP Swift Lua ```javascript fetch('https://your-moonbase-host/api/auth/user', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify({ username: 'YOUR_USERNAME', password: 'YOUR_PASSWORD', hwid: 'OPTIONAL_HWID' }) }) .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error('Error:', error)); ``` ```typescript interface AuthRequest { username: string; password: string; hwid?: string; } interface AuthResponse { success: boolean; token?: string; message?: string; } const authenticateUser = async (credentials: AuthRequest): Promise => { try { const response = await fetch('https://your-moonbase-host/api/auth/user', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify(credentials) }); return await response.json() as AuthResponse; } catch (error) { throw new Error(`Authentication failed: ${error}`); } }; ``` ```python import requests import json from typing import Optional, Dict, Any def authenticate_user(username: str, password: str, hwid: Optional[str] = None) -> Dict[Any, Any]: """Authenticate user with the API""" url = 'https://your-moonbase-host/api/auth/user' payload = { 'username': username, 'password': password, 'hwid': hwid } try: response = requests.post( url, json=payload, headers={'Content-Type': 'application/json'}, timeout=10 ) response.raise_for_status() return response.json() except requests.exceptions.RequestException as e: print(f"Authentication error: {e}") raise # Usage result = authenticate_user('YOUR_USERNAME', 'YOUR_PASSWORD', 'OPTIONAL_HWID') print(json.dumps(result, indent=2)) ``` ```java import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.net.URI; import java.time.Duration; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.Map; public class AuthClient { private static final HttpClient client = HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(10)) .build(); public static String authenticateUser(String username, String password, String hwid) { try { ObjectMapper mapper = new ObjectMapper(); Map payload = Map.of( "username", username, "password", password, "hwid", hwid != null ? hwid : "" ); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://your-moonbase-host/api/auth/user")) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(mapper.writeValueAsString(payload))) .timeout(Duration.ofSeconds(30)) .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); return response.body(); } catch (Exception e) { throw new RuntimeException("Authentication failed", e); } } } ``` ```csharp using System; using System.Net.Http; using System.Text; using System.Text.Json; using System.Threading.Tasks; public class AuthClient { private static readonly HttpClient httpClient = new HttpClient(); public class AuthRequest { public string Username { get; set; } public string Password { get; set; } public string Hwid { get; set; } } public static async Task AuthenticateUserAsync(string username, string password, string hwid = null) { var request = new AuthRequest { Username = username, Password = password, Hwid = hwid }; var json = JsonSerializer.Serialize(request); var content = new StringContent(json, Encoding.UTF8, "application/json"); try { var response = await httpClient.PostAsync( "https://your-moonbase-host/api/auth/user", content); response.EnsureSuccessStatusCode(); return await response.Content.ReadAsStringAsync(); } catch (HttpRequestException ex) { throw new Exception($"Authentication failed: {ex.Message}"); } } } ``` ```cpp #include #include #include #include #include using json = nlohmann::json; struct WriteCallbackData { std::string data; }; static size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userp) { size_t totalSize = size * nmemb; static_cast(userp)->data.append( static_cast(contents), totalSize ); return totalSize; } std::string authenticateUser( const std::string& username, const std::string& password, const std::string& hwid = "" ) { CURL* curl = curl_easy_init(); if (!curl) { throw std::runtime_error("Failed to initialize CURL"); } json payload = { {"username", username}, {"password", password}, {"hwid", hwid} }; std::string jsonString = payload.dump(); WriteCallbackData response; struct curl_slist* headers = nullptr; headers = curl_slist_append(headers, "Content-Type: application/json"); curl_easy_setopt(curl, CURLOPT_URL, "https://your-moonbase-host/api/auth/user"); curl_easy_setopt(curl, CURLOPT_POST, 1L); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, jsonString.c_str()); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); CURLcode res = curl_easy_perform(curl); curl_slist_free_all(headers); curl_easy_cleanup(curl); if (res != CURLE_OK) { throw std::runtime_error( std::string("CURL request failed: ") + curl_easy_strerror(res) ); } return response.data; } int main() { try { std::string result = authenticateUser("alice", "s3cr3tP@ss", "HWID-12345"); auto jsonRes = json::parse(result); std::cout << "Server replied: " << jsonRes.dump(2) << std::endl; } catch (const std::exception& ex) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; } ``` ```ruby require 'net/http' require 'json' require 'uri' class AuthClient API_URL = 'https://your-moonbase-host/api/auth/user' def self.authenticate_user(username, password, hwid: nil) uri = URI(API_URL) payload = { username: username, password: password, hwid: hwid }.compact http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = uri.scheme == 'https' http.read_timeout = 30 request = Net::HTTP::Post.new(uri) request['Content-Type'] = 'application/json' request['Accept'] = 'application/json' request.body = payload.to_json begin response = http.request(request) case response when Net::HTTPSuccess JSON.parse(response.body) else raise "Authentication failed: #{response.code} #{response.message}" end rescue JSON::ParserError => e raise "Invalid JSON response: #{e.message}" rescue StandardError => e raise "Request failed: #{e.message}" end end end # Usage result = AuthClient.authenticate_user('YOUR_USERNAME', 'YOUR_PASSWORD', hwid: 'OPTIONAL_HWID') puts JSON.pretty_generate(result) ``` ```go package main import ( "bytes" "context" "encoding/json" "fmt" "net/http" "time" ) type AuthRequest struct { Username string `json:"username"` Password string `json:"password"` HWID string `json:"hwid,omitempty"` } type AuthResponse struct { Success bool `json:"success"` Token string `json:"token,omitempty"` Message string `json:"message,omitempty"` } func authenticateUser(ctx context.Context, username, password, hwid string) (*AuthResponse, error) { client := &http.Client{ Timeout: 30 * time.Second, } payload := AuthRequest{ Username: username, Password: password, HWID: hwid, } jsonData, err := json.Marshal(payload) if err != nil { return nil, fmt.Errorf("failed to marshal request: %w", err) } req, err := http.NewRequestWithContext(ctx, "POST", "https://your-moonbase-host/api/auth/user", bytes.NewBuffer(jsonData)) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("request failed: %w", err) } defer resp.Body.Close() var authResp AuthResponse if err := json.NewDecoder(resp.Body).Decode(&authResp); err != nil { return nil, fmt.Errorf("failed to decode response: %w", err) } return &authResp, nil } func main() { ctx := context.Background() result, err := authenticateUser(ctx, "YOUR_USERNAME", "YOUR_PASSWORD", "OPTIONAL_HWID") if err != nil { panic(err) } fmt.Printf("Authentication result: %+v\n", result) } ``` ```rust use reqwest::Client; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use tokio; #[derive(Serialize)] struct AuthRequest { username: String, password: String, #[serde(skip_serializing_if = "Option::is_none")] hwid: Option, } #[derive(Deserialize, Debug)] struct AuthResponse { success: bool, token: Option, message: Option, } #[tokio::main] async fn main() -> Result<(), Box> { let client = Client::builder() .timeout(std::time::Duration::from_secs(30)) .build()?; let auth_request = AuthRequest { username: "YOUR_USERNAME".to_string(), password: "YOUR_PASSWORD".to_string(), hwid: Some("OPTIONAL_HWID".to_string()), }; let response = client .post("https://your-moonbase-host/api/auth/user") .json(&auth_request) .header("Accept", "application/json") .send() .await?; match response.status() { reqwest::StatusCode::OK => { let auth_response: AuthResponse = response.json().await?; println!("Authentication successful: {:#?}", auth_response); } status => { let error_text = response.text().await?; eprintln!("Authentication failed with status {}: {}", status, error_text); } } Ok(()) } ``` ```php $username, 'password' => $password ]; if ($hwid !== null) { $payload['hwid'] = $hwid; } $context = stream_context_create([ 'http' => [ 'method' => 'POST', 'header' => [ 'Content-Type: application/json', 'Accept: application/json' ], 'content' => json_encode($payload), 'timeout' => self::TIMEOUT, 'ignore_errors' => true ] ]); $response = file_get_contents(self::API_URL, false, $context); if ($response === false) { throw new Exception('Failed to make HTTP request'); } $httpCode = null; if (isset($http_response_header)) { preg_match('/HTTP\/\d+\.\d+ (\d+)/', $http_response_header[0], $matches); $httpCode = (int)$matches[1]; } if ($httpCode >= 400) { throw new Exception("HTTP Error $httpCode: $response"); } $decodedResponse = json_decode($response, true); if (json_last_error() !== JSON_ERROR_NONE) { throw new Exception('Invalid JSON response: ' . json_last_error_msg()); } return $decodedResponse; } } // Usage try { $result = AuthClient::authenticateUser('YOUR_USERNAME', 'YOUR_PASSWORD', 'OPTIONAL_HWID'); echo json_encode($result, JSON_PRETTY_PRINT); } catch (Exception $e) { echo "Error: " . $e->getMessage() . PHP_EOL; } ?> ``` ```swift import Foundation struct AuthRequest: Codable { let username: String let password: String let hwid: String? } struct AuthResponse: Codable { let success: Bool let token: String? let message: String? } class AuthClient { static let shared = AuthClient() private let session = URLSession.shared func authenticateUser(username: String, password: String, hwid: String? = nil) async throws -> AuthResponse { guard let url = URL(string: "https://your-moonbase-host/api/auth/user") else { throw URLError(.badURL) } let authRequest = AuthRequest(username: username, password: password, hwid: hwid) let jsonData = try JSONEncoder().encode(authRequest) var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.setValue("application/json", forHTTPHeaderField: "Accept") request.httpBody = jsonData request.timeoutInterval = 30 let (data, response) = try await session.data(for: request) guard let httpResponse = response as? HTTPURLResponse else { throw URLError(.badServerResponse) } guard 200...299 ~= httpResponse.statusCode else { throw URLError(.badServerResponse) } return try JSONDecoder().decode(AuthResponse.self, from: data) } } // Usage Task { do { let result = try await AuthClient.shared.authenticateUser( username: "YOUR_USERNAME", password: "YOUR_PASSWORD", hwid: "OPTIONAL_HWID" ) print("Authentication result: \(result)") } catch { print("Authentication failed: \(error)") } } ``` ```lua local http = require("socket.http") local ltn12 = require("ltn12") local json = require("dkjson") local function authenticate_user(username, password, hwid) local api_url = os.getenv("NEXT_PUBLIC_API_URL") .. "/api/auth/user" local payload = json.encode({ username = username, password = password, hwid = hwid }) local response = {} local ok, code, headers, status = http.request{ url = api_url, method = "POST", headers = { ["Content-Type"] = "application/json", ["Accept"] = "application/json", ["Content-Length"] = tostring(#payload), }, source = ltn12.source.string(payload), sink = ltn12.sink.table(response), } if not ok or code ~= 200 then error("Authentication failed: HTTP " .. tostring(code)) end local body, err = json.decode(table.concat(response), 1, nil) if err then error("JSON decode error: " .. err) end return body end -- Usage local result = authenticate_user("YOUR_USERNAME", "YOUR_PASSWORD", "OPTIONAL_HWID") print(json.encode(result, { indent = true })) ``` # User files examples Use the API base URL This endpoint returns binary data, so save the response to disk or a blob. JavaScript TypeScript Python Java C# C++ Ruby Go Rust PHP Swift Lua ```javascript fetch('https://your-moonbase-host/api/user/files/your_file_name', { method: 'GET', headers: { 'Authorization': '', 'Token': '' } }) .then(response => { if (!response.ok) throw new Error('Download failed'); return response.blob(); }) .then(blob => { // Create download link const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'filename.ext'; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); }) .catch(error => console.error('Error:', error)); ``` ```typescript const downloadFile = async ( fileName: string, token: string, apiKey: string ): Promise => { try { const response = await fetch( `${process.env.NEXT_PUBLIC_API_URL}/api/user/files/${fileName}`, { method: 'GET', headers: { 'Authorization': apiKey, 'Token': token } } ); if (!response.ok) { throw new Error(`Download failed: ${response.statusText}`); } const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = fileName; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); } catch (error) { throw new Error(`File download failed: ${error}`); } }; ``` ```python import requests def download_file(file_name: str, token: str, api_key: str, save_path: str): """Download a file from the API""" url = f'https://your-moonbase-host/api/user/files/{file_name}' headers = { 'Authorization': api_key, 'Token': token } response = requests.get(url, headers=headers, stream=True, timeout=30) response.raise_for_status() with open(save_path, 'wb') as f: for chunk in response.iter_content(chunk_size=8192): f.write(chunk) print(f"File downloaded successfully to {save_path}") # Usage download_file('your_file_name', '', '', './downloaded_file.ext') ``` ```java import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; public class FileDownloadClient { private static final HttpClient client = HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(10)) .build(); public static void downloadFile(String fileName, String token, String apiKey, Path savePath) throws Exception { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://your-moonbase-host/api/user/files/" + fileName)) .header("Authorization", apiKey) .header("Token", token) .GET() .timeout(Duration.ofMinutes(5)) .build(); HttpResponse response = client.send( request, HttpResponse.BodyHandlers.ofByteArray() ); if (response.statusCode() == 200) { Files.write(savePath, response.body()); System.out.println("File downloaded successfully"); } else { throw new RuntimeException("Download failed: " + response.statusCode()); } } } ``` ```csharp using System; using System.IO; using System.Net.Http; using System.Threading.Tasks; public class FileDownloadClient { private static readonly HttpClient httpClient = new HttpClient(); public static async Task DownloadFileAsync( string fileName, string token, string apiKey, string savePath ) { var request = new HttpRequestMessage( HttpMethod.Get, $"https://your-moonbase-host/api/user/files/{fileName}" ); request.Headers.Add("Authorization", apiKey); request.Headers.Add("Token", token); var response = await httpClient.SendAsync(request); response.EnsureSuccessStatusCode(); using (var fs = new FileStream(savePath, FileMode.Create, FileAccess.Write)) { await response.Content.CopyToAsync(fs); } Console.WriteLine($"File downloaded to {savePath}"); } } ``` ```cpp #include #include #include #include static size_t WriteToFile(void* ptr, size_t size, size_t nmemb, std::ofstream* stream) { stream->write(static_cast(ptr), size * nmemb); return size * nmemb; } void downloadFile( const std::string& fileName, const std::string& token, const std::string& apiKey, const std::string& savePath ) { CURL* curl = curl_easy_init(); if (!curl) throw std::runtime_error("Failed to initialize CURL"); std::ofstream outFile(savePath, std::ios::binary); if (!outFile.is_open()) { curl_easy_cleanup(curl); throw std::runtime_error("Failed to open output file"); } std::string url = "https://your-moonbase-host/api/user/files/" + fileName; struct curl_slist* headers = nullptr; headers = curl_slist_append(headers, ("Authorization: " + apiKey).c_str()); headers = curl_slist_append(headers, ("Token: " + token).c_str()); curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteToFile); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &outFile); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); CURLcode res = curl_easy_perform(curl); curl_slist_free_all(headers); curl_easy_cleanup(curl); outFile.close(); if (res != CURLE_OK) { throw std::runtime_error("Download failed: " + std::string(curl_easy_strerror(res))); } } ``` ```ruby require 'net/http' require 'uri' class FileDownloadClient API_BASE = 'https://your-moonbase-host/api/user/files' def self.download_file(file_name, token, api_key, save_path) uri = URI("#{API_BASE}/#{file_name}") http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = uri.scheme == 'https' http.read_timeout = 300 request = Net::HTTP::Get.new(uri) request['Authorization'] = api_key request['Token'] = token response = http.request(request) case response when Net::HTTPSuccess File.open(save_path, 'wb') do |file| file.write(response.body) end puts "File downloaded to #{save_path}" else raise "Download failed: #{response.code} #{response.message}" end end end # Usage FileDownloadClient.download_file( 'your_file_name', '', '', './downloaded_file.ext' ) ``` ```go package main import ( "context" "fmt" "io" "net/http" "os" "time" ) func downloadFile(ctx context.Context, fileName, token, apiKey, savePath string) error { client := &http.Client{Timeout: 5 * time.Minute} url := fmt.Sprintf("https://your-moonbase-host/api/user/files/%s", fileName) req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return fmt.Errorf("failed to create request: %w", err) } req.Header.Set("Authorization", apiKey) req.Header.Set("Token", token) resp, err := client.Do(req) if err != nil { return fmt.Errorf("request failed: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("download failed with status: %d", resp.StatusCode) } outFile, err := os.Create(savePath) if err != nil { return fmt.Errorf("failed to create file: %w", err) } defer outFile.Close() _, err = io.Copy(outFile, resp.Body) if err != nil { return fmt.Errorf("failed to write file: %w", err) } fmt.Printf("File downloaded to %s\n", savePath) return nil } func main() { ctx := context.Background() err := downloadFile(ctx, "your_file_name", "", "", "./downloaded_file.ext") if err != nil { panic(err) } } ``` ```rust use reqwest::Client; use std::fs::File; use std::io::Write; use tokio; #[tokio::main] async fn main() -> Result<(), Box> { let client = Client::builder() .timeout(std::time::Duration::from_secs(300)) .build()?; let file_name = "your_file_name"; let url = format!("https://your-moonbase-host/api/user/files/{}", file_name); let response = client .get(&url) .header("Authorization", "") .header("Token", "") .send() .await?; if !response.status().is_success() { return Err(format!("Download failed: {}", response.status()).into()); } let bytes = response.bytes().await?; let mut file = File::create("./downloaded_file.ext")?; file.write_all(&bytes)?; println!("File downloaded successfully"); Ok(()) } ``` ```php [ 'method' => 'GET', 'header' => [ 'Authorization: ' . $apiKey, 'Token: ' . $token ], 'timeout' => 300, 'ignore_errors' => true ] ]); $fileContent = file_get_contents($url, false, $context); if ($fileContent === false) { throw new Exception('Failed to download file'); } // Check HTTP status code if (isset($http_response_header)) { preg_match('/HTTP\/\d+\.\d+ (\d+)/', $http_response_header[0], $matches); $httpCode = (int)$matches[1]; if ($httpCode !== 200) { throw new Exception("Download failed with HTTP code: $httpCode"); } } if (file_put_contents($savePath, $fileContent) === false) { throw new Exception('Failed to save file'); } echo "File downloaded successfully to $savePath\n"; } // Usage try { downloadFile('your_file_name', '', '', './downloaded_file.ext'); } catch (Exception $e) { echo "Error: " . $e->getMessage() . PHP_EOL; } ?> ``` ```swift import Foundation class FileDownloadClient { static let shared = FileDownloadClient() private let session = URLSession.shared func downloadFile( fileName: String, token: String, apiKey: String, savePath: URL ) async throws { guard let url = URL(string: "https://your-moonbase-host/api/user/files/\(fileName)") else { throw URLError(.badURL) } var request = URLRequest(url: url) request.httpMethod = "GET" request.setValue(apiKey, forHTTPHeaderField: "Authorization") request.setValue(token, forHTTPHeaderField: "Token") request.timeoutInterval = 300 let (tempURL, response) = try await session.download(for: request) guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { throw URLError(.badServerResponse) } try FileManager.default.moveItem(at: tempURL, to: savePath) print("File downloaded to \(savePath.path)") } } // Usage Task { do { let savePath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] .appendingPathComponent("downloaded_file.ext") try await FileDownloadClient.shared.downloadFile( fileName: "your_file_name", token: "", apiKey: "", savePath: savePath ) } catch { print("Download failed: \(error)") } } ``` ```lua local http = require("socket.http") local ltn12 = require("ltn12") local function download_file(file_name, token, api_key, save_path) local url = os.getenv("NEXT_PUBLIC_API_URL") .. "/api/user/files/" .. file_name local response = {} local ok, code = http.request{ url = url, method = "GET", headers = { ["Authorization"] = api_key, ["Token"] = token, }, sink = ltn12.sink.table(response), } if not ok or code ~= 200 then error("Download failed: HTTP " .. tostring(code)) end local file = io.open(save_path, "wb") if not file then error("Failed to open file for writing: " .. save_path) end file:write(table.concat(response)) file:close() print("File downloaded to " .. save_path) end -- Usage download_file("your_file_name", "", "", "./downloaded_file.ext") ``` # Examples Moonbase prints your public API key to the server console on first startup. # User profile examples Use the API base URL Use your public API key and the JWT from authentication. JavaScript TypeScript Python Java C# C++ Ruby Go Rust PHP Swift Lua ```javascript fetch('https://your-moonbase-host/api/user/info', { method: 'GET', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'Authorization': '', 'Token': '' } }) .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error('Error:', error)); ``` ```typescript interface UserInfoResponse { // define your response properties } const getUserInfo = async (token: string, apiKey: string): Promise => { try { const response = await fetch('https://your-moonbase-host/api/user/info', { method: 'GET', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'Authorization': apiKey, 'Token': token } }); return await response.json() as UserInfoResponse; } catch (error) { throw new Error(`Request failed: ${error}`); } }; ``` ```python import requests import json def get_user_info(token: str, api_key: str): url = 'https://your-moonbase-host/api/user/info' headers = { 'Content-Type': 'application/json', 'Authorization': api_key, 'Token': token } response = requests.get(url, headers=headers, timeout=10) response.raise_for_status() return response.json() result = get_user_info('', '') print(json.dumps(result, indent=2)) ``` ```java import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.net.URI; import java.time.Duration; public class InfoClient { private static final HttpClient client = HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(10)) .build(); public static String getUserInfo(String token, String apiKey) throws Exception { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://your-moonbase-host/api/user/info")) .header("Content-Type", "application/json") .header("Authorization", apiKey) .header("Token", token) .GET() .timeout(Duration.ofSeconds(30)) .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); return response.body(); } } ``` ```csharp using System; using System.Net.Http; using System.Threading.Tasks; public class InfoClient { private static readonly HttpClient httpClient = new HttpClient(); public static async Task GetUserInfoAsync(string token, string apiKey) { var request = new HttpRequestMessage(HttpMethod.Get, "https://your-moonbase-host/api/user/info"); request.Headers.Add("Content-Type", "application/json"); request.Headers.Add("Accept", "application/json"); request.Headers.Add("Authorization", apiKey); request.Headers.Add("Token", token); var response = await httpClient.SendAsync(request); response.EnsureSuccessStatusCode(); return await response.Content.ReadAsStringAsync(); } } ``` ```cpp #include #include #include static size_t WriteCallback(void* contents, size_t size, size_t nmemb, std::string* s) { size_t newLength = size * nmemb; s->append((char*)contents, newLength); return newLength; } std::string getUserInfo(const std::string& token, const std::string& apiKey) { CURL* curl = curl_easy_init(); if (!curl) throw std::runtime_error("Failed to initialize CURL"); std::string response; struct curl_slist* headers = nullptr; headers = curl_slist_append(headers, "Content-Type: application/json"); headers = curl_slist_append(headers, ("Authorization: " + apiKey).c_str()); headers = curl_slist_append(headers, ("Token: " + token).c_str()); curl_easy_setopt(curl, CURLOPT_URL, "https://your-moonbase-host/api/user/info"); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); CURLcode res = curl_easy_perform(curl); curl_slist_free_all(headers); curl_easy_cleanup(curl); if (res != CURLE_OK) throw std::runtime_error("CURL request failed"); return response; } ``` ```ruby require 'net/http' require 'json' require 'uri' class InfoClient API_URL = 'https://your-moonbase-host/api/user/info' def self.get_user_info(token, api_key) uri = URI(API_URL) http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = uri.scheme == 'https' http.read_timeout = 30 request = Net::HTTP::Get.new(uri) request['Content-Type'] = 'application/json' request['Accept'] = 'application/json' request['Authorization'] = api_key request['Token'] = token response = http.request(request) case response when Net::HTTPSuccess JSON.parse(response.body) else raise "Request failed: #{response.code} #{response.message}" end end end result = InfoClient.get_user_info('', '') puts JSON.pretty_generate(result) ``` ```go package main import ( "context" "fmt" "net/http" "time" "io/ioutil" ) func getUserInfo(ctx context.Context, token, apiKey string) ([]byte, error) { client := &http.Client{Timeout: 30 * time.Second} req, err := http.NewRequestWithContext(ctx, "GET", "https://your-moonbase-host/api/user/info", nil) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") req.Header.Set("Authorization", apiKey) req.Header.Set("Token", token) resp, err := client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() return ioutil.ReadAll(resp.Body) } func main() { ctx := context.Background() data, err := getUserInfo(ctx, "", "") if err != nil { panic(err) } fmt.Println(string(data)) } ``` ```rust use reqwest::Client; use tokio; #[tokio::main] async fn main() -> Result<(), Box> { let client = Client::builder() .timeout(std::time::Duration::from_secs(30)) .build()?; let response = client .get("https://your-moonbase-host/api/user/info") .header("Content-Type", "application/json") .header("Authorization", "") .header("Token", "") .send() .await?; let body = response.text().await?; println!("{}", body); Ok(()) } ``` ```php [ 'method' => 'GET', 'header' => [ 'Content-Type: application/json', 'Accept: application/json', 'Authorization: ' . $apiKey, 'Token: ' . $token ], 'timeout' => 30, 'ignore_errors' => true ] ]); $response = file_get_contents('https://your-moonbase-host/api/user/info', false, $context); if ($response === false) { throw new Exception('HTTP request failed'); } return $response; } try { echo getUserInfo('', ''); } catch (Exception $e) { echo $e->getMessage(); } ?> ``` ```swift import Foundation class InfoClient { static let shared = InfoClient() private let session = URLSession.shared func getUserInfo(token: String, apiKey: String) async throws -> Data { guard let url = URL(string: "https://your-moonbase-host/api/user/info") else { throw URLError(.badURL) } var request = URLRequest(url: url) request.httpMethod = "GET" request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.setValue("application/json", forHTTPHeaderField: "Accept") request.setValue(apiKey, forHTTPHeaderField: "Authorization") request.setValue(token, forHTTPHeaderField: "Token") request.timeoutInterval = 30 let (data, _) = try await session.data(for: request) return data } } Task { do { let data = try await InfoClient.shared.getUserInfo(token: "", apiKey: "") print(String(data: data, encoding: .utf8) ?? "") } catch { print("Request failed: \(error)") } } ``` ```lua local http = require("socket.http") local ltn12 = require("ltn12") local function get_user_info(token, api_key) local url = os.getenv("NEXT_PUBLIC_API_URL") .. "/api/user/info" local response = {} local ok, code = http.request{ url = url, method = "GET", headers = { ["Content-Type"] = "application/json", ["Accept"] = "application/json", ["Authorization"] = api_key, ["Token"] = token, }, sink = ltn12.sink.table(response), } if not ok or code ~= 200 then error("Request failed: HTTP " .. tostring(code)) end return table.concat(response) end local result = get_user_info("", "") print(result) ``` # Moonbase Guide import { CreditCard, LifeBuoy, Rocket } from 'lucide-react'; Moonbase is self-hosted, so your docs map directly to the server you run. Use the pages below to install, set up keys, and launch the app. Your base URL is the host where you run Moonbase. API keys are printed once in the server console on first startup. Get started [#-get-started] Payments [#-payments] Help [#-help] # Installation via GitHub Use GitHub for painless updates without downloading new release packages each time. 1. Access the repository [#1-access-the-repository] * **Download from BuiltByBit (optional):** If you already installed via BuiltByBit and want to stay there, you can keep updating manually. * **Request GitHub access:** After your BuiltByBit download, open a #ticket. You will be granted access to the private GitHub repo. 2. Clone the repo [#2-clone-the-repo] * Remove the old BuiltByBit copy if you are switching. * Open a terminal or PowerShell where you want the project. ```bash git clone https://github.com/aledlb8/moonbase.git ``` You now have a `moonbase` folder in your chosen directory. 3. Pulling updates [#3-pulling-updates] Whenever a new release drops: ```bash cd moonbase git pull ``` Your `.env` files stay local and are not committed to Git. If we add or rename env variables, update them manually when told. # Build and Run Rebuild the app [#rebuild-the-app] ```bash cd app npm run build ``` Start the services [#start-the-services] Run the API first so it can initialize, then start the app. ```bash .\api.bat .\app.bat ``` ```bash cd api node index.js ``` ```bash cd app npm run start ``` When configuring clients or webhooks, use the public host where your Moonbase API runs, not `localhost`. # Initial Setup Once the repo is cloned, you will see this structure: 1. Install dependencies [#1-install-dependencies] ```bash .\install.bat ``` This installs everything for both the API and the app. 2. First API run (get your keys) [#2-first-api-run-get-your-keys] ```bash .\api.bat ``` The Public API Key and Private API Key are printed once in the server console on first startup. Save them right away. 3. Add keys to app/.env [#3-add-keys-to-appenv] Open `app/.env` and add: ``` PUBLIC_API_KEY=your_public_key_here PRIVATE_API_KEY=your_private_key_here ``` 4. Configure the rest of the environment [#4-configure-the-rest-of-the-environment] Update any remaining `.env` values in `api/.env` and `app/.env` for your host, port, and payment provider settings. # Troubleshooting Before digging deeper, verify these basics: * The API process is running and showing no startup errors. * Your keys are saved correctly in `app/.env`. * You rebuilt the app after changing `.env` values. **Cause:** The batch file sometimes fails when invoking the `npm` process. **Fix:** Run both services manually: ```bash cd app npm run build npm run start ``` ```bash cd api node index.js ``` # Coinbase Setup 1. Create a Commerce wallet [#1-create-a-commerce-wallet] Sign up at [https://beta.commerce.coinbase.com/payments](https://beta.commerce.coinbase.com/payments). 2. Set your business name [#2-set-your-business-name] Configure it at [https://beta.commerce.coinbase.com/settings/business](https://beta.commerce.coinbase.com/settings/business). 3. Copy your API key [#3-copy-your-api-key] Get it from [https://beta.commerce.coinbase.com/settings/security](https://beta.commerce.coinbase.com/settings/security). 4. Create a webhook endpoint [#4-create-a-webhook-endpoint] Get the webhook secret from [https://beta.commerce.coinbase.com/settings/notifications](https://beta.commerce.coinbase.com/settings/notifications). Add an endpoint pointing to: ``` http://:80/api/purchase/webhook/coinbase ``` 5. Enable required events [#5-enable-required-events] * `charge:confirmed` * `charge:failed` Coinbase must be able to reach your webhook URL. Use your public host or domain. # Payments Connect one or more payment providers to accept card and crypto payments. Webhooks must reach your running Moonbase API. Use your public host or domain, not `localhost`. # PayPal Setup 1. Create a REST API app [#1-create-a-rest-api-app] Go to [https://developer.paypal.com/dashboard](https://developer.paypal.com/dashboard) and create a REST API app. Copy the Client ID and Secret into your environment configuration. 2. Add a webhook [#2-add-a-webhook] From [https://developer.paypal.com/dashboard/webhooks](https://developer.paypal.com/dashboard/webhooks), add a webhook URL: ``` http://:80/api/purchase/webhook/paypal ``` 3. Subscribe to the event [#3-subscribe-to-the-event] * `PAYMENT.CAPTURE.COMPLETED` Make sure you are using the right credentials and webhook type for your environment. # Stripe Setup 1. Get your Stripe secret key [#1-get-your-stripe-secret-key] Create a Stripe account and copy the secret key from the dashboard. 2. Create a webhook [#2-create-a-webhook] Set the destination URL to your API host: ``` http://:80/api/purchase/webhook ``` 3. Enable the checkout event [#3-enable-the-checkout-event] Subscribe to: * `checkout.session.completed` If your API is public, use your domain and HTTPS. Stripe must be able to reach the URL from the internet.