Skip to content

Client Library Overview

@kowloon/client is an isomorphic JavaScript client for the Kowloon API. The same package runs unmodified in Node, in the browser, and inside a React Native app -- it detects its environment and picks the right storage backend automatically.

Terminal window
npm install @kowloon/client

Besides the default export, the package publishes several subpath exports for consumers who only want a slice of the client:

@kowloon/client
@kowloon/client/auth
@kowloon/client/activities
@kowloon/client/feed
@kowloon/client/search
@kowloon/client/admin
@kowloon/client/embeds
@kowloon/client/theme/palette.json

Most apps just use the default export and reach everything through one instance.

import KowloonClient from '@kowloon/client'
const client = new KowloonClient({
baseUrl: 'https://kwln.social',
storage, // optional -- auto-detected if omitted
headers, // optional -- extra headers merged into every request
timeout, // optional -- request timeout in ms
onUnauthorized, // optional -- see below
})
await client.init()

baseUrl is required. If it's missing, the constructor throws a plain Error (not a KowloonError subclass -- the distinction matters if you're pattern-matching on the error hierarchy below).

client.init() calls auth.restoreSession() internally, which attempts to load a persisted token and refresh the user's session (see Auth). Always await it before assuming client.auth.getUser() reflects reality.

If you don't pass a storage adapter explicitly, detectStorage() picks one for you:

  1. React Native (global.navigator.product === 'ReactNative') -- an AsyncStorageAdapter wrapping @react-native-async-storage/async-storage, loaded dynamically via require. If that package isn't installed, it falls back to an in-memory store with a console warning rather than throwing.
  2. Browser (window.localStorage present) -- a LocalStorageAdapter.
  3. Node (neither of the above) -- an in-memory Map-backed store. Sessions do not survive process restarts under this default -- pass your own persistent adapter if you need that in a Node context (a CLI tool, a server-to-server integration, etc.).

All adapters expose the same async shape: { getItem, setItem, removeItem, clear }, and all swallow their own internal errors (logging to console.error and falling back safely) rather than throwing out of the client.

A KowloonClient instance wires up one sub-client per API area. Construction order matters internally -- moderation and files are built before activities, because ActivitiesClient delegates file uploads to FilesClient and invalidates ModerationClient's cache after block/mute actions -- but as a consumer you don't need to think about that; just use client.<name>.

Sub-client Purpose
auth Register, log in/out, session restore, token access.
activities Every write operation -- posts, replies, reacts, circles, groups, bookmarks, pages, profile, follow/block/mute, flags. Funnels through POST /outbox.
feeds Every read operation -- timelines, single-object lookups, listings, search-adjacent lookups.
files Upload, list, inspect, delete, and build serving URLs for uploaded files.
notifications List, count, and mark notifications read/dismissed.
search Full-text and handle-shaped search across the server.
admin Server-admin CRUD surface. Also reachable via the @kowloon/client/admin subpath export, but it's wired onto every normal instance too -- see the note below.
moderation Client-side block/mute filtering for content fetched anonymously from other servers.
themes List/read themes publicly; create/update/delete/set-default as an admin.

Plus a few plain modules re-exported from the package root rather than instantiated as sub-clients:

If you pass an onUnauthorized callback, it fires when the server responds 401 to a request that itself carried an Authorization header. A 401 on a request that was never authenticated in the first place (you simply aren't logged in) does not trigger it. This distinction lets you use onUnauthorized specifically to detect "your session just expired / was revoked" and prompt a re-login, without it firing spuriously on ordinary anonymous browsing.

Every HTTP-level failure is normalized into one error hierarchy, rooted at KowloonError:

KowloonError (base -- has statusCode, response, requestId)
|---- AuthenticationError (401)
|---- AuthorizationError (403)
|---- NotFoundError (404)
|---- ValidationError (400, and the general 400--499 fallback -- has an `.errors` array)
|---- ServerError (5xx)
`---- NetworkError (client-side connectivity failure -- timeout/abort/DNS; no fixed status)

createErrorFromStatus(status, message, options) is the single function that maps an HTTP status to the right subclass, so you can rely on this hierarchy holding consistently across every sub-client -- with one exception:

Most methods across the library take a single options object: client.activities.createPost({ content, to }) rather than positional arguments. This is a deliberate, library-wide convention -- but it is not universal. A handful of methods (mostly on FilesClient and NotificationsClient) take a bare ID string instead. See the gotchas page for the exact list before you assume every call follows the same shape.