Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ name: CI

on:
push:
pull_request:

jobs:
lint:
name: "Lint Code"
build:
name: "Build & Test"
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v4
Expand All @@ -20,5 +20,24 @@ jobs:
with:
node-version: ${{ vars.NODEJS_VERSION }}
cache: 'pnpm'

- run: pnpm install
- run: pnpm run lint

# Drift check: regenerate the low-level layer from the vendored spec and
# fail if the committed output is stale. The generated code under
# `src/gen/` must always match `spec/client.yaml`.
- name: Generate client layer
run: pnpm generate
- name: Verify generated code is up to date
run: |
git diff --exit-code src/gen || {
echo "::error::Generated code in src/gen is out of date. Run 'pnpm generate' and commit the result."
exit 1
}

- name: Build
run: pnpm run build
- name: Lint
run: pnpm run lint
- name: Test
run: pnpm test
1 change: 1 addition & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ jobs:
env:
LUNOGRAM_API_KEY: ${{ secrets.LUNOGRAM_API_KEY }}
LUNOGRAM_API_URL: ${{ secrets.LUNOGRAM_API_URL }}
LUNOGRAM_PROJECT_ID: ${{ secrets.LUNOGRAM_PROJECT_ID }}
107 changes: 104 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ npm install @lunogram/js-sdk
```typescript
import { Lunogram } from '@lunogram/js-sdk'

const lunogram = new Lunogram('your-api-key')
// The project UUID is required and scopes every request to that project.
const lunogram = new Lunogram('your-api-key', 'your-project-uuid')

// Identify a user
await lunogram.user.upsert({
Expand All @@ -46,6 +47,32 @@ await lunogram.organization.upsert({
})
```

## Project Scoping

Every Lunogram Client API request is scoped to a single **project**. You supply
the project UUID once, when constructing the client, and the SDK injects it into
every request path automatically — you never pass it per call.

All Client API endpoints live under `/api/client/projects/{projectId}/...`:

| Operation | Path |
| ------------------------ | ----------------------------------------------------------------- |
| Users | `/api/client/projects/{projectId}/users` |
| User events | `/api/client/projects/{projectId}/users/events` |
| User scheduled | `/api/client/projects/{projectId}/users/scheduled` |
| User inbox | `/api/client/projects/{projectId}/users/inbox` |
| User devices | `/api/client/projects/{projectId}/users/devices` |
| Organizations | `/api/client/projects/{projectId}/organizations` |
| Organization members | `/api/client/projects/{projectId}/organizations/users` |
| Organization events | `/api/client/projects/{projectId}/organizations/events` |
| Organization scheduled | `/api/client/projects/{projectId}/organizations/scheduled` |
| Organization inbox | `/api/client/projects/{projectId}/organizations/inbox` |
| Push (VAPID key) | `/api/client/projects/{projectId}/push/vapid` |
| Sessions | `/api/client/projects/{projectId}/auth-methods/{authMethodId}/sessions` |

The `projectId` must be a valid UUID; the client throws a `ValidationError` at
construction time if it is missing, empty, or malformed.

## Identity Model

