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/nodejs.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
# Clean-install dependencies, build the bundle, and test supported Node.js and browser environments.

name: Node.js CI

Expand All @@ -10,8 +9,7 @@ on:
branches: [ master ]

jobs:
build:

node-tests:
runs-on: ubuntu-latest

strategy:
Expand All @@ -24,8 +22,29 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: npm
- run: npm ci
- run: npm run build
- run: npm run test:node
env:
CI: true

browser-tests:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- name: Use Node.js 22
uses: actions/setup-node@v4
with:
node-version: 22.x
cache: npm
- run: npm ci
- name: Setup Chrome
id: setup-chrome
uses: browser-actions/setup-chrome@v2
- run: npm run build
- run: npm run test:web
env:
CI: true
PUPPETEER_EXECUTABLE_PATH: ${{ steps.setup-chrome.outputs.chrome-path }}
12 changes: 11 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,19 @@ jobs:
- name: Build
run: npm run build

- name: Run tests
- name: Run Node.js tests
run: npm run test:node

- name: Setup Chrome
id: setup-chrome
uses: browser-actions/setup-chrome@v2

- name: Run browser tests
run: npm run test:web
env:
CI: true
PUPPETEER_EXECUTABLE_PATH: ${{ steps.setup-chrome.outputs.chrome-path }}

- name: Verify version matches tag
run: |
PACKAGE_VERSION="v$(node -p "require('./package.json').version")"
Expand Down
74 changes: 44 additions & 30 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,55 +2,69 @@

## Project Overview

mg-api-js is the official JavaScript API wrapper for the MyGeotab telematics platform. It provides a `GeotabApi` class for making authenticated API calls from both browser and Node.js environments. Zero runtime dependencies uses native `fetch` (browser) and `http`/`https` (Node.js).
mg-api-js is the official JavaScript API wrapper for the MyGeotab telematics platform. It provides a `GeotabApi` class for authenticated API calls in browser and Node.js environments. The published package has zero runtime dependencies and uses native `fetch` in browsers and `http`/`https` in Node.js. Supported Node.js versions start at 18.

## Build & Test Commands

| Command | Purpose |
|---|---|
| `npm run build` | Production build → `dist/api.min.js` (webpack + babel, targets IE 10) |
| `npm run serve` | Dev server on port 9000 |
| `npm test` | Build + start dev server + run all tests (node + web) |
| `npm run test:node` | Node.js tests only (fast, no server needed) |
| `npm run test:web` | Browser tests only (Puppeteer, needs dev server) |
| `npm run mocha:node` | Run node specs directly with mocha |
| `npm run build` | Production build → `dist/api.min.js` (webpack + Babel, targeting IE 10 syntax) |
| `npm run serve` | Start the development server on port 9000 |
| `npm test` | Build, start the development server, and run all Node.js and browser tests |
| `npm run test:node` | Run Node.js tests against the committed bundle |
| `npm run test:web` | Start the development server and run browser tests |
| `npm run mocha:node` | Run Node.js specs directly with Mocha |

Browser tests use `puppeteer-core` and require Chrome. The test harness checks common Chrome installation paths; set `PUPPETEER_EXECUTABLE_PATH` when Chrome is installed elsewhere. GitHub Actions installs Chrome with `browser-actions/setup-chrome` and supplies this variable.

## Architecture

```
lib/api.js → UMD entry point, webpack bundles this to dist/api.min.js
lib/GeotabApi.js → Public facade: authenticate(), call(), multiCall(), getSession(), forget()
lib/ApiHelper.js → Internal logic: auth flow, credential management, error handling, HTTP dispatch
lib/HttpCall.js → Transport: fetch (browser) or http/https (Node), timeout via Promise.race
lib/LocalStorageCredentialStore.js → Credential persistence (localStorage or mock)
lib/LocalStorageMock.js → In-memory localStorage replacement for Node.js
lib/api.js → UMD entry point bundled to dist/api.min.js
lib/GeotabApi.js → Public facade: authenticate(), call(), multiCall(), getSession(), forget()
lib/ApiHelper.js → Authentication, credential-store, retry, and response handling
lib/HttpCall.js → Browser fetch or Node.js http/https transport
lib/LocalStorageCredentialStore.js → Browser localStorage adapter or Node.js in-memory fallback
lib/LocalStorageMock.js → In-memory localStorage replacement for Node.js
```

## Credential Persistence

- `rememberMe` defaults to `true`. In browsers this persists session credentials as plaintext JSON in same-origin `localStorage`; in Node.js the default store is process-local memory.
- `rememberMe: false` prevents new persistence but does not clear credentials written by an earlier instance.
- `forget()` refreshes the session; it is not a logout API and may persist replacement credentials.
- `newCredentialStore` is a supported synchronous adapter with `get()`, `set(credentials, server)`, and `clear()` methods. `get()` returns a falsey value or `{ credentials, server }`.
- Keep the README threat model and custom-store contract aligned with credential-management changes.

