Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial Release #51

Merged
merged 3 commits into from
Mar 5, 2025
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
node-version-file: package.json
cache: npm

- run: npm ci
- run: npm ci

- run: npm run lint

Expand Down
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# Delivery Tracker Changelog

## [Initial Release] - {PR_MERGE_DATE}
## [Initial Release] - 2025-03-04

The initial release. Has support for...
Tracks deliveries, packages, and parcels.

Has two commands to start: one to add a new delivery and one to view all the deliveries you're tracking.

Has initial support for the following carriers...
- UPS.
- FedEx.
- USPS.
Binary file modified assets/extension-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified metadata/delivery-tracker-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified metadata/delivery-tracker-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified metadata/delivery-tracker-3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified metadata/delivery-tracker-4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
229 changes: 106 additions & 123 deletions package-lock.json

Large diffs are not rendered by default.

9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,24 @@
"package",
"packages",
"parcel",
"parcels"
"parcels",
"USPS",
"UPS",
"FedEx"
],
"license": "MIT",
"commands": [
{
"name": "track-deliveries",
"title": "Track Deliveries",
"subtitle": "Deliveries, packages, and parcels",
"subtitle": "Delivery Tracker",
"description": "View the deliveries you're tracking.",
"mode": "view"
},
{
"name": "track-new-delivery",
"title": "Track New Delivery",
"subtitle": "Deliveries, packages, and parcels",
"subtitle": "Delivery Tracker",
"description": "Starts tracking a new delivery.",
"mode": "view"
}
Expand Down
20 changes: 14 additions & 6 deletions src/carriers/fedex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ export async function updateFedexTracking(delivery: Delivery): Promise<Package[]
const loginResponse = await loginWithCachedData(apiKey, secretKey);

console.log("Calling FedEx tracking");
const upsTrackingInfo = await track(trackingNumber, loginResponse.access_token);
const fedexTrackingInfo = await track(trackingNumber, loginResponse.access_token);

const packages = convertUpsTrackingToPackages(upsTrackingInfo);
const packages = convertFedexTrackingToPackages(fedexTrackingInfo);

console.log(`Updated tracking for ${trackingNumber}`);

Expand All @@ -59,17 +59,25 @@ async function loginWithCachedData(apiKey: string, secretKey: string): Promise<L
console.log("Logging into FedEx");
loginResponse = await login(apiKey, secretKey);

// expires_in is in seconds and not a timestamp, e.g. `3599`.
// turn it into a timestamp for later validation.
loginResponse.expires_in = new Date().getTime() + loginResponse.expires_in * 1000;

cache.set(cacheKey, JSON.stringify(loginResponse));
} else {
loginResponse = JSON.parse(cache.get(cacheKey) ?? "{}");

const now = new Date().getTime();

if (now + loginResponse.expires_in * 1000 < now + 30 * 1000) {
// we are less than 30 seconds form the access token expiring
if (loginResponse.expires_in < now + 30 * 1000) {
// we are less than 30 seconds from the access token expiring
console.log("Access key expired; logging into FedEx");
loginResponse = await login(apiKey, secretKey);

// expires_in is in seconds and not a timestamp, e.g. `3599`.
// turn it into a timestamp for later validation.
loginResponse.expires_in = new Date().getTime() + loginResponse.expires_in * 1000;

cache.set(cacheKey, JSON.stringify(loginResponse));
}
}
Expand Down Expand Up @@ -158,14 +166,14 @@ async function track(trackingNumber: string, accessToken: string): Promise<Fedex

const trackingResponse = (await response.json()) as FedexTrackingInfo;
if (!trackingResponse) {
console.log("Failed to parse FedEx login response");
console.log("Failed to parse FedEx tracking response");
throw new Error("Failed to parse FedEx track response. Please file a bug report.");
}

return trackingResponse;
}

function convertUpsTrackingToPackages(fedexTrackingInfo: FedexTrackingInfo): Package[] {
function convertFedexTrackingToPackages(fedexTrackingInfo: FedexTrackingInfo): Package[] {
return fedexTrackingInfo.output.completeTrackResults
.flatMap((results) => results.trackResults)
.map((aPackage) => {
Expand Down
9 changes: 5 additions & 4 deletions src/carriers/ups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Delivery } from "../delivery";
const cache = new Cache();
const cacheKey = "upsLogin";
const host = "onlinetools.ups.com";
const deliveredStatusCode = "011";

export async function ableToTrackUpsRemotely(): Promise<boolean> {
const preferences = getPreferenceValues<Preferences.TrackDeliveries>();
Expand Down Expand Up @@ -67,7 +68,7 @@ async function loginWithCachedData(clientId: string, clientSecret: string): Prom
loginResponse = JSON.parse(cache.get(cacheKey) ?? "{}");

if (Number(loginResponse.issued_at) + Number(loginResponse.expires_in) * 1000 < new Date().getTime() + 30 * 1000) {
// we are less than 30 seconds form the access token expiring
// we are less than 30 seconds from the access token expiring
console.log("Access key expired; logging into UPS");
loginResponse = await login(clientId, clientSecret);

Expand All @@ -82,7 +83,7 @@ async function login(clientId: string, clientSecret: string): Promise<LoginRespo
const response = await fetch(`https://${host}/security/v1/oauth/token`, {
method: "POST",
headers: {
Authorization: "Basic " + btoa(clientId + ":" + clientSecret),
Authorization: "Basic " + Buffer.from(clientId + ":" + clientSecret).toString("base64"),
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
Expand Down Expand Up @@ -153,7 +154,7 @@ async function track(trackingNumber: string, accessToken: string): Promise<UpsTr

const trackingResponse = (await response.json()) as UpsTrackingInfo;
if (!trackingResponse) {
console.log("Failed to parse UPS login response");
console.log("Failed to parse UPS tracking response");
throw new Error("Failed to parse UPS track response. Please file a bug report.");
}

Expand All @@ -169,7 +170,7 @@ function convertUpsTrackingToPackages(upsTrackingInfo: UpsTrackingInfo): Package
const scheduledDeliveryDate = aPackage.deliveryDate.find((deliveryDate) => deliveryDate.type === "SDD")?.date;

return {
delivered: aPackage.currentStatus.code === "011",
delivered: aPackage.currentStatus.code === deliveredStatusCode,
deliveryDate: convertUpsDateToDate(deliveryDate || rescheduledDeliveryDate || scheduledDeliveryDate),
activity: [],
};
Expand Down
4 changes: 3 additions & 1 deletion src/carriers/usps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ export async function updateUspsTracking(delivery: Delivery): Promise<Package[]>

return [
{
delivered: new Date() === delivery.manualDeliveryDate,
delivered: delivery.manualDeliveryDate
? new Date().setHours(0, 0, 0, 0) > delivery.manualDeliveryDate.setHours(0, 0, 0, 0)
: false, // truncate the time from both now and the manual delivery date
deliveryDate: delivery.manualDeliveryDate,
activity: [],
},
Expand Down
14 changes: 12 additions & 2 deletions src/debugData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ export const debugDeliveries: Delivery[] = [
carrier: "fedex",
debug: true,
},
{
id: "DD17AC8D-9048-43ED-AF35-4195A2F97243",
name: "no packages",
trackingNumber: "198451726304587",
carrier: "fedex",
debug: true,
},
];

export const debugPackages: PackageMap = {};
Expand Down Expand Up @@ -91,12 +98,12 @@ debugPackages[debugDeliveries[4].id] = {
activity: [],
},
{
deliveryDate: new Date(new Date().getTime() + 1000 * 60 * 60 * 24 * 4), // 2 days ahead
deliveryDate: new Date(new Date().getTime() + 1000 * 60 * 60 * 24 * 2), // 2 days ahead
delivered: false,
activity: [],
},
{
deliveryDate: new Date(new Date().getTime() + 1000 * 60 * 60 * 24 * 4), // 1 days ahead
deliveryDate: new Date(new Date().getTime() + 1000 * 60 * 60 * 24 * 1), // 1 day ahead
delivered: true,
activity: [],
},
Expand All @@ -121,3 +128,6 @@ debugPackages[debugDeliveries[5].id] = {
},
],
};
debugPackages[debugDeliveries[6].id] = {
packages: [],
};
21 changes: 13 additions & 8 deletions src/package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export interface PackageMap {
}

export function deliveryIcon(packages?: Package[]): Icon {
if (!packages || packages.length == 0) {
if (!packages || packages.length === 0) {
// there are no packages for this tracking, possible before data has been gotten from API
return Icon.QuestionMarkCircle;
}
Expand All @@ -43,7 +43,7 @@ export function deliveryIcon(packages?: Package[]): Icon {
export function deliveryStatus(packages?: Package[]): { value: string; color?: Color } {
// check whether all, some, or no packages in a track are delivered

if (!packages || packages.length == 0) {
if (!packages || packages.length === 0) {
return {
value: "No packages",
color: Color.Orange,
Expand All @@ -63,12 +63,13 @@ export function deliveryStatus(packages?: Package[]): { value: string; color?: C
};
}

//find closest estimated delivered package
// find closest estimated delivered package
const closestPackage = getPackageWithEarliestDeliveryDate(packages);

let accessoryText = "En route";
if (closestPackage.deliveryDate) {
accessoryText = calculateDayDifference(closestPackage.deliveryDate).toString() + " days until delivery";
if (closestPackage?.deliveryDate) {
const now = new Date();
accessoryText = calculateDayDifference(closestPackage.deliveryDate, now).toString() + " days until delivery";
}

let accessoryColor = undefined;
Expand All @@ -83,7 +84,11 @@ export function deliveryStatus(packages?: Package[]): { value: string; color?: C
};
}

export function getPackageWithEarliestDeliveryDate(packages: Package[]): Package {
export function getPackageWithEarliestDeliveryDate(packages: Package[]): Package | null {
if (packages.length === 0) {
return null;
}

const now = new Date();

return packages.reduce((closest, current) => {
Expand All @@ -110,10 +115,10 @@ export function getPackageWithEarliestDeliveryDate(packages: Package[]): Package
});
}

export function calculateDayDifference(deliverDate: Date): number {
export function calculateDayDifference(deliveryDate: Date, comparisonDate: Date): number {
const millisecondsInDay = 1000 * 60 * 60 * 24;

const millisecondsDifference = deliverDate.getTime() - new Date().getTime();
const millisecondsDifference = deliveryDate.getTime() - comparisonDate.getTime();
let dayDifference = Math.ceil(millisecondsDifference / millisecondsInDay);

if (dayDifference < 0) {
Expand Down
Loading
Loading