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
7 changes: 0 additions & 7 deletions .changeset/driver-app-stores.md

This file was deleted.

5 changes: 5 additions & 0 deletions .changeset/socket-token.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@fleetbase/sdk': minor
---

Add `fleetbase.socket.token()` (`POST socket/token`) to mint a short-lived realtime socket token server-side. It resolves to `{ token, expires_in, expires_at }` (`SocketTokenResponse`) and follows `setAdapter`. Additive; no existing export, store, or method changes.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@

All notable changes to the Fleetbase JavaScript SDK are documented here. This project follows semantic versioning.

## 2.1.0

### Added

- Fleet-Ops driver workflow stores and resources: `manifests` (with `optimize` and `drivers.manifests`), `manifestStops` (PATCH `update`), `trailers` (CRUD plus `attach`, `detach`, `connections`, `track` and `vehicles.trailers`), `fuelReports`, `issues` and `workOrders` (with `send`), and the driver password actions `changePassword`, `forgotPassword` and `resetPassword`.
- `fleetbase.socket.token()` to mint realtime socket tokens.

### Fixed

- `NodeAdapter` sent GET requests to the wrong path and ignored headers set after construction; it now shares the browser Fetch transport.

## 2.0.0

### Changed
Expand Down
64 changes: 64 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,70 @@ const place = new Place({

The root package exports the existing resource classes, adapters, collection helpers, resolver and registry hooks, string helpers, validation utilities, and TypeScript request/configuration types.

## Realtime socket tokens

Fleetbase publishes realtime events over SocketCluster. Channel subscriptions are authorized with a short-lived socket token, so the flow is always:

1. **On your server**, use your secret key to mint a token with `fleetbase.socket.token()` (`POST /v1/socket/token`).
2. Send **only the token** to the browser or device. Never send the API key.
3. In the browser, connect with `socketcluster-client`, present the token (`socket.authenticate(token)` or an in-memory `authEngine`), then subscribe.

```ts
// server.ts: runs on your backend, never in the browser
import Fleetbase from '@fleetbase/sdk';

const fleetbase = new Fleetbase(process.env.FLEETBASE_SECRET_KEY!);

app.post('/realtime-token', requireSignedInUser, async (_req, res) => {
// { token, expires_in, expires_at }
res.json(await fleetbase.socket.token());
});
```

```ts
// browser.ts
import { create } from 'socketcluster-client';

async function fetchSocketToken(): Promise<{ token: string; expires_in: number }> {
const response = await fetch('/realtime-token', { method: 'POST', credentials: 'include' });
if (!response.ok) throw new Error(`Token request failed: ${response.status}`);
return response.json();
}

// Keep the token in memory only. SocketCluster calls loadToken() before every (re)connect,
// so the token travels in the handshake and subscriptions are authorized from the start.
let current: { token: string; refreshAt: number } | null = null;
const remember = ({ token, expires_in }: { token: string; expires_in: number }) => {
current = { token, refreshAt: Date.now() + (expires_in - 60) * 1000 };
return token;
};
const authEngine = {
saveToken: async (_name: string, token: string) => token,
removeToken: async () => {
const token = current?.token ?? null;
current = null;
return token;
},
loadToken: async () => (current && Date.now() < current.refreshAt ? current.token : remember(await fetchSocketToken())),
};

const socket = create({ hostname: 'socket.example.com', secure: true, port: 443, authEngine });

// Refresh about 60 seconds before expiry without dropping subscriptions.
setInterval(async () => {
if (current && Date.now() >= current.refreshAt) {
await socket.authenticate(remember(await fetchSocketToken()));
}
}, 15_000);

const channel = socket.subscribe(`company.${companyUuid}`);
for await (const event of channel) {
console.log(event);
}
```

A token minted with an API key may subscribe to its company channel (`company.{company uuid}`), its own key channel (`api.{key id}`), and channels of resources that belong to the same company (for example `order.{order uuid or public id}`). A server that does not have realtime authentication configured answers the mint request with `404`; in that case connect without a token as before.

## Custom adapters

Implement the stable adapter interface when requests need to use an application-specific transport:
Expand Down
14 changes: 6 additions & 8 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
> v2.0.0 ~ "Modern JavaScript packaging and first-party compatibility"
> v2.1.0 ~ "Driver workflows, realtime socket tokens and a working Node transport"

## Highlights

- Strict TypeScript implementation with native ESM, CommonJS, browser bundles, source maps, and public declarations.
- Preserved adapter extension points, authentication/header overrides, mutation bodies, and driver GeoJSON coordinates used by Navigator and Storefront.
- Deterministic contract tests with enforced 100% statement, branch, function, and line coverage, plus packed-artifact framework and package-manager checks.
- Structured API errors, consistent resource state cleanup, and corrected collection and persistence helpers.
- Automatic tagging, npm publication, and GitHub releases after a reviewed release-branch merge, with provenance, checksum verification, and post-publication ESM/CommonJS checks.
- Fleet-Ops driver workflow stores: `manifests` (with `optimize` and `drivers.manifests`), `manifestStops`, `trailers` (CRUD, `attach`, `detach`, `connections`, `track`, `vehicles.trailers`), `fuelReports`, `issues` and `workOrders` (with `send`), plus the driver password actions `changePassword`, `forgotPassword` and `resetPassword`.
- `fleetbase.socket.token()` mints short-lived realtime socket tokens (`POST socket/token`) for server-side code that holds a secret key or user token.
- `NodeAdapter` requests work: GET requests reach the right path, and headers set after construction with `setHeaders` are sent. Node consumers of v2.0.0, including the Storefront SDK under Node, should upgrade.

## Compatibility

Node 20.19.4 remains a legacy runtime compatibility target; Node 22/24 are recommended. Building this repository requires Node 22.13 or newer. Existing v1 declaration-path consumers retain a compatibility entry.
All additions are additive; no export, store or method changes. Node 20.19.4 remains a legacy runtime target; Node 22/24 are recommended.

This is the review branch's release metadata, not evidence that v2 has been published. Native application acceptance and owner-controlled publishing configuration must pass before release.
This is the release branch's metadata, not evidence that v2.1.0 has been published. Merging this release PR into `main` tags and publishes it.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@fleetbase/sdk",
"version": "2.0.0",
"version": "2.1.0",
"description": "Official JavaScript and TypeScript SDK for the Fleetbase API",
"type": "module",
"main": "./dist/index.cjs",
Expand Down
38 changes: 21 additions & 17 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@ overrides:
flatted: '>=3.4.2'
'picomatch@>=4.0.0 <4.0.4': '>=4.0.4'
serialize-javascript: '>=7.0.3'
'brace-expansion@>=4.0.0 <5.0.11': '^5.0.11'
'fast-uri@>=3.0.0 <3.1.8': '^3.1.8'
'shell-quote@>=1.8.4 <1.11.0': '^1.11.0'
'source-map-js@<1.2.2': '^1.2.2'
29 changes: 6 additions & 23 deletions src/adapters/node.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import BrowserAdapter from './browser.js';
import { requestWithFetch } from './fetch.js';
import type { AdapterConfig, RequestOptions } from '../types.js';
import type { AdapterConfig } from '../types.js';

/**
* The Fetch transport for Node.js. It is the browser transport plus a User-Agent, so
* every verb, the current host and namespace, and headers set later with `setHeaders`
* work the same way in both.
*/
export default class NodeAdapter extends BrowserAdapter {
constructor(config: AdapterConfig = {}) {
const headers = new Headers(config.headers);
Expand All @@ -10,25 +14,4 @@ export default class NodeAdapter extends BrowserAdapter {
}
super({ ...config, headers });
}

override request(method: string, url: string, options: RequestOptions & { data?: unknown } = {}): Promise<unknown> {
const { data, ...requestOptions } = options;
return requestWithFetch(this.fetchConfig, { path: url, method, data, options: requestOptions });
}

override post(path: string, data: unknown = {}, options: RequestOptions = {}): Promise<unknown> {
return this.request('POST', path, { ...options, data });
}

override put(path: string, data: unknown = {}, options: RequestOptions = {}): Promise<unknown> {
return this.request('PUT', path, { ...options, data });
}

override patch(path: string, data: unknown = {}, options: RequestOptions = {}): Promise<unknown> {
return this.request('PATCH', path, { ...options, data });
}

override delete(path: string, options: RequestOptions = {}, legacyOptions?: RequestOptions): Promise<unknown> {
return this.request('DELETE', path, legacyOptions ?? options);
}
}
5 changes: 5 additions & 0 deletions src/fleetbase.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { detectAdapter } from './adapters/detect.js';
import Socket from './socket.js';
import Store from './store.js';
import {
driverActions,
Expand Down Expand Up @@ -117,6 +118,8 @@ export default class Fleetbase {
fuelReports: Store<FuelReport>;
issues: Store<Issue>;
workOrders: WorkOrderStore;
/** Realtime helpers, e.g. `socket.token()` to mint a short-lived socket token server-side. */
socket: Socket;

constructor(publicKey: string, config: FleetbaseConfig = {}, debug = false) {
if (typeof publicKey !== 'string' || publicKey.length === 0) {
Expand Down Expand Up @@ -154,6 +157,7 @@ export default class Fleetbase {
this.fuelReports = new Store<FuelReport>('fuel-report', this.adapter);
this.issues = new Store<Issue>('issue', this.adapter);
this.workOrders = new Store<WorkOrder>('work-order', this.adapter).extendActions(workOrderActions) as WorkOrderStore;
this.socket = new Socket(this.adapter);
}

static newInstance(...params: ConstructorParameters<typeof Fleetbase>): Fleetbase {
Expand All @@ -162,6 +166,7 @@ export default class Fleetbase {

setAdapter(adapter: AdapterLike): void {
this.adapter = adapter;
this.socket.adapter = adapter;
for (const store of this.stores()) {
store.adapter = adapter;
}
Expand Down
4 changes: 3 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import EmberJsAdapter from './adapters/ember.js';
import NodeAdapter from './adapters/node.js';
import Fleetbase from './fleetbase.js';
import Resource from './resource.js';
import Socket from './socket.js';
import Store from './store.js';
import { register } from './registry.js';

Expand All @@ -13,7 +14,7 @@ register('adapter', 'NodeAdapter', NodeAdapter);
register('adapter', 'EmberJsAdapter', EmberJsAdapter);

export default Fleetbase;
export { Fleetbase, Adapter, BrowserAdapter, EmberJsAdapter, NodeAdapter, Resource, Store };
export { Fleetbase, Adapter, BrowserAdapter, EmberJsAdapter, NodeAdapter, Resource, Socket, Store };
export { detectAdapter } from './adapters/detect.js';
export { default as Collection, createCollection, isCollection, iter, objectAt, replace, uniqBy } from './collection.js';
export { FleetbaseError } from './errors.js';
Expand Down Expand Up @@ -46,5 +47,6 @@ export {
} from './utils.js';
export { isResource } from './resource.js';
export type * from './types.js';
export type { SocketTokenResponse } from './socket.js';

export type { DriverStore, ManifestStore, ManifestStopStore, OrderStore, OrganizationStore, ServiceQuoteStore, TrailerStore, VehicleStore, WorkOrderStore } from './fleetbase.js';
Loading
Loading