## Code Conventions

- **Module format**: CommonJS with `exports.default = ClassName`; UMD wrapper in `api.js`
- **Naming**: PascalCase files and classes (`GeotabApi.js`), camelCase methods, underscore-prefix for private members (`this._helper`)
- **Style**: 2-space indent, LF line endings, no trailing whitespace (see `.editorconfig`)
- **Patterns**: ES2017 classes with async/await; dual callback/promise API on all public methods
- **Tests**: Mocha + Chai (BDD style), Nock for HTTP mocking, Puppeteer for browser tests
- **Test file naming**: `{context}-{Feature}.spec.js` (e.g., `node-Credentials.spec.js`)
- **Module format**: CommonJS with `exports.default = ClassName`; UMD wrapper in `lib/api.js`
- **Naming**: PascalCase files/classes, camelCase methods, underscore-prefixed private facade members
- **Style**: Spaces, LF endings, and no trailing whitespace according to `.editorconfig`
- **Patterns**: ES2017 classes with async/await and dual callback/promise public APIs
- **Tests**: Mocha + Chai, Nock for Node.js HTTP mocks, and Puppeteer Core for browser tests
- **Test naming**: `{context}-{Feature}.spec.js`, such as `node-Credentials.spec.js`

## Git Conventions

- Commit messages: lowercase, imperative, concise, single-line (e.g., `refactor error handling for DRYness`)
- No conventional commits prefix
- Branch names use Jira ticket IDs (e.g., `MYG-62290`)
- PRs merge to `master`
- Commit messages are lowercase, imperative, concise, and single-line (for example, `refactor error handling for DRYness`).
- Do not add conventional-commit prefixes unless the repository convention changes.
- Existing Jira-linked branches use ticket IDs such as `MYG-62290`; PRs merge to `master`.

## Rules

- All unit tests must pass before making a commit. Run `npm run test:node` to verify.
- Run `npm test` before committing changes that affect runtime code, bundling, credentials, or browser behavior.
- At minimum, run `npm run test:node` for Node-only test changes.
- Rebuild and commit `dist/api.min.js`, its source map, and license file when `lib/` changes.
- Keep the package version, README CDN pin, source banner, lockfile, and distribution license banner aligned; `node-Version.spec.js` enforces this.
- Do not replace the pinned README CDN example with an unversioned package URL.

## Key Details

- The bundled output `dist/api.min.js` is committed to the repo
- Browser vs Node detection uses `typeof window` checks
- Auto re-authentication on `InvalidUserException` (retries failed call after re-auth)
- Default API timeout: 180 seconds
- API endpoint format: `https://{server}/apiv1/`
- CI: GitHub Actions, Node.js 16.x
- The generated `dist/` bundle is committed to the repository.
- Browser versus Node.js transport detection uses `typeof window`.
- `InvalidUserException` triggers authentication refresh and one retry of the failed call.
- Default API timeout: 180 seconds.
- API endpoint format: `https://{server}/apiv1/`.
- CI runs Node.js tests on 18, 20, and 22, plus browser tests on Node.js 22.
- The publish workflow builds and runs both Node.js and browser tests on Node.js 24 before publishing.
74 changes: 54 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,13 @@ $ npm install --save mg-api-js

### Browser

To access the wrapper in the browser, the library needs to be loaded in. This can be done by downloading `api.min.js` and referencing the file as needed.

Alternatively, this can be done using jsdelivr CDN:
To use the wrapper in a browser, download `dist/api.min.js` from a release or load an exact version from jsDelivr:

```html
<!-- This will grab the most up to date version of the api wrapper -->
<script src="https://cdn.jsdelivr.net/npm/mg-api-js"></script>

<!-- This will grab the specified version of the api wrapper -->
<script src="https://cdn.jsdelivr.net/npm/mg-api-js@2.0.1"></script>
<script src="https://cdn.jsdelivr.net/npm/mg-api-js@3.0.2/dist/api.min.js"></script>
```

For more options using jsdelivr, visit the [jsdelivr documentation](https://www.jsdelivr.com/features).
Pin both the package version and file path in production. An unversioned CDN URL can change when a new release is published, so upgrades should be reviewed and deployed deliberately. For other URL formats, see the [jsDelivr documentation](https://www.jsdelivr.com/features).

## Creating the Object
**Note**: *As of v2.0.0, the GeotabApi object no longer accepts an authentication callback.*
Expand Down Expand Up @@ -65,7 +59,6 @@ const authentication = {
credentials: {
database: 'database',
userName: 'username',
password: 'password',
sessionId: '123456...'
},
path: 'serverAddress'
Expand All @@ -81,9 +74,9 @@ This optional parameter allows you to define some default behavior of the api:

| Argument | Type | Description | Default |
| --- | --- | --- | --- |
| rememberMe | *boolean* | Determines whether or not to store the credentials/session in the datastore | `true` |
| timeout | *number* | The length of time the wrapper will wait for a response from the server (in seconds) | `3` |
| newCredentialStore | *object* | Overrides the default datastore for remembered credentials/sessions | `false` |
| rememberMe | *boolean* | Determines whether authenticated credentials are persisted in the credential store | `true` |
| timeout | *number* | The length of time the wrapper will wait for a response from the server (in seconds) | `180` |
| newCredentialStore | *object* | Uses a custom synchronous credential store instead of the default store | `false` |
| fullResponse | *boolean* | Removes error handling and provides the full [Http Server Response](https://nodejs.org/api/http.html#class-httpserverresponse) when in a node environment or the full [Fetch Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) when in a browser environment. More information in the **Full Response** section | `false` |

Example options object:
Expand All @@ -98,16 +91,57 @@ const options = {
}
```

#### Providing your own Datastore
#### Credential persistence and threat model

With the default `rememberMe: true`, browsers store the authenticated credential object as plaintext JSON in same-origin [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). This normally contains a session ID, user name, and database; if you provide a session credential object containing other secrets, those values are stored too. The Node.js default is an in-memory `LocalStorageMock` and does not persist across processes.

A stored session ID is a bearer credential. Any script executing on the same origin can read it, including code introduced through cross-site scripting (XSS) or a compromised third-party dependency. Browser extensions, shared browser profiles, and local device access may also expose it. Applications using the default store should apply normal XSS and dependency-supply-chain defenses.

Use `rememberMe: false` when persistent browser sessions are not required or the application cannot accept this risk. This prevents newly authenticated credentials from being written, but it does **not** remove credentials saved by an earlier instance. Clear the custom store during logout or migration. When using the default browser store, remove the `geotabAPI_credentials` and `geotabAPI_server` localStorage entries.

`api.forget()` is a session-refresh helper, not a logout operation: it clears the store, immediately authenticates again, and can persist the replacement session when `rememberMe` is enabled.

By default, the wrapper will use [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) in browsers, and a [LocalStorageMock](https://github.com/Geotab/mg-api-js/blob/master/lib/LocalStorageMock.js) in node.
#### Providing a custom credential store

The supported `newCredentialStore` option accepts a synchronous object with this interface:

- `get()` returns `false`, `null`, or `{ credentials, server }`.
- `set(credentials, server)` stores the supplied credential object and server name.
- `clear()` removes the stored values.

For example, this memory-only store shares credentials between API instances without writing them to `localStorage`:

```javascript
class MemoryCredentialStore {
constructor() {
this.value = false;
}

If you want to override this behavior, you can provide an instance of a datastore object in the options object when constructing the wrapper.
get() {
return this.value;
}

set(credentials, server) {
this.value = { credentials, server };
}

clear() {
this.value = false;
}
}

const credentialStore = new MemoryCredentialStore();
const options = {
rememberMe: true,
newCredentialStore: credentialStore
};

const api = new GeotabApi(authentication, options);
// On logout, discard the API instance and clear the application-owned store.
credentialStore.clear();
```

At minimum, the datastore must have the following methods:
- `get()`
- `set()`
- `clear()`
A custom store can integrate with an application-controlled credential broker or provide memory-only behavior. Encryption whose key is available to the same page JavaScript does not protect credentials from malicious same-origin scripts, because those scripts can invoke the store or read credentials after retrieval.

## Methods

Expand Down
2 changes: 1 addition & 1 deletion dist/api.min.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/api.min.js.LICENSE.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*! mg-api-js - v2.0.1
* Release on: 2020-04-27
/*! mg-api-js - v3.0.2
* Release on: 2026-04-13
* Copyright (c) 2020 Geotab Inc
* Licensed MIT */

Expand Down
2 changes: 1 addition & 1 deletion dist/api.min.js.map

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions lib/ApiHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,10 @@ class ApiHelper {

/**
* Sets the local credentials
* @param {object} result credentials object using result of a promise
* @param {object} credentials credentials object using result of a promise
*/
setLocalCredentials(result, server) {
this.credentialStore.set(result.credentials, server);
setLocalCredentials(credentials, server) {
this.credentialStore.set(credentials, server);
}

clearLocalCredentials() {
Expand Down Expand Up @@ -207,7 +207,7 @@ class ApiHelper {
let server = response.data.result.path === 'ThisServer' ? this.server : response.data.result.path;
this.server = server;
if (this.rememberMe) {
this.setLocalCredentials(response.data.result, server);
this.setLocalCredentials(response.data.result.credentials, server);
}
}
return response;
Expand Down
4 changes: 2 additions & 2 deletions lib/api.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*! mg-api-js - v2.0.1
* Release on: 2020-04-27
/*! mg-api-js - v3.0.2
* Release on: 2026-04-13
* Copyright (c) 2020 Geotab Inc
* Licensed MIT */
// UMD Declaration
Expand Down
Loading
Loading