Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

ff1-cli

Package Overview
Dependencies
Maintainers
1
Versions
14
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ff1-cli - npm Package Compare versions

Comparing version
1.0.7
to
1.0.8
+84
-6
dist/src/utilities/ff1-device.js

@@ -40,6 +40,62 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.isTransientDeviceNetworkError = isTransientDeviceNetworkError;
exports.sendPlaylistToDevice = sendPlaylistToDevice;
const logger = __importStar(require("../logger"));
const ff1_compatibility_1 = require("./ff1-compatibility");
const SEND_RETRY_ATTEMPTS = 3;
const SEND_RETRY_BASE_DELAY_MS = 750;
/**
* Sleep for a short duration before retrying transient network errors.
*
* @param {number} delayMs - Milliseconds to wait
* @returns {Promise<void>} Promise that resolves after the delay
*/
function waitForRetry(delayMs) {
return new Promise((resolve) => {
setTimeout(resolve, delayMs);
});
}
/**
* isTransientDeviceNetworkError returns true when a send failure is likely temporary.
*
* This classifier intentionally targets resolver and route-level failures that are
* common on local mDNS/Wi-Fi environments. Permanent command errors should surface
* immediately without retry loops.
*
* @param {unknown} error - Error thrown by fetch
* @returns {boolean} True when retrying is likely to recover
*/
function isTransientDeviceNetworkError(error) {
if (!(error instanceof Error)) {
return false;
}
const networkCodes = new Set([
'EHOSTUNREACH',
'ENETUNREACH',
'ENOTFOUND',
'EAI_AGAIN',
'ETIMEDOUT',
'ECONNREFUSED',
'ECONNRESET',
]);
const message = error.message || '';
const messageLooksTransient = message.includes('fetch failed') ||
message.includes('getaddrinfo') ||
message.includes('EHOSTUNREACH') ||
message.includes('ENOTFOUND') ||
message.includes('ETIMEDOUT') ||
message.includes('network timeout') ||
message.includes('No route to host');
if (messageLooksTransient) {
return true;
}
const causeCode = error.cause &&
typeof error.cause === 'object' &&
'code' in error.cause &&
typeof error.cause.code === 'string'
? error.cause.code
: undefined;
return Boolean(causeCode && networkCodes.has(causeCode));
}
/**
* Send a DP1 playlist to an FF1 device using the cast API

@@ -123,8 +179,30 @@ *

}
// Make the API request
const response = await fetch(apiUrl, {
method: 'POST',
headers,
body: JSON.stringify(requestBody),
});
// Make the API request with bounded retries for transient local network errors.
let response = null;
for (let attempt = 1; attempt <= SEND_RETRY_ATTEMPTS; attempt += 1) {
try {
response = await fetch(apiUrl, {
method: 'POST',
headers,
body: JSON.stringify(requestBody),
});
break;
}
catch (error) {
const shouldRetry = attempt < SEND_RETRY_ATTEMPTS && isTransientDeviceNetworkError(error);
if (!shouldRetry) {
throw error;
}
const retryDelay = SEND_RETRY_BASE_DELAY_MS * attempt;
logger.warn(`Transient network error while sending playlist (attempt ${attempt}/${SEND_RETRY_ATTEMPTS}): ${error.message}`);
logger.debug(`Retrying playlist send in ${retryDelay}ms`);
await waitForRetry(retryDelay);
}
}
if (!response) {
return {
success: false,
error: 'Failed to send playlist to device after retries',
};
}
// Check response status

@@ -131,0 +209,0 @@ if (!response.ok) {

@@ -240,2 +240,4 @@ # FF1-CLI Documentation

# a clear version message before any cast request is sent.
# The send path also retries transient local-network errors (for example intermittent
# mDNS/Wi-Fi resolver failures) with a short backoff before returning a final error.
# Send a hosted DP-1 playlist

@@ -242,0 +244,0 @@ npm run dev -- send "https://cdn.example.com/playlist.json"

+1
-1
{
"name": "ff1-cli",
"version": "1.0.7",
"version": "1.0.8",
"description": "CLI to fetch NFT information and build DP1 playlists using Grok API",

@@ -5,0 +5,0 @@ "main": "dist/index.js",