SDK Overview

The @glyphmoe/sdk is the TypeScript toolkit for building Glyph extensions. It handles HTTP requests, rate limiting, retries, HTML parsing, and cookie management so you can focus on parsing novel content.

Architecture

Your Extension (TypeScript)
    ↓ calls get(), post(), load()
@glyphmoe/sdk (request layer, rate limiter, HTML parsing)
    ↓ resolved to window.__wit.* globals at build time
WKWebView bridge (window.prompt() → Native)
    ↓ synchronous JS→Native communication
Glyph Native Runtime (URLSession, SwiftSoup, cookie store)
    ↓ handles HTTP fetches, HTML parsing, host functions
Novel Website

Extensions run inside a WKWebView. The bridge uses window.prompt() for synchronous JS-to-Native communication, there are no async message handlers or XHR involved. The native side handles HTTP fetches (via URLSession), HTML parsing (via SwiftSoup), and host functions (cookies, content rating, user agent). The SDK handles all the plumbing, your code just calls get(), load(), etc. All HTTP requests go through the native runtime, which provides per-source cookie isolation, domain restrictions, and network logging.

All SDK functions are synchronous, no async/await needed in extension code. For example, get(url) returns a string directly, not a Promise.

Extensions are compiled to IIFE format (not ESM). esbuild bundles everything with globalName: 'GlyphExtension'. WIT imports (glyph:extension/http@0.1.0, glyph:extension/html@0.1.0, glyph:extension/host@0.1.0) are resolved to window.__wit.* globals at build time.

Languages

Glyph supports two extension languages, pick what fits your project:

LanguageStatusWhen to use
TypeScript / JavaScriptRecommended. Mature SDK, scaffolder, test runner, playgroundDefault choice for new sources
Rust → WebAssembly ComponentExperimental. Pipeline works, but no helper crate or test runner yetWhen you have a Rust library you want to reuse, or want stronger type safety

Both compile to the same WIT contract and run on the same iOS host. For Rust, see Rust Extensions. The rest of this page covers the JS/TS path.

Quick Start

npx create-glyph-extension my-extensions
cd my-extensions
npm run dev

The CLI scaffolds a complete project with an example source. The dev server builds your extension on-the-fly and serves it at a local URL. Add that URL in Glyph (Settings → Extensions → +) to test live.

Project Structure

my-extensions/
├── sources/
│   └── my-source/
│       └── src/
│           ├── main.ts     # Entry point -> export const source = createSource({...})
│           ├── parser.ts   # HTML parsing logic
│           └── my-source.test.ts
├── repo.json               # Repository metadata
├── package.json            # Uses @glyphmoe/cli commands
└── dist/                   # Built bundles + index.json

CLI Commands

The @glyphmoe/cli package provides all development commands:

CommandDescription
npm run devDevelopment server with hot reload
npm run buildProduction build with validation
npm run testRun tests
npm run validateValidate extensions (flags: --typecheck, --fix, --ci)
npx glyph add <id>Add a new source to your project
npx glyph logcatStart a standalone log receiver on port 9999 for remote log streaming

See the CLI Reference for full details.

Creating a Source

Every extension exports a source object:

import { createSource, get, load, RateLimit } from '@glyphmoe/sdk'

const BASE = 'https://example-novels.com'

export const source = createSource({
  // Identity
  id: 'example-novels',
  name: 'Example Novels',
  baseUrl: BASE,
  icon: 'https://example-novels.com/favicon.ico',
  language: 'en',
  dev: 'your-name',

  // Rate limiting (recommended)
  rateLimit: RateLimit.balanced, // 3 req/sec

  // Required methods
  searchNovels(query, page) {
    const html = get(`${BASE}/search?q=${encodeURIComponent(query)}&page=${page}`)
    const $ = load(html)
    // ... parse and return { items, hasNextPage }
  },
  fetchNovelDetails(novelUrl) {
    const html = get(novelUrl)
    const $ = load(html)
    // ... parse and return { id, title, url, chapters, ... }
  },
  fetchChapterContent(chapterUrl) {
    const html = get(chapterUrl)
    const $ = load(html)
    return $('div.chapter-content').html() ?? ''
  },
})

createSource() handles all the boilerplate:

  • Sets User-Agent and Accept headers automatically
  • Configures the rate limiter
  • Runs setup at module load time (no async initialise() needed)
  • Auto-detects capabilities (discover, filters, login, etc.) from the methods and flags you provide and writes them into index.json, the host uses these to gate features without probing the bundle

See the Source Interface for all required and optional methods.

Next steps

If you want to…Read
Implement all the methods a source needsSource Interface
Make HTTP requests, set headers, throttleHTTP & Requests
Parse HTML, paginate, read cookiesHelpers & Utilities
Declare what your source supportsCapabilities
Build and shipPublishing
Debug a misbehaving extensionDebugging
Use Rust instead of TypeScriptRust Extensions
Understand the underlying contractWIT Contract