# 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.