@modeminc/rift-sdk
v0.1.0
Published
The official Rift App SDK. Connect over WebSocket (socket mode - no inbound port), render a block UI, and handle taps, without hand-writing the wire protocol.
Downloads
147
Readme
Rift App SDK
Build apps for Rift without hand-writing the WebSocket wire protocol. Your app connects out to Rift over a single socket (no inbound port, no public URL), renders a block UI, and receives taps.
npm i @modeminc/rift-sdkQuick start
import { RiftApp, Blocks } from '@modeminc/rift-sdk';
const app = new RiftApp(process.env.RIFT_TOKEN!);
let count = 0;
const view = () => [
Blocks.header('Hello 👋'),
Blocks.text(`Clicked **${count}** times`),
Blocks.button('Click me', 'click', 'success'),
];
app.onReady(() => app.render(view())); // draw when we connect
app.onAction('click', () => { // a button was tapped
count++;
app.render(view()); // redraw
});
app.connect();That's a complete, live Rift app. Run it on any machine (your laptop, a VPS, a Worker with Node compat) - it dials out to Rift, so there's nothing to host or expose.
Get a token
- Open rift.modeminc.com/rift/developers.
- Create an app - you get a master token (for your own testing).
- When someone adds your app to a channel, they get their own per-install
rappi_token. That's the token that goes in their copy of the app.
Put the token in an env var (RIFT_TOKEN), never in source you share.
The model
Rift apps are socket mode: exactly one outbound WebSocket, a small typed protocol, and a block UI that Rift draws natively (no HTML/JS to sandbox). Three messages:
| Direction | Message | Meaning |
|---|---|---|
| Rift → app | { t: "ready" } | Connected. Render now. |
| Rift → app | { t: "interaction", user, action_id, value? } | Someone tapped a button. |
| app → Rift | { t: "render", blocks: [ … ] } | Draw / redraw the UI. |
The SDK turns those into onReady, onAction / onInteraction, and render().
Blocks
Blocks.* are thin builders over plain objects, so literals work too. Rift renders them all
natively:
Blocks.header('Title')
Blocks.text('**Markdown** here', { muted: true })
Blocks.divider()
Blocks.spacer(12)
Blocks.badge('LIVE', '#22c55e')
Blocks.image('https://…/pic.png')
Blocks.button('Label', 'my_action_id', 'success', 'optional-value') // style: success | danger | neutral
Blocks.input('name', { label: 'Name', placeholder: 'Ada' })
Blocks.textarea('bio', { label: 'About', placeholder: '…' })
Blocks.select('theme', ['blue', 'green'], { label: 'Theme', value: 'blue' })
Blocks.keyValue([{ k: 'Status', v: 'Online' }, { k: 'Uptime', v: '3d' }])
Blocks.progress(72) // 0-100
Blocks.code(['> building…', '> done']) // or a single string
Blocks.steps(['Connect', 'Configure', 'Run'], 1) // current step index
Blocks.card([ /* children */ ], 'Title') // bordered container
Blocks.section([ /* children */ ])
Blocks.row([ /* children laid out in columns */ ])Inputs and forms
input, textarea and select each take an id. Their live values are delivered with
every interaction in i.values, keyed by that id - so a Save button reads the whole form:
app.onAction('save', (i) => {
console.log(i.values.name, i.values.theme);
});API
new RiftApp(token | options)
new RiftApp('rappi_…')
new RiftApp({
token: 'rappi_…',
url: 'wss://rift.modeminc.com/v1/apps/gateway', // default
reconnect: true, // default
reconnectDelayMs: 2000, // default
})Events / handlers
app.onReady(fn)- fired every (re)connect. Render here.app.onAction(actionId, fn)- route one button'saction_idto a handler.app.onInteraction(fn)- every tap (useonActionfor routing).app.on('connect' | 'disconnect' | 'error' | 'message', fn)- lifecycle + raw frames.
Methods
app.connect()- open the socket (auto-reconnects unless disabled).app.render(blocks)- draw / redraw.app.close()- disconnect and stop reconnecting.
What's here
src/ the SDK (RiftApp, Blocks, protocol types)
examples/ runnable single-file apps
templates/ scaffolds - copy one to start a new appRoadmap
The block UI + interactions above is the shipping surface today. Coming next, to reach full parity with a Root-style DevKit: message events, and REST domains for channels, roles, files and a per-app key/value store. The client API is designed to grow into those without breaking the block-UI code above.
