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

@mdream/js

Package Overview
Dependencies
Maintainers
1
Versions
19
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@mdream/js - npm Package Compare versions

Comparing version
1.0.7
to
1.1.0
+22
-10
dist/negotiate.d.mts

@@ -0,1 +1,2 @@

type ContentNegotiationResult = 'markdown' | 'html' | 'not-acceptable';
interface AcceptEntry {

@@ -12,14 +13,25 @@ type: string;

/**
* Determine if a client prefers markdown over HTML using proper content negotiation.
* Perform RFC 7231 content negotiation for HTML vs Markdown.
*
* Uses Accept header quality weights and position ordering:
* - If text/markdown or text/plain has higher quality than text/html -> markdown
* - If same quality, earlier position in Accept header wins
* - Bare wildcard does NOT trigger markdown (prevents breaking OG crawlers)
* - sec-fetch-dest: document always returns false (browser navigation)
*
* @param acceptHeader - The HTTP Accept header value
* @param secFetchDest - The Sec-Fetch-Dest header value
* Resolution rules:
* - `Sec-Fetch-Dest: document` always returns `'html'` (browser navigation).
* - Missing or empty Accept header returns `'html'` (server picks default).
* - q=0 entries are treated as explicit rejections and ignored for matching
* (but still count towards "something was listed").
* - `text/markdown` and `text/plain` are the markdown-capable types.
* - `text/html` and `application/xhtml+xml` are the html-capable types.
* - `*_/_*` and `text/*` are wildcards; they satisfy 406 but never on their
* own tip negotiation towards markdown (preserves OG crawler behavior).
* - If nothing in the Accept header can be served (no explicit match, no
* wildcard), returns `'not-acceptable'` so the caller can send 406.
* - Otherwise, compares best markdown entry vs best html-or-wildcard entry
* by q, then by position.
*/
declare function negotiateContent(acceptHeader?: string, secFetchDest?: string): ContentNegotiationResult;
/**
* Determine if a client prefers markdown over HTML. Convenience wrapper over
* {@link negotiateContent}; treats `'not-acceptable'` the same as `'html'`
* (callers that want 406 semantics should use `negotiateContent` directly).
*/
declare function shouldServeMarkdown(acceptHeader?: string, secFetchDest?: string): boolean;
export { parseAcceptHeader, shouldServeMarkdown };
export { ContentNegotiationResult, negotiateContent, parseAcceptHeader, shouldServeMarkdown };

@@ -31,14 +31,21 @@ function parseAcceptHeader(accept) {

}
function shouldServeMarkdown(acceptHeader, secFetchDest) {
if (secFetchDest === "document") return false;
function negotiateContent(acceptHeader, secFetchDest) {
if (secFetchDest === "document") return "html";
const accept = acceptHeader || "";
if (!accept) return false;
const parts = accept.split(",");
if (!accept) return "html";
let bestMdQ = -1;
let bestMdPos = -1;
let htmlQ = -1;
let htmlPos = -1;
let bestHtmlQ = -1;
let bestHtmlPos = -1;
let bestWildcardQ = -1;
let bestWildcardPos = -1;
let sawAnyEntry = false;
let sawAcceptable = false;
let rejectedMd = false;
let rejectedHtml = false;
const parts = accept.split(",");
for (let i = 0; i < parts.length; i++) {
const part = parts[i].trim();
if (!part) continue;
sawAnyEntry = true;
const semicolonIdx = part.indexOf(";");

@@ -51,3 +58,10 @@ let type;

const paramStr = part.slice(semicolonIdx + 1);
const qIdx = paramStr.indexOf("q=");
let qIdx = -1;
for (let j = 0; j < paramStr.length - 1; j++) {
const c = paramStr.charCodeAt(j);
if ((c === 113 || c === 81) && paramStr.charCodeAt(j + 1) === 61) {
qIdx = j;
break;
}
}
if (qIdx !== -1) {

@@ -60,18 +74,51 @@ const qStart = qIdx + 2;

}
if (type === "text/markdown" || type === "text/plain") {
if (q > bestMdQ || q === bestMdQ && (bestMdPos === -1 || i < bestMdPos)) {
const normalized = type.toLowerCase();
if (normalized === "text/markdown" || normalized === "text/plain") {
if (q === 0) {
rejectedMd = true;
continue;
}
sawAcceptable = true;
if (q > bestMdQ || q === bestMdQ && bestMdPos === -1) {
bestMdQ = q;
bestMdPos = i;
}
} else if (type === "text/html") {
htmlQ = q;
htmlPos = i;
} else if (normalized === "text/html" || normalized === "application/xhtml+xml") {
if (q === 0) {
rejectedHtml = true;
continue;
}
sawAcceptable = true;
if (q > bestHtmlQ || q === bestHtmlQ && bestHtmlPos === -1) {
bestHtmlQ = q;
bestHtmlPos = i;
}
} else if (normalized === "*/*" || normalized === "text/*") {
if (q === 0) continue;
sawAcceptable = true;
if (q > bestWildcardQ || q === bestWildcardQ && bestWildcardPos === -1) {
bestWildcardQ = q;
bestWildcardPos = i;
}
}
}
if (bestMdPos === -1) return false;
if (htmlPos === -1) return true;
if (bestMdQ > htmlQ) return true;
if (bestMdQ === htmlQ && bestMdPos < htmlPos) return true;
return false;
if (sawAnyEntry && !sawAcceptable) return "not-acceptable";
if (bestMdPos === -1 && !rejectedMd && bestWildcardPos !== -1) {
bestMdQ = bestWildcardQ;
bestMdPos = bestWildcardPos;
}
if (bestHtmlPos === -1 && !rejectedHtml && bestWildcardPos !== -1) {
bestHtmlQ = bestWildcardQ;
bestHtmlPos = bestWildcardPos;
}
if (bestMdPos === -1 && bestHtmlPos === -1) return "not-acceptable";
if (bestMdPos === -1) return "html";
if (bestHtmlPos === -1) return "markdown";
if (bestMdQ > bestHtmlQ) return "markdown";
if (bestMdQ === bestHtmlQ && bestMdPos < bestHtmlPos) return "markdown";
return "html";
}
export { parseAcceptHeader, shouldServeMarkdown };
function shouldServeMarkdown(acceptHeader, secFetchDest) {
return negotiateContent(acceptHeader, secFetchDest) === "markdown";
}
export { negotiateContent, parseAcceptHeader, shouldServeMarkdown };
{
"name": "@mdream/js",
"type": "module",
"version": "1.0.7",
"version": "1.1.0",
"description": "JavaScript HTML-to-Markdown engine for mdream. Escape hatch for hooks and edge runtimes.",

@@ -6,0 +6,0 @@ "author": {