Users and organizations are identified by an array of `ExternalID` objects:
Expand All @@ -69,7 +96,10 @@ The SDK automatically generates an anonymous identifier for each session. When y
```typescript
import { BrowserClient } from '@lunogram/js-sdk'

const client = new BrowserClient({ apiKey: 'your-api-key' })
const client = new BrowserClient({
apiKey: 'your-api-key',
projectId: 'your-project-uuid',
})

// anonymousId is auto-generated
client.user.anonymousId
Expand Down Expand Up @@ -100,7 +130,7 @@ The SDK is also exposed on `window.Lunogram`:

```html
<script>
const lunogram = new Lunogram('your-api-key')
const lunogram = new Lunogram('your-api-key', 'your-project-uuid')
lunogram.user.upsert({
identifier: [{ externalId: 'user-123' }],
email: 'user@example.com',
Expand All @@ -115,6 +145,7 @@ import { Client } from '@lunogram/js-sdk'

const client = new Client({
apiKey: 'your-api-key',
projectId: 'your-project-uuid', // required — scopes all requests to this project
urlEndpoint: 'https://your-api.com/api', // optional
})

Expand Down Expand Up @@ -179,6 +210,76 @@ await client.organization.schedule.upsert({
})
```

### Inbox

Create, query, count and update inbox messages for users and organizations. The
same surface is available under `client.organization.inbox.*`.

```typescript
// Create inbox messages (max 100 per call)
await client.user.inbox.create([
{
target: [{ externalId: 'user-123' }],
identifier: { externalId: 'msg-1' },
channel: 'inbox',
content: { title: 'Welcome', body: 'Thanks for joining!' },
},
])

// Query a user's visible messages
const { results, total } = await client.user.inbox.query({
source: 'default',
externalId: 'user-123',
channel: 'inbox',
status: 'unread',
})

// Unread / total counts
const counts = await client.user.inbox.count({
source: 'default',
externalId: 'user-123',
channel: 'inbox',
})

// Mark messages read / archived
await client.user.inbox.markRead([{ target: [{ externalId: 'user-123' }], messageId: 'msg-1' }])
await client.user.inbox.markArchived([{ target: [{ externalId: 'user-123' }], messageId: 'msg-1' }])
```

### Devices & Push

Register a device's push subscription and fetch the project's VAPID public key
for Web Push. In the browser, the current session identifier is injected
automatically.

```typescript
const { publicKey } = await client.push.getVapidPublicKey()

await client.user.devices.register({
identifier: [{ externalId: 'user-123' }],
deviceId: 'device-1',
os: 'web',
config: {
endpoint: 'https://push.example.com/...',
keys: { p256dh: '...', auth: '...' },
},
})
```

### Sessions

Mint a short-lived session token for an end user, server-side, so the client can
call the Client API directly. The permissions are defined by the session auth
method (policy) named in the call.

```typescript
const session = await client.sessions.create('auth-method-uuid', {
userId: 'user-123',
})

// session.token is a bearer token for the Client API; session.expiresAt is its expiry
```

### Multiple Identifiers

You can pass multiple identifiers from different sources:
Expand Down
5 changes: 4 additions & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ export default defineConfig(
eslint.configs.recommended,
tseslint.configs.recommended,
{
ignores: ["lib/**", "node_modules/**", "dist/**"],
// `src/gen/**` is auto-generated by `pnpm generate` (openapi-typescript)
// and must not be hand-edited; exclude it from linting rather than tweaking
// the generated output.
ignores: ["lib/**", "node_modules/**", "dist/**", "src/gen/**"],
},
);
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
"url": "https://github.com/lunogram/js-sdk"
},
"scripts": {
"generate": "openapi-typescript spec/client.yaml -o src/gen/schema.ts",
"build": "rimraf lib/ && pnpm run build:esm && pnpm run build:cjs",
"build:esm": "tsc --project ./tsconfig.module.json",
"build:cjs": "tsc --project ./tsconfig.json",
"lint": "eslint . --ext .ts",
"test": "vitest run test/url.test.ts test/browser.test.ts",
"test:e2e": "vitest run test/e2e.test.ts",
"release:github": "pnpm publish --no-git-checks --registry=https://npm.pkg.github.com",
"release:npm": "npm publish --access public --registry=https://registry.npmjs.org"
Expand Down Expand Up @@ -50,6 +52,7 @@
"@eslint/js": "^9.37.0",
"@types/uuid": "^10.0.0",
"eslint": "^9.37.0",
"openapi-typescript": "^7.13.0",
"rimraf": "^6.0.1",
"typescript": "^5.6.2",
"typescript-eslint": "^8.46.0",
Expand All @@ -59,6 +62,7 @@
"lib"
],
"dependencies": {
"openapi-fetch": "^0.17.0",
"tslib": "^2.8.1",
"uuid": "^13.0.0"
}
Expand Down
Loading
Loading