Skip to content
Open
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
20 changes: 20 additions & 0 deletions .c8rc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"all": true,
"include": [
"lib/**/*.js"
],
"exclude": [
"test/**",
"**/node_modules/**"
],
"reporter": [
"text",
"text-summary",
"lcov"
],
"check-coverage": true,
"lines": 70,
"statements": 70,
"functions": 70,
"branches": 60
}
22 changes: 19 additions & 3 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
{
"extends": "airbnb-base",
"parserOptions": {
"ecmaVersion": 2022,
"sourceType": "module"
},
"env": {
"node": true,
"es2022": true
},
"rules": {
"import/no-extraneous-dependencies": ["error", {"devDependencies": true}]
}
}
"import/no-extraneous-dependencies": ["error", {"devDependencies": true}],
"import/extensions": ["error", "ignorePackages", {"js": "always"}],
"prefer-destructuring": ["error", {"array": false}]
},
"overrides": [
{
"files": ["test/**/*.js"],
"env": {"mocha": true}
}
]
}
82 changes: 82 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Continuous Integration: runs on every pull request and on pushes to master.
Comment thread
joao-onfleet marked this conversation as resolved.
# Complements npm-publish.yml (which only runs at release time) by giving
# fast feedback on lint, tests, and security advisories before code is merged.

name: CI

# Least-privilege default for GITHUB_TOKEN: all jobs here only read the repo
# (checkout + npm). Individual jobs can escalate with a job-level permissions
# key if they ever need more.
permissions:
contents: read

on:
pull_request:
branches: [master]
push:
branches: [master]

jobs:
test:
name: Test (Node ${{ matrix.node-version }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Cover the declared floor (20) and current/next LTS lines so a
# runtime regression (e.g. the removed `assert {}` import syntax) is
# caught here instead of at release.
node-version: [20, 22, 24]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: npm
- run: npm ci
- run: npm test

coverage:
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
name: Coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
# Runs the suite under c8 and enforces the thresholds in .c8rc.json.
- run: npm run coverage
- uses: actions/upload-artifact@v4
if: always()
with:
name: coverage-lcov
path: coverage/lcov.info
if-no-files-found: ignore

audit:
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
name: Security audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
# Fail only on high/critical advisories. Lower-severity findings are
# reported by Dependabot; tighten this threshold once the tree is clean.
- run: npm audit --audit-level=high

lint:
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm run lint
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
3 changes: 2 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
import onfleet from './lib/onfleet.js';
export default onfleet;

export default onfleet;
96 changes: 46 additions & 50 deletions lib/Methods.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ const Methods = async (key, api, ...args) => {
if (args.length === 0 && operations === 'GET' && altPath) {
url = `${api.api.baseUrl}${altPath}`;
}

// 1 or more arguments
if (args.length >= 1 && ['GET', 'DELETE', 'PUT'].includes(operations)) {
if (['name', 'shortId', 'phone', 'workers', 'organizations', 'teams'].includes(args[1])) {
Expand Down Expand Up @@ -102,11 +102,11 @@ const Methods = async (key, api, ...args) => {

// Query Params extension
if (queryParams) {
for (const element of args) {
args.forEach((element) => {
if (util.isQueryParam(element)) {
url = util.appendQueryParameters(url, element);
}
}
});
}

// Reference https://docs.onfleet.com/reference/delivery-manifest
Expand All @@ -115,68 +115,64 @@ const Methods = async (key, api, ...args) => {
if (item.hubId && item.workerId) {
body = {
path: `providers/manifest/generate?hubId=${item.hubId}&workerId=${item.workerId}`,
method: "GET",
method: 'GET',
};
hasBody = true;
}
if (item.googleApiKey) {
api.api.headers["X-API-Key"] = `Google ${item.googleApiKey}`;
// eslint-disable-next-line no-param-reassign
api.api.headers['X-API-Key'] = `Google ${item.googleApiKey}`;
}
if (item.startDate || item.endDate) {
const queryParams = {};
if (item.startDate) queryParams.startDate = item.startDate;
if (item.endDate) queryParams.endDate = item.endDate;
url = util.appendQueryParameters(url, queryParams);
const dateQueryParams = {};
if (item.startDate) dateQueryParams.startDate = item.startDate;
if (item.endDate) dateQueryParams.endDate = item.endDate;
url = util.appendQueryParameters(url, dateQueryParams);
}
});
}

// Send the HTTP request through the rate limiter
try {
const res = await limiter.schedule(() => fetch(url, {
method: operations,
headers: api.api.headers,
timeout: timeoutInMilliseconds,
body: hasBody ? JSON.stringify(body) : undefined,
}));

// For every request, we compare the reservoir with the remaining rate limit in the header
const reservoir = await limiter.currentReservoir();
const rateLimitRemaining = res.headers.get('x-ratelimit-remaining');
if (reservoir < rateLimitRemaining) {
reassignRate(rateLimitRemaining);
}

if (res.ok) {
if (operations === 'DELETE') {
return res.status;
}
return res.json().catch(() => res.status);
}
const res = await limiter.schedule(() => fetch(url, {
method: operations,
headers: api.api.headers,
timeout: timeoutInMilliseconds,
body: hasBody ? JSON.stringify(body) : undefined,
}));

// For every request, we compare the reservoir with the remaining rate limit in the header
const reservoir = await limiter.currentReservoir();
const rateLimitRemaining = res.headers.get('x-ratelimit-remaining');
if (reservoir < rateLimitRemaining) {
reassignRate(rateLimitRemaining);
}

const error = await res.json();
const errorCode = error.message.error;
const errorInfo = [
error.message.message,
errorCode,
error.message.cause,
error.message.request,
];

if (errorCode === 2300) {
throw new RateLimitError(...errorInfo);
} else if (errorCode >= 1100 && errorCode <= 1108) {
throw new PermissionError(...errorInfo);
} else if (errorCode >= 2500) {
throw new ServiceError(...errorInfo);
} else if (errorCode === 2218) { // Precondition error for Auto-Dispatch
throw new ServiceError(...errorInfo);
if (res.ok) {
if (operations === 'DELETE') {
return res.status;
}
throw new HttpError(...errorInfo);
return res.json().catch(() => res.status);
}

} catch (error) {
throw error;
const error = await res.json();
const errorCode = error.message.error;
const errorInfo = [
error.message.message,
errorCode,
error.message.cause,
error.message.request,
];

if (errorCode === 2300) {
throw new RateLimitError(...errorInfo);
} else if (errorCode >= 1100 && errorCode <= 1108) {
throw new PermissionError(...errorInfo);
} else if (errorCode >= 2500) {
throw new ServiceError(...errorInfo);
} else if (errorCode === 2218) { // Precondition error for Auto-Dispatch
throw new ServiceError(...errorInfo);
}
throw new HttpError(...errorInfo);
};

export default Methods;
12 changes: 4 additions & 8 deletions lib/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,13 @@ const createError = (name, message, status = null, cause = null, request = null)
// Create specific error types using the factory function
const ValidationError = (message) => createError('ValidationError', message);

const PermissionError = (message, status, cause, request) =>
createError('PermissionError', message, status, cause, request);
const PermissionError = (message, status, cause, request) => createError('PermissionError', message, status, cause, request);

const HttpError = (message, status, cause, request) =>
createError('HttpError', message, status, cause, request);
const HttpError = (message, status, cause, request) => createError('HttpError', message, status, cause, request);

const RateLimitError = (message, status, cause, request) =>
createError('RateLimitError', message, status, cause, request);
const RateLimitError = (message, status, cause, request) => createError('RateLimitError', message, status, cause, request);

const ServiceError = (message, status, cause, request) =>
createError('ServiceError', message, status, cause, request);
const ServiceError = (message, status, cause, request) => createError('ServiceError', message, status, cause, request);

// Export the custom error creators
export {
Expand Down
27 changes: 16 additions & 11 deletions lib/onfleet.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
// Define constants for default values
const DEFAULT_URL = 'https://onfleet.com';
const DEFAULT_PATH = '/api';
const DEFAULT_API_VERSION = '/v2';
const DEFAULT_TIMEOUT = 70000;

import { createRequire } from 'module';
import * as constants from './constants.js';
import * as util from './util.js';
import { ValidationError } from './error.js';
import packageData from '../package.json' assert { type: 'json' };

import Admins from './resources/Administrators.js';
import Containers from './resources/Containers.js';
import Destinations from './resources/Destinations.js';
Expand All @@ -22,6 +15,17 @@ import Workers from './resources/Workers.js';
import Webhooks from './resources/Webhooks.js';
import CustomFields from './resources/CustomFields.js';

// Define constants for default values
const DEFAULT_URL = 'https://onfleet.com';
const DEFAULT_PATH = '/api';
const DEFAULT_API_VERSION = '/v2';
const DEFAULT_TIMEOUT = 70000;

// Read package metadata without a JSON import attribute so the module loads
// consistently across Node versions and tooling (ESLint, bundlers).
const require = createRequire(import.meta.url);
const packageData = require('../package.json');

const { name, version } = packageData;

const resources = {
Expand Down Expand Up @@ -84,6 +88,7 @@ class Onfleet {
this.api.headers = { ...this.api.headers, ...headers };
}

// eslint-disable-next-line class-methods-use-this
initBottleneckOptions({
LIMITER_RESERVOIR = constants.LIMITER_RESERVOIR,
LIMITER_WAIT_UPON_DEPLETION = constants.LIMITER_WAIT_UPON_DEPLETION,
Expand All @@ -99,14 +104,14 @@ class Onfleet {
}

initResources() {
Object.entries(resources).forEach(([name, Resource]) => {
const endpoint = name.toLowerCase();
Object.entries(resources).forEach(([resourceName, Resource]) => {
const endpoint = resourceName.toLowerCase();
this[endpoint] = new Resource(this);
});
}

async verifyKey() {
return await util.authenticate(this.api);
return util.authenticate(this.api);
}
}

Expand Down
2 changes: 1 addition & 1 deletion lib/resources/Containers.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ export default class Containers extends Resource {
},
});
}
};
}
8 changes: 4 additions & 4 deletions lib/resources/Routeplan.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ export default class Routeplan extends Resource {
path: '/routePlans/:routePlanId',
method: 'PUT',
},
addTasksToRoutePlan:{
path:'/routePlans/:routePlanId/tasks',
method:'PUT',
addTasksToRoutePlan: {
path: '/routePlans/:routePlanId/tasks',
method: 'PUT',
},
deleteOne: {
path: '/routePlans/:routePlanId',
method: 'DELETE',
}
},
});
}
}
2 changes: 1 addition & 1 deletion lib/resources/Teams.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export default class Teams extends Resource {
matchMetadata: {
path: '/teams/metadata',
method: 'POST',
}
},
});
}
}
4 changes: 1 addition & 3 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,4 @@ export const appendQueryParameters = (url, queryObj) => {
return path;
};

export const isQueryParam = (obj) => {
return typeof obj === 'object';
};
export const isQueryParam = (obj) => typeof obj === 'object';
Loading
Loading