Ghost Server API Client
The `castle-api-client` package functions as the official JavaScript/TypeScript client for interacting with the `ghost-server` API. It implements a custom JSON RPC-like protocol, making HTTP POST requests to a single endpoint (`/api`) with a JSON body containing `method` and `args` fields. This design enables a unified and simplified communication interface. The client is highly versatile, supporting a broad spectrum of JavaScript environments, including Node.js, modern web browsers, Electron applications, and React Native projects. Its primary function is centered around user identity and authentication, relying on deep integration with Expo user accounts. This strategic reliance streamlines authentication workflows for Expo-centric applications. A key differentiator for `castle-api-client` is its architectural independence: it was specifically developed to decouple `ghost-server` from larger, more monolithic web stacks, allowing for faster development cycles and greater agility in evolving its features. This client is currently at stable version 7.0.0. While no specific release cadence is publicly detailed, its design emphasizes responsiveness to evolving needs, offering a lightweight and efficient solution for secure, cross-platform communication within the Expo ecosystem, particularly beneficial for projects needing robust login and identity management without entanglement in complex legacy systems.
Common errors
-
Unhandled promise rejection: TypeError: client.call is not a function
cause Attempting to invoke the `call` method on an uninitialized `GhostApiClient` instance, or `client` is not the expected object.fixEnsure `new GhostApiClient(...)` has been successfully executed before attempting to call any methods on the `client` object. Verify that `client` variable holds the instantiated `GhostApiClient`. -
HTTP Error 401: Unauthorized
cause The API call requires authentication, but a valid Expo authentication token was either missing, expired, or incorrectly provided in the request payload (e.g., via `auth.loginWithExpoToken` method).fixVerify that `EXPO_AUTH_TOKEN` (or the equivalent credential) is correctly set and passed to the client or the specific authentication method. Renew expired tokens if necessary. -
SyntaxError: Unexpected token < in JSON at position 0
cause The `ghost-server` responded with non-JSON content, often an HTML error page, instead of the expected JSON. This can happen due to server-side errors, incorrect endpoint URLs, or network issues.fixDouble-check the `serverUrl` provided to the `GhostApiClient` constructor. Confirm the `ghost-server` is running correctly and that no network proxies or firewalls are interfering with the JSON response. Inspect the raw HTTP response body if possible. -
Error: Missing or invalid 'method' field in request body
cause The object passed to `client.call` did not contain a `method` property, or its value was an empty string or not a string, which is required by the underlying JSON RPC protocol.fixEnsure that the first argument to `client.call` is an object that explicitly includes a `method` property with a valid, non-empty string value corresponding to an existing server method (e.g., `{ method: 'user.getProfile', args: {...} }`).
Warnings
- breaking Major version updates to `castle-api-client` (e.g., `v7.0.0`) typically introduce breaking changes to the API surface, configuration options, or internal behaviors. Users upgrading from `v6.x.x` or earlier should meticulously consult the official changelog or migration guides for specific details, as method signatures, constructor parameters, or error handling mechanisms may have changed significantly.
- gotcha The client's identity and authentication mechanisms are tightly coupled with Expo user accounts. This means direct integration with other non-Expo identity providers or implementing entirely custom authentication flows might require significant custom implementation efforts or might not be supported out-of-the-box by this client.
- gotcha This `castle-api-client` uses a specific JSON RPC-like protocol where all requests are HTTP POST to a single endpoint with a JSON body containing `method` and `args`. Developers accustomed to RESTful, GraphQL, or other standard API paradigms should carefully review the `ghost-server` documentation for available methods and their expected argument structures to avoid malformed requests and unexpected errors.
- gotcha The quickstart and README often provide a hardcoded production URL (`https://ghost-server.app.render.com/api`). For any production application, API endpoints should be stored in environment variables or a secure configuration management system. Hardcoding URLs can lead to deployment errors, security vulnerabilities, and difficulties in managing different environments (development, staging, production).
Install
-
npm install castle-api-client -
yarn add castle-api-client -
pnpm add castle-api-client
Imports
- GhostApiClient
const GhostApiClient = require('castle-api-client');import { GhostApiClient } from 'castle-api-client'; - APIClientError
import APIClientError from 'castle-api-client';
import { APIClientError } from 'castle-api-client'; - APIClientConfig
import { APIClientConfig } from 'castle-api-client';import type { APIClientConfig } from 'castle-api-client';
Quickstart
import { GhostApiClient } from 'castle-api-client';
async function runGhostClientExample() {
const expoAuthToken = process.env.EXPO_AUTH_TOKEN ?? ''; // Securely retrieve your Expo auth token
if (!expoAuthToken) {
console.warn("EXPO_AUTH_TOKEN environment variable is not set. API calls requiring authentication may fail.");
}
// Initialize the client with the server URL
const client = new GhostApiClient({
serverUrl: 'https://ghost-server.app.render.com/api',
// Further configuration options like custom fetch, timeouts, etc. can be added here
});
try {
console.log('Attempting to log in with Expo token...');
// The API uses a generic 'call' method with 'method' and 'args' fields
const loginResponse = await client.call({
method: 'auth.loginWithExpoToken', // Example: a hypothetical login method
args: { token: expoAuthToken },
});
if (loginResponse.error) {
console.error('Login failed:', loginResponse.error.message, loginResponse.clientError);
return;
}
console.log('Login successful. Result:', loginResponse.result);
const sessionToken = loginResponse.result?.sessionToken; // Assuming session token is returned
if (sessionToken) {
console.log('Fetching user profile...');
// Make another call using the obtained session token
const userProfile = await client.call({
method: 'user.getProfile', // Example: a hypothetical method to get user profile
args: { sessionToken: sessionToken },
});
if (userProfile.error) {
console.error('Failed to get user profile:', userProfile.error.message);
return;
}
console.log('User Profile:', userProfile.result);
}
} catch (error) {
console.error('An unexpected client error occurred:', error instanceof Error ? error.message : String(error));
}
}
runGhostClientExample();