← Back to SDKs TS
TypeScript —
TS
TypeScript — @airlockapp/gateway-sdk
A zero-dependency TypeScript client SDK for the Airlock Integrations Gateway API. Uses native fetch — works in Node.js 18+ and modern browsers.
Installation
Bash
npm install @airlockapp/gateway-sdk npm install @airlockapp/gateway-sdk Quick Start
With Bearer Token
TypeScript
import { AirlockGatewayClient } from "@airlockapp/gateway-sdk";
const client = new AirlockGatewayClient({
baseUrl: "https://<your-gateway-host>",
token: "your-token",
}); import { AirlockGatewayClient } from "@airlockapp/gateway-sdk";
const client = new AirlockGatewayClient({
baseUrl: "https://<your-gateway-host>",
token: "your-token",
}); With Enforcer App Credentials
TypeScript
const client = new AirlockGatewayClient({
baseUrl: "https://<your-gateway-host>",
clientId: "your-client-id",
clientSecret: "your-client-secret",
}); const client = new AirlockGatewayClient({
baseUrl: "https://<your-gateway-host>",
clientId: "your-client-id",
clientSecret: "your-client-secret",
}); Authentication
Personal Access Token (PAT)
PAT is the recommended authentication for user-scoped operations. Sent via the X-PAT header.
TypeScript
// After obtaining a PAT from your org admin or Mobile Approver
client.setPat("airlock_pat_...");
// Clear PAT when no longer needed
client.setPat(undefined); // After obtaining a PAT from your org admin or Mobile Approver
client.setPat("airlock_pat_...");
// Clear PAT when no longer needed
client.setPat(undefined); Dual Auth (setBearerToken)
TypeScript
// After user login (Device Auth Grant or Auth Code + PKCE)
client.setBearerToken(accessToken); // After user login (Device Auth Grant or Auth Code + PKCE)
client.setBearerToken(accessToken); Authentication by Enforcer App Kind
| Kind | OAuth2 Flow | SDK Methods |
|---|---|---|
| Agent / Desktop / VsCode | Device Auth Grant (RFC 8628) | login(onUserCode) |
| Web | Auth Code + PKCE (RFC 7636) | loginWithAuthCode or getAuthorizationUrl + exchangeCode |
| Mobile | Auth Code + PKCE (RFC 7636) | getAuthorizationUrl + exchangeCode |
Gateway Client API
| Method | Description |
|---|---|
echo() | Gateway discovery / health |
setPat(pat) | Set Personal Access Token (X-PAT header) |
setBearerToken(token) | Set Bearer token for user-scoped operations |
checkConsent() | Check user consent for this enforcer app |
submitArtifact(request) | Submit artifact for approval |
encryptAndSubmitArtifact(request) | JCS + SHA-256 + AES-GCM encrypt, then submit (Node.js) |
getExchangeStatus(requestId) | Get exchange status |
waitForDecision(requestId, timeout) | Long-poll for decision |
submitAck(msgId, requestId?) | Acknowledge decision delivery (POST /v1/acks, fire-and-forget) |
withdrawExchange(requestId) | Withdraw pending exchange |
initiatePairing(request) | Start pairing session |
claimPairing(request) | Claim a pre-generated pairing code |
getPairingStatus(nonce) | Poll pairing status |
revokePairing(routingToken) | Revoke a pairing |
sendHeartbeat(request) | Presence heartbeat |
getEffectiveDndPolicies(enforcerId, workspaceId, sessionId?) | Fetch effective DND policies |
Auth Client (AirlockAuthClient)
| Method | Purpose |
|---|---|
discover | OIDC discovery |
login | Device code login |
loginWithAuthCode | Auth code + PKCE (local callback) |
getAuthorizationUrl, exchangeCode | Auth code + PKCE (manual redirect) |
refreshTokenAsync, getAccessToken | Token refresh / access |
logout | Sign out (revoke) |
Pairing
Standard Pairing (Enforcer-Initiated)
TypeScript
// 1. Initiate a pairing session
const resp = await client.initiatePairing({
enforcerId: "my-enforcer",
workspaceName: "my-project",
x25519PublicKey: myPublicKey,
});
// 2. Display pairing code to user
console.log(`Pairing code: ${resp.pairingCode}`);
// 3. Poll for approval from the Mobile Approver
const status = await client.getPairingStatus(resp.nonce);
// status.state === "Completed" → save status.routingToken // 1. Initiate a pairing session
const resp = await client.initiatePairing({
enforcerId: "my-enforcer",
workspaceName: "my-project",
x25519PublicKey: myPublicKey,
});
// 2. Display pairing code to user
console.log(`Pairing code: ${resp.pairingCode}`);
// 3. Poll for approval from the Mobile Approver
const status = await client.getPairingStatus(resp.nonce);
// status.state === "Completed" → save status.routingToken Pre-Generated Code Pairing (Approver-Initiated)
TypeScript
const claim = await client.claimPairing({
code: "ABCD-1234",
enforcerId: "my-enforcer",
workspaceName: "my-project",
x25519PublicKey: myPublicKey,
});
// claim.routingToken is ready to use const claim = await client.claimPairing({
code: "ABCD-1234",
enforcerId: "my-enforcer",
workspaceName: "my-project",
x25519PublicKey: myPublicKey,
});
// claim.routingToken is ready to use Consent Check
TypeScript
import { AirlockGatewayError } from "@airlockapp/gateway-sdk";
try {
const status = await client.checkConsent();
// status === "approved" — proceed normally
} catch (e) {
if (e instanceof AirlockGatewayError) {
if (e.errorCode === "app_consent_required") {
// User hasn't granted consent
} else if (e.errorCode === "app_consent_pending") {
// Consent request sent, waiting for approval
}
}
} import { AirlockGatewayError } from "@airlockapp/gateway-sdk";
try {
const status = await client.checkConsent();
// status === "approved" — proceed normally
} catch (e) {
if (e instanceof AirlockGatewayError) {
if (e.errorCode === "app_consent_required") {
// User hasn't granted consent
} else if (e.errorCode === "app_consent_pending") {
// Consent request sent, waiting for approval
}
}
} Submit and Poll
Encrypt and Submit (Node.js)
TypeScript
const requestId = await client.encryptAndSubmitArtifact({
enforcerId: "my-enforcer",
plaintextPayload: JSON.stringify({ kind: "shell", cmd: "npm publish" }),
encryptionKeyBase64Url: derivedAes256KeyFromPairing,
metadata: { routingToken: "rt-abc" },
}); const requestId = await client.encryptAndSubmitArtifact({
enforcerId: "my-enforcer",
plaintextPayload: JSON.stringify({ kind: "shell", cmd: "npm publish" }),
encryptionKeyBase64Url: derivedAes256KeyFromPairing,
metadata: { routingToken: "rt-abc" },
}); Manual Ciphertext
TypeScript
// Submit an artifact for approval
const requestId = await client.submitArtifact({
enforcerId: "my-enforcer",
artifactHash: "sha256-hash",
ciphertext: {
alg: "aes-256-gcm",
data: "base64-encrypted-content",
nonce: "nonce",
tag: "tag",
},
metadata: { routingToken: "rt-abc" },
});
// Wait for a decision (long-poll)
const decision = await client.waitForDecision(requestId, 30);
if (decision?.body?.decision === "approve") {
console.log(`Approved: ${decision.body.reason}`);
} // Submit an artifact for approval
const requestId = await client.submitArtifact({
enforcerId: "my-enforcer",
artifactHash: "sha256-hash",
ciphertext: {
alg: "aes-256-gcm",
data: "base64-encrypted-content",
nonce: "nonce",
tag: "tag",
},
metadata: { routingToken: "rt-abc" },
});
// Wait for a decision (long-poll)
const decision = await client.waitForDecision(requestId, 30);
if (decision?.body?.decision === "approve") {
console.log(`Approved: ${decision.body.reason}`);
} Error Handling
All errors throw AirlockGatewayError with helper getters:
TypeScript
import { AirlockGatewayError } from "@airlockapp/gateway-sdk";
try {
await client.submitArtifact(request);
} catch (e) {
if (e instanceof AirlockGatewayError) {
if (e.isQuotaExceeded) { /* 429 */ }
if (e.isPairingRevoked) { /* 403 pairing_revoked */ }
if (e.isConflict) { /* 409 */ }
console.error(`Error ${e.statusCode}: ${e.message}`);
}
} import { AirlockGatewayError } from "@airlockapp/gateway-sdk";
try {
await client.submitArtifact(request);
} catch (e) {
if (e instanceof AirlockGatewayError) {
if (e.isQuotaExceeded) { /* 429 */ }
if (e.isPairingRevoked) { /* 403 pairing_revoked */ }
if (e.isConflict) { /* 409 */ }
console.error(`Error ${e.statusCode}: ${e.message}`);
}
} Encryption
Uses X25519 ECDH via libsodium-wrappers-sumo:
sodium.crypto_box_keypair()— generate X25519 keypairsodium.crypto_scalarmult()— X25519 ECDH scalar multiplicationcrypto.hkdfSync('sha256', ...)— HKDF-SHA256 (info:HARP-E2E-AES256GCM)
Test Enforcer CLI
A fully interactive TUI that demonstrates the complete enforcer lifecycle.
Bash
# From the repo root — build the SDK first (required once)
cd src/typescript
npm install
npm run build
# Run the test enforcer
cd test-enforcer
npm install
npm start # From the repo root — build the SDK first (required once)
cd src/typescript
npm install
npm run build
# Run the test enforcer
cd test-enforcer
npm install
npm start Prerequisites: Node.js 18+. Configuration saved to ~/.airlock/test-enforcer-typescript.json.