@trops/dash-core 0.1.160 → 0.1.162
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/electron/index.js +225 -16
- package/dist/electron/index.js.map +1 -1
- package/dist/index.esm.js +972 -336
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +972 -336
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/electron/index.js
CHANGED
|
@@ -29,11 +29,12 @@ var require$$2$4 = require('os');
|
|
|
29
29
|
var require$$4$1 = require('url');
|
|
30
30
|
var require$$2$3 = require('vm');
|
|
31
31
|
var require$$1$5 = require('croner');
|
|
32
|
+
var require$$1$6 = require('node-vibrant/node');
|
|
33
|
+
var require$$3$4 = require('http');
|
|
32
34
|
var require$$0$c = require('events');
|
|
33
|
-
var require$$
|
|
34
|
-
var require$$3$4 = require('net');
|
|
35
|
+
var require$$3$5 = require('net');
|
|
35
36
|
var require$$4$2 = require('tls');
|
|
36
|
-
var require$$1$
|
|
37
|
+
var require$$1$7 = require('crypto');
|
|
37
38
|
var require$$0$a = require('zlib');
|
|
38
39
|
var require$$0$b = require('buffer');
|
|
39
40
|
|
|
@@ -3748,7 +3749,7 @@ const { getFileContents: getFileContents$5, writeToFile: writeToFile$2 } = file;
|
|
|
3748
3749
|
const ObjectsToCsv = require$$5;
|
|
3749
3750
|
const Transform = transform;
|
|
3750
3751
|
const { extractColorsFromImageURL: extractColorsFromImageURL$1 } = color;
|
|
3751
|
-
const https$
|
|
3752
|
+
const https$2 = require$$8;
|
|
3752
3753
|
const appName$5 = "Dashboard";
|
|
3753
3754
|
|
|
3754
3755
|
const dataController$1 = {
|
|
@@ -3901,7 +3902,7 @@ const dataController$1 = {
|
|
|
3901
3902
|
|
|
3902
3903
|
const writeStream = fs$8.createWriteStream(resolvedFilepath);
|
|
3903
3904
|
|
|
3904
|
-
https$
|
|
3905
|
+
https$2
|
|
3905
3906
|
.get(url, (resp) => {
|
|
3906
3907
|
resp.on("data", (chunk) => {
|
|
3907
3908
|
writeStream.write(chunk);
|
|
@@ -38774,10 +38775,14 @@ css$1.stringify = stringify;
|
|
|
38774
38775
|
*
|
|
38775
38776
|
* Color extraction pipeline for generating themes from a website URL.
|
|
38776
38777
|
* Extracts brand colors from HTML meta tags, CSS custom properties,
|
|
38777
|
-
*
|
|
38778
|
+
* computed styles, and favicon/logo images (via node-vibrant).
|
|
38778
38779
|
*/
|
|
38779
38780
|
|
|
38780
38781
|
const css = css$1;
|
|
38782
|
+
const { Vibrant } = require$$1$6;
|
|
38783
|
+
const https$1 = require$$8;
|
|
38784
|
+
const http$2 = require$$3$4;
|
|
38785
|
+
const { URL: URL$2 } = require$$4$1;
|
|
38781
38786
|
|
|
38782
38787
|
// ─── Color conversion helpers ───────────────────────────────────────────────
|
|
38783
38788
|
|
|
@@ -39072,6 +39077,178 @@ function extractComputedColors(computedStyles) {
|
|
|
39072
39077
|
return results;
|
|
39073
39078
|
}
|
|
39074
39079
|
|
|
39080
|
+
// ─── Favicon extraction ──────────────────────────────────────────────────────
|
|
39081
|
+
|
|
39082
|
+
/**
|
|
39083
|
+
* Parse HTML to find favicon and apple-touch-icon URLs.
|
|
39084
|
+
* Prefers apple-touch-icon (higher resolution) and largest available sizes.
|
|
39085
|
+
* @param {string} htmlContent - Raw HTML string
|
|
39086
|
+
* @returns {Array<{url: string, priority: number}>} Sorted by priority (highest first)
|
|
39087
|
+
*/
|
|
39088
|
+
function extractFaviconUrls(htmlContent) {
|
|
39089
|
+
if (!htmlContent) return [];
|
|
39090
|
+
const icons = [];
|
|
39091
|
+
|
|
39092
|
+
// apple-touch-icon (higher resolution, best for extraction)
|
|
39093
|
+
const appleTouchPattern =
|
|
39094
|
+
/<link[^>]*rel\s*=\s*["']apple-touch-icon(?:-precomposed)?["'][^>]*>/gi;
|
|
39095
|
+
let match;
|
|
39096
|
+
while ((match = appleTouchPattern.exec(htmlContent)) !== null) {
|
|
39097
|
+
const hrefMatch = match[0].match(/href\s*=\s*["']([^"']+)["']/i);
|
|
39098
|
+
if (hrefMatch) {
|
|
39099
|
+
const sizesMatch = match[0].match(/sizes\s*=\s*["'](\d+)x(\d+)["']/i);
|
|
39100
|
+
const size = sizesMatch ? parseInt(sizesMatch[1], 10) : 180; // apple-touch-icon defaults to 180
|
|
39101
|
+
icons.push({ url: hrefMatch[1], priority: 100 + size });
|
|
39102
|
+
}
|
|
39103
|
+
}
|
|
39104
|
+
|
|
39105
|
+
// Standard favicon link tags (icon, shortcut icon)
|
|
39106
|
+
const iconPattern =
|
|
39107
|
+
/<link[^>]*rel\s*=\s*["'](?:shortcut\s+)?icon["'][^>]*>/gi;
|
|
39108
|
+
while ((match = iconPattern.exec(htmlContent)) !== null) {
|
|
39109
|
+
const hrefMatch = match[0].match(/href\s*=\s*["']([^"']+)["']/i);
|
|
39110
|
+
if (hrefMatch) {
|
|
39111
|
+
const sizesMatch = match[0].match(/sizes\s*=\s*["'](\d+)x(\d+)["']/i);
|
|
39112
|
+
const size = sizesMatch ? parseInt(sizesMatch[1], 10) : 16;
|
|
39113
|
+
icons.push({ url: hrefMatch[1], priority: size });
|
|
39114
|
+
}
|
|
39115
|
+
}
|
|
39116
|
+
|
|
39117
|
+
// Sort by priority descending (prefer largest / apple-touch-icon)
|
|
39118
|
+
icons.sort((a, b) => b.priority - a.priority);
|
|
39119
|
+
return icons;
|
|
39120
|
+
}
|
|
39121
|
+
|
|
39122
|
+
/**
|
|
39123
|
+
* Resolve a potentially relative URL against a base URL.
|
|
39124
|
+
* @param {string} href - The href from the HTML (may be relative)
|
|
39125
|
+
* @param {string} baseUrl - The page URL to resolve against
|
|
39126
|
+
* @returns {string|null} Absolute URL or null if invalid
|
|
39127
|
+
*/
|
|
39128
|
+
function resolveUrl(href, baseUrl) {
|
|
39129
|
+
try {
|
|
39130
|
+
return new URL$2(href, baseUrl).href;
|
|
39131
|
+
} catch {
|
|
39132
|
+
return null;
|
|
39133
|
+
}
|
|
39134
|
+
}
|
|
39135
|
+
|
|
39136
|
+
/**
|
|
39137
|
+
* Fetch a URL and return the response as a Buffer.
|
|
39138
|
+
* Follows redirects (up to 5). Times out after 10 seconds.
|
|
39139
|
+
* @param {string} url - Absolute URL to fetch
|
|
39140
|
+
* @returns {Promise<Buffer>}
|
|
39141
|
+
*/
|
|
39142
|
+
function fetchBuffer(url) {
|
|
39143
|
+
return new Promise((resolve, reject) => {
|
|
39144
|
+
const parsedUrl = new URL$2(url);
|
|
39145
|
+
const client = parsedUrl.protocol === "https:" ? https$1 : http$2;
|
|
39146
|
+
const request = client.get(
|
|
39147
|
+
url,
|
|
39148
|
+
{ timeout: 10000, headers: { "User-Agent": "Dash/1.0" } },
|
|
39149
|
+
(res) => {
|
|
39150
|
+
// Follow redirects
|
|
39151
|
+
if (
|
|
39152
|
+
res.statusCode >= 300 &&
|
|
39153
|
+
res.statusCode < 400 &&
|
|
39154
|
+
res.headers.location
|
|
39155
|
+
) {
|
|
39156
|
+
const redirectUrl = resolveUrl(res.headers.location, url);
|
|
39157
|
+
if (redirectUrl) {
|
|
39158
|
+
fetchBuffer(redirectUrl).then(resolve).catch(reject);
|
|
39159
|
+
return;
|
|
39160
|
+
}
|
|
39161
|
+
}
|
|
39162
|
+
if (res.statusCode !== 200) {
|
|
39163
|
+
reject(new Error(`HTTP ${res.statusCode}`));
|
|
39164
|
+
return;
|
|
39165
|
+
}
|
|
39166
|
+
const chunks = [];
|
|
39167
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
39168
|
+
res.on("end", () => resolve(Buffer.concat(chunks)));
|
|
39169
|
+
res.on("error", reject);
|
|
39170
|
+
},
|
|
39171
|
+
);
|
|
39172
|
+
request.on("error", reject);
|
|
39173
|
+
request.on("timeout", () => {
|
|
39174
|
+
request.destroy();
|
|
39175
|
+
reject(new Error("Timeout"));
|
|
39176
|
+
});
|
|
39177
|
+
});
|
|
39178
|
+
}
|
|
39179
|
+
|
|
39180
|
+
/**
|
|
39181
|
+
* Extract colors from favicon/logo images using node-vibrant.
|
|
39182
|
+
* Tries icons in priority order (apple-touch-icon first, largest first).
|
|
39183
|
+
* Returns on the first successful extraction.
|
|
39184
|
+
*
|
|
39185
|
+
* @param {string} htmlContent - Raw HTML to parse for icon URLs
|
|
39186
|
+
* @param {string} baseUrl - Page URL for resolving relative icon paths
|
|
39187
|
+
* @returns {Promise<Array<{hex: string, source: string, confidence: number}>>}
|
|
39188
|
+
*/
|
|
39189
|
+
async function extractFaviconColors(htmlContent, baseUrl) {
|
|
39190
|
+
const iconEntries = extractFaviconUrls(htmlContent);
|
|
39191
|
+
if (iconEntries.length === 0) {
|
|
39192
|
+
// Fallback: try /favicon.ico at the domain root
|
|
39193
|
+
try {
|
|
39194
|
+
const rootFavicon = new URL$2("/favicon.ico", baseUrl).href;
|
|
39195
|
+
iconEntries.push({ url: rootFavicon, priority: 1 });
|
|
39196
|
+
} catch {
|
|
39197
|
+
return [];
|
|
39198
|
+
}
|
|
39199
|
+
}
|
|
39200
|
+
|
|
39201
|
+
for (const entry of iconEntries) {
|
|
39202
|
+
const absoluteUrl = resolveUrl(entry.url, baseUrl);
|
|
39203
|
+
if (!absoluteUrl) continue;
|
|
39204
|
+
|
|
39205
|
+
try {
|
|
39206
|
+
const buffer = await fetchBuffer(absoluteUrl);
|
|
39207
|
+
const palette = await Vibrant.from(buffer).getPalette();
|
|
39208
|
+
|
|
39209
|
+
const results = [];
|
|
39210
|
+
const swatchNames = [
|
|
39211
|
+
"Vibrant",
|
|
39212
|
+
"DarkVibrant",
|
|
39213
|
+
"LightVibrant",
|
|
39214
|
+
"Muted",
|
|
39215
|
+
"DarkMuted",
|
|
39216
|
+
"LightMuted",
|
|
39217
|
+
];
|
|
39218
|
+
|
|
39219
|
+
for (const name of swatchNames) {
|
|
39220
|
+
const swatch = palette[name];
|
|
39221
|
+
if (!swatch) continue;
|
|
39222
|
+
const [r, g, b] = swatch.rgb;
|
|
39223
|
+
const hex = rgbToHex({
|
|
39224
|
+
r: Math.round(r),
|
|
39225
|
+
g: Math.round(g),
|
|
39226
|
+
b: Math.round(b),
|
|
39227
|
+
});
|
|
39228
|
+
results.push({
|
|
39229
|
+
hex,
|
|
39230
|
+
source: "favicon",
|
|
39231
|
+
confidence: 0.7,
|
|
39232
|
+
});
|
|
39233
|
+
}
|
|
39234
|
+
|
|
39235
|
+
if (results.length > 0) {
|
|
39236
|
+
console.log(
|
|
39237
|
+
`[themeFromUrlController] Favicon vibrant: ${results.length} swatches from ${absoluteUrl}`,
|
|
39238
|
+
);
|
|
39239
|
+
return results;
|
|
39240
|
+
}
|
|
39241
|
+
} catch (err) {
|
|
39242
|
+
console.warn(
|
|
39243
|
+
`[themeFromUrlController] Favicon extraction failed for ${absoluteUrl}: ${err.message}`,
|
|
39244
|
+
);
|
|
39245
|
+
// Try next icon
|
|
39246
|
+
}
|
|
39247
|
+
}
|
|
39248
|
+
|
|
39249
|
+
return [];
|
|
39250
|
+
}
|
|
39251
|
+
|
|
39075
39252
|
// ─── Merge & rank ────────────────────────────────────────────────────────────
|
|
39076
39253
|
|
|
39077
39254
|
/**
|
|
@@ -39198,13 +39375,23 @@ function mergeAndRank(allColors, maxColors = 6) {
|
|
|
39198
39375
|
/**
|
|
39199
39376
|
* Extract a ranked color palette from website content.
|
|
39200
39377
|
*
|
|
39378
|
+
* When `baseUrl` is provided, also extracts colors from favicon/logo images
|
|
39379
|
+
* via node-vibrant (async). Without `baseUrl`, runs synchronously using only
|
|
39380
|
+
* meta tags, CSS vars, and computed styles.
|
|
39381
|
+
*
|
|
39201
39382
|
* @param {Object} params
|
|
39202
39383
|
* @param {string} params.htmlContent - Raw HTML of the page
|
|
39203
39384
|
* @param {string} params.cssContent - Concatenated CSS content
|
|
39204
39385
|
* @param {Object} params.computedStyles - Map of selector → { color, backgroundColor, borderColor }
|
|
39205
|
-
* @
|
|
39206
|
-
|
|
39207
|
-
|
|
39386
|
+
* @param {string} [params.baseUrl] - Page URL for resolving favicon paths (enables image extraction)
|
|
39387
|
+
* @returns {Promise<{ palette: Array, rawCount: number }>}
|
|
39388
|
+
*/
|
|
39389
|
+
async function extractColorsFromUrl({
|
|
39390
|
+
htmlContent,
|
|
39391
|
+
cssContent,
|
|
39392
|
+
computedStyles,
|
|
39393
|
+
baseUrl,
|
|
39394
|
+
}) {
|
|
39208
39395
|
console.log("[themeFromUrlController] Starting color extraction pipeline");
|
|
39209
39396
|
|
|
39210
39397
|
const metaColors = extractMetaColors(htmlContent);
|
|
@@ -39222,7 +39409,27 @@ function extractColorsFromUrl({ htmlContent, cssContent, computedStyles }) {
|
|
|
39222
39409
|
`[themeFromUrlController] Computed styles: ${computedColors.length} colors`,
|
|
39223
39410
|
);
|
|
39224
39411
|
|
|
39225
|
-
|
|
39412
|
+
// Favicon extraction (async, requires baseUrl)
|
|
39413
|
+
let faviconColors = [];
|
|
39414
|
+
if (baseUrl) {
|
|
39415
|
+
try {
|
|
39416
|
+
faviconColors = await extractFaviconColors(htmlContent, baseUrl);
|
|
39417
|
+
console.log(
|
|
39418
|
+
`[themeFromUrlController] Favicon/logo: ${faviconColors.length} colors`,
|
|
39419
|
+
);
|
|
39420
|
+
} catch (err) {
|
|
39421
|
+
console.warn(
|
|
39422
|
+
`[themeFromUrlController] Favicon extraction failed: ${err.message}`,
|
|
39423
|
+
);
|
|
39424
|
+
}
|
|
39425
|
+
}
|
|
39426
|
+
|
|
39427
|
+
const allColors = [
|
|
39428
|
+
...metaColors,
|
|
39429
|
+
...cssVarColors,
|
|
39430
|
+
...computedColors,
|
|
39431
|
+
...faviconColors,
|
|
39432
|
+
];
|
|
39226
39433
|
console.log(`[themeFromUrlController] Total raw colors: ${allColors.length}`);
|
|
39227
39434
|
|
|
39228
39435
|
const palette = mergeAndRank(allColors);
|
|
@@ -39238,6 +39445,8 @@ function extractColorsFromUrl({ htmlContent, cssContent, computedStyles }) {
|
|
|
39238
39445
|
|
|
39239
39446
|
const themeFromUrlController$1 = {
|
|
39240
39447
|
extractColorsFromUrl,
|
|
39448
|
+
extractFaviconUrls,
|
|
39449
|
+
extractFaviconColors,
|
|
39241
39450
|
};
|
|
39242
39451
|
|
|
39243
39452
|
var themeFromUrlController_1 = themeFromUrlController$1;
|
|
@@ -41465,7 +41674,7 @@ let Receiver$1 = class Receiver extends Writable {
|
|
|
41465
41674
|
var receiver = Receiver$1;
|
|
41466
41675
|
|
|
41467
41676
|
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^Duplex" }] */
|
|
41468
|
-
const { randomFillSync } = require$$1$
|
|
41677
|
+
const { randomFillSync } = require$$1$7;
|
|
41469
41678
|
|
|
41470
41679
|
const PerMessageDeflate$2 = permessageDeflate;
|
|
41471
41680
|
const { EMPTY_BUFFER: EMPTY_BUFFER$1, kWebSocket: kWebSocket$2, NOOP: NOOP$1 } = constants;
|
|
@@ -42560,10 +42769,10 @@ var extension$1 = { format: format$1, parse: parse$2 };
|
|
|
42560
42769
|
|
|
42561
42770
|
const EventEmitter$1 = require$$0$c;
|
|
42562
42771
|
const https = require$$8;
|
|
42563
|
-
const http$1 = require$$
|
|
42564
|
-
const net = require$$3$
|
|
42772
|
+
const http$1 = require$$3$4;
|
|
42773
|
+
const net = require$$3$5;
|
|
42565
42774
|
const tls = require$$4$2;
|
|
42566
|
-
const { randomBytes, createHash: createHash$1 } = require$$1$
|
|
42775
|
+
const { randomBytes, createHash: createHash$1 } = require$$1$7;
|
|
42567
42776
|
const { URL: URL$1 } = require$$4$1;
|
|
42568
42777
|
|
|
42569
42778
|
const PerMessageDeflate$1 = permessageDeflate;
|
|
@@ -44170,8 +44379,8 @@ var subprotocol$1 = { parse };
|
|
|
44170
44379
|
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^Duplex$", "caughtErrors": "none" }] */
|
|
44171
44380
|
|
|
44172
44381
|
const EventEmitter = require$$0$c;
|
|
44173
|
-
const http = require$$
|
|
44174
|
-
const { createHash } = require$$1$
|
|
44382
|
+
const http = require$$3$4;
|
|
44383
|
+
const { createHash } = require$$1$7;
|
|
44175
44384
|
|
|
44176
44385
|
const extension = extension$1;
|
|
44177
44386
|
const PerMessageDeflate = permessageDeflate;
|