@stephensauceda/apple-news 1.1.0 → 1.3.0
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/index.js +2 -1
- package/dist/index.js.map +2 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/auth.js", "../src/request.js", "../src/multipart.js", "../src/client.js", "../src/helpers/fetchBundleFiles.js"],
|
|
4
|
-
"sourcesContent": ["import { createHmac } from 'node:crypto'\n\n/**\n * @param {Date} date\n * @returns {string}\n */\nexport function formatAppleDate(date) {\n const iso = date.toISOString()\n return iso.replace(/\\.\\d{3}Z$/, 'Z')\n}\n\n/**\n * @param {string|Uint8Array|Buffer|undefined|null} value\n * @returns {Buffer}\n */\nfunction toBuffer(value) {\n if (value == null) {\n return Buffer.alloc(0)\n }\n\n if (Buffer.isBuffer(value)) {\n return value\n }\n\n if (value instanceof Uint8Array) {\n return Buffer.from(value)\n }\n\n return Buffer.from(String(value), 'utf8')\n}\n\n/**\n * @typedef BuildCanonicalRequestInput\n * @property {string} method\n * @property {string} url\n * @property {string} date\n * @property {string} [contentType]\n * @property {string|Uint8Array|Buffer|undefined|null} [body]\n */\n\n/**\n * Build canonical payload for Apple HHMAC signing.\n *\n * @param {BuildCanonicalRequestInput} input\n * @returns {Buffer}\n */\nexport function buildCanonicalRequest(input) {\n const method = input.method.toUpperCase()\n const contentType = input.contentType ?? ''\n const prefix = `${method}${input.url}${input.date}${contentType}`\n const prefixBuffer = Buffer.from(prefix, 'utf8')\n const bodyBuffer = toBuffer(input.body)\n\n if (bodyBuffer.length === 0) {\n return prefixBuffer\n }\n\n return Buffer.concat([prefixBuffer, bodyBuffer])\n}\n\n/**\n * @param {string} apiSecret Base64 encoded Apple News API secret\n * @param {Buffer} canonicalPayload\n * @returns {string}\n */\nexport function signCanonicalRequest(apiSecret, canonicalPayload) {\n const key = Buffer.from(apiSecret, 'base64')\n\n return createHmac('sha256', key).update(canonicalPayload).digest('base64')\n}\n\n/**\n * @param {{ apiId: string, signature: string, date: string }} input\n * @returns {string}\n */\nexport function buildAuthorizationHeader(input) {\n return `HHMAC; key=\"${input.apiId}\"; signature=\"${input.signature}\"; date=\"${input.date}\"`\n}\n\n/**\n * @typedef CreateSignedHeadersInput\n * @property {string} apiId\n * @property {string} apiSecret\n * @property {string} method\n * @property {string} url\n * @property {string|Date} [date]\n * @property {string} [contentType]\n * @property {string|Uint8Array|Buffer|undefined|null} [body]\n */\n\n/**\n * @param {CreateSignedHeadersInput} input\n * @returns {{ headers: Record<string, string>, date: string, signature: string, canonicalPayload: Buffer }}\n */\nexport function createSignedHeaders(input) {\n const date =\n typeof input.date === 'string'\n ? input.date\n : formatAppleDate(input.date ?? new Date())\n const contentType = input.contentType ?? ''\n\n const canonicalPayload = buildCanonicalRequest({\n method: input.method,\n url: input.url,\n date,\n contentType,\n body: input.body\n })\n\n const signature = signCanonicalRequest(input.apiSecret, canonicalPayload)\n const authorization = buildAuthorizationHeader({\n apiId: input.apiId,\n signature,\n date\n })\n\n const headers = {\n Accept: 'application/json',\n Authorization: authorization\n }\n\n if (contentType) {\n headers['Content-Type'] = contentType\n }\n\n return {\n headers,\n date,\n signature,\n canonicalPayload\n }\n}\n", "import { createSignedHeaders } from './auth.js'\n\n/**\n * @typedef RequestOptions\n * @property {string} apiId\n * @property {string} apiSecret\n * @property {string} method\n * @property {string} endpoint\n * @property {string} [host]\n * @property {Record<string, string | number | boolean | undefined>} [query]\n * @property {string} [contentType]\n * @property {string|Buffer|Uint8Array|null} [body]\n * @property {string|Date} [date]\n */\n\nexport class AppleNewsApiError extends Error {\n /**\n * @param {string} message\n * @param {Object} details\n * @param {number} details.status\n * @param {string} details.method\n * @param {string} details.url\n * @param {unknown[]} [details.apiErrors]\n * @param {unknown} [details.responseBody]\n */\n constructor(message, details) {\n super(message)\n this.name = 'AppleNewsApiError'\n this.status = details.status\n this.method = details.method\n this.url = details.url\n this.apiErrors = details.apiErrors\n this.responseBody = details.responseBody\n }\n}\n\n/**\n * @param {string} host\n * @param {string} endpoint\n * @param {Record<string, string | number | boolean | undefined>} [query]\n * @returns {string}\n */\nexport function buildRequestUrl(host, endpoint, query) {\n const url = new URL(`https://${host}${endpoint}`)\n\n if (query) {\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined) {\n continue\n }\n\n url.searchParams.set(key, String(value))\n }\n }\n\n return url.toString()\n}\n\n/**\n * @param {unknown} value\n * @returns {value is Record<string, unknown>}\n */\nfunction isObject(value) {\n return value !== null && typeof value === 'object'\n}\n\n/**\n * @param {unknown} parsed\n * @returns {unknown[]|undefined}\n */\nfunction getApiErrors(parsed) {\n if (!isObject(parsed)) {\n return undefined\n }\n\n const maybeErrors = parsed.errors\n if (!Array.isArray(maybeErrors)) {\n return undefined\n }\n\n return maybeErrors\n}\n\n/**\n * @param {unknown} parsed\n * @returns {unknown}\n */\nfunction unwrapData(parsed) {\n if (!isObject(parsed)) {\n return parsed\n }\n\n if ('data' in parsed) {\n return parsed.data\n }\n\n return parsed\n}\n\n/**\n * Execute one signed Apple News API request.\n *\n * @param {RequestOptions} options\n * @returns {Promise<unknown>}\n */\nexport async function requestSigned(options) {\n const host = options.host ?? 'news-api.apple.com'\n\n const url = buildRequestUrl(host, options.endpoint, options.query)\n const signed = createSignedHeaders({\n apiId: options.apiId,\n apiSecret: options.apiSecret,\n method: options.method,\n url,\n date: options.date,\n contentType: options.contentType,\n body: options.body\n })\n\n const response = await fetch(url, {\n method: options.method,\n headers: signed.headers,\n body: options.body ?? undefined\n })\n\n const rawBody = await response.text()\n let parsedBody = null\n\n if (rawBody.length > 0) {\n try {\n parsedBody = JSON.parse(rawBody)\n } catch {\n parsedBody = rawBody\n }\n }\n\n if (!response.ok) {\n const apiErrors = getApiErrors(parsedBody)\n throw new AppleNewsApiError(\n `${options.method.toUpperCase()} ${options.endpoint} failed with status ${response.status}`,\n {\n status: response.status,\n method: options.method.toUpperCase(),\n url,\n apiErrors,\n responseBody: parsedBody\n }\n )\n }\n\n if (parsedBody === null) {\n return null\n }\n\n return unwrapData(parsedBody)\n}\n", "const CRLF = '\\r\\n'\n\n/**\n * @typedef BundleFile\n * @property {Buffer|Uint8Array|string} data\n * @property {string} [mimeType]\n */\n\n/**\n * @param {Buffer|Uint8Array|string} data\n * @returns {Buffer}\n */\nfunction toBuffer(data) {\n if (Buffer.isBuffer(data)) {\n return data\n }\n\n if (data instanceof Uint8Array) {\n return Buffer.from(data)\n }\n\n return Buffer.from(String(data), 'utf8')\n}\n\n/**\n * @param {string} boundary\n * @param {string} name\n * @param {Buffer} value\n * @param {string} contentType\n * @param {string} [filename]\n * @returns {Buffer}\n */\nfunction buildPart(boundary, name, value, contentType, filename) {\n const lines = [`--${boundary}`]\n\n if (filename) {\n lines.push(\n `Content-Disposition: form-data; name=\"${name}\"; filename=\"${filename}\"`\n )\n } else {\n lines.push(`Content-Disposition: form-data; name=\"${name}\"`)\n }\n\n lines.push(`Content-Type: ${contentType}`)\n lines.push('')\n\n return Buffer.concat([\n Buffer.from(lines.join(CRLF) + CRLF, 'utf8'),\n value,\n Buffer.from(CRLF, 'utf8')\n ])\n}\n\n/**\n * @param {Record<string, BundleFile>|undefined} bundleFiles\n */\nfunction validateBundleFiles(bundleFiles) {\n if (!bundleFiles) {\n return\n }\n\n for (const name of Object.keys(bundleFiles)) {\n if (name === 'article.json' || name === 'metadata') {\n throw new TypeError(\n 'bundleFiles cannot contain reserved names: article.json or metadata'\n )\n }\n }\n}\n\n/**\n * @param {Object} input\n * @param {Record<string, unknown>} input.article\n * @param {Record<string, unknown>} [input.metadata]\n * @param {Record<string, BundleFile>} [input.bundleFiles]\n * @param {string} [input.boundary]\n * @returns {{ contentType: string, body: Buffer }}\n */\nexport function buildArticleMultipartBody(input) {\n if (!input.article || typeof input.article !== 'object') {\n throw new TypeError('article is required and must be an object')\n }\n\n validateBundleFiles(input.bundleFiles)\n\n const boundary = input.boundary ?? `apple-news-${Date.now().toString(16)}`\n const metadata = input.metadata ?? {}\n\n const parts = []\n parts.push(\n buildPart(\n boundary,\n 'article.json',\n Buffer.from(JSON.stringify(input.article), 'utf8'),\n 'application/json'\n )\n )\n\n parts.push(\n buildPart(\n boundary,\n 'metadata',\n Buffer.from(JSON.stringify({ data: metadata }), 'utf8'),\n 'application/json'\n )\n )\n\n if (input.bundleFiles) {\n let index = 0\n for (const [filename, file] of Object.entries(input.bundleFiles)) {\n const fieldName = `file${index}`\n index += 1\n parts.push(\n buildPart(\n boundary,\n fieldName,\n toBuffer(file.data),\n file.mimeType ?? 'application/octet-stream',\n filename\n )\n )\n }\n }\n\n parts.push(Buffer.from(`--${boundary}--${CRLF}`, 'utf8'))\n\n return {\n contentType: `multipart/form-data; boundary=${boundary}`,\n body: Buffer.concat(parts)\n }\n}\n", "import { requestSigned } from './request.js'\nimport { buildArticleMultipartBody } from './multipart.js'\n\n/**\n * @typedef AppleNewsClientConfig\n * @property {string} apiId\n * @property {string} apiSecret\n * @property {string} [host]\n */\n\nfunction assertRequired(value, name) {\n if (!value || typeof value !== 'string') {\n throw new TypeError(`${name} is required and must be a non-empty string`)\n }\n}\n\nfunction assertId(value, name) {\n if (!value || typeof value !== 'string') {\n throw new TypeError(`${name} is required and must be a non-empty string`)\n }\n}\n\n/**\n * @typedef {Object} ArticleBundleFile\n * @property {Buffer|Uint8Array|string} data\n * @property {string} [mimeType]\n */\n\n/**\n * @param {Object} options\n * @param {boolean} [options.isPreview=true]\n * @param {boolean} [options.isSponsored=false]\n * @param {boolean} [options.isPaid=false]\n * @param {string[]} [options.sections]\n * @param {'KIDS'|'MATURE'|'GENERAL'} [options.maturityRating]\n * @param {string} [options.revision]\n * @param {string} host\n * @param {boolean} includeRevision\n */\nfunction buildMetadata(options, host, includeRevision = false) {\n const metadata = {\n isPreview: options.isPreview ?? true,\n isSponsored: options.isSponsored ?? false,\n isPaid: options.isPaid ?? false\n }\n\n if (options.sections !== undefined) {\n metadata.links = {\n sections: options.sections.map(\n (sectionId) =>\n `https://${host}/sections/${encodeURIComponent(sectionId)}`\n )\n }\n }\n\n if (options.maturityRating !== undefined) {\n metadata.maturityRating = options.maturityRating\n }\n\n if (includeRevision) {\n metadata.revision = options.revision\n }\n\n return metadata\n}\n\n/**\n * @typedef {Object} SharedOptions\n * @property {string|Date} [date]\n */\n\n/**\n * @param {Record<string, unknown>|undefined} options\n * @returns {SharedOptions}\n */\nfunction getSharedOptions(options) {\n return {\n date: options?.date\n }\n}\n\nexport class AppleNewsClient {\n /**\n * @param {AppleNewsClientConfig} config\n */\n constructor(config) {\n assertRequired(config?.apiId, 'apiId')\n assertRequired(config?.apiSecret, 'apiSecret')\n\n this.apiId = config.apiId\n this.apiSecret = config.apiSecret\n this.host = config.host ?? 'news-api.apple.com'\n }\n\n /**\n * @param {Object} options\n * @param {string} options.channelId\n * @param {string|Date} [options.date]\n */\n async readChannel(options) {\n assertId(options?.channelId, 'channelId')\n return this.#request(\n 'GET',\n `/channels/${options.channelId}`,\n getSharedOptions(options)\n )\n }\n\n /**\n * @param {Object} options\n * @param {string} options.channelId\n * @param {string|Date} [options.date]\n */\n async listSections(options) {\n assertId(options?.channelId, 'channelId')\n return this.#request(\n 'GET',\n `/channels/${options.channelId}/sections`,\n getSharedOptions(options)\n )\n }\n\n /**\n * @param {Object} options\n * @param {string} options.sectionId\n * @param {string|Date} [options.date]\n */\n async readSection(options) {\n assertId(options?.sectionId, 'sectionId')\n return this.#request(\n 'GET',\n `/sections/${options.sectionId}`,\n getSharedOptions(options)\n )\n }\n\n /**\n * @param {Object} options\n * @param {string} options.articleId\n * @param {string|Date} [options.date]\n */\n async readArticle(options) {\n assertId(options?.articleId, 'articleId')\n return this.#request(\n 'GET',\n `/articles/${options.articleId}`,\n getSharedOptions(options)\n )\n }\n\n /**\n * @param {Object} options\n * @param {string} options.articleId\n * @param {string|Date} [options.date]\n */\n async deleteArticle(options) {\n assertId(options?.articleId, 'articleId')\n return this.#request(\n 'DELETE',\n `/articles/${options.articleId}`,\n getSharedOptions(options)\n )\n }\n\n /**\n * @param {Record<string, unknown>} options\n * @param {string} [options.channelId]\n * @param {string} [options.sectionId]\n * @param {string|Date} [options.date]\n */\n async searchArticles(options) {\n const hasChannelId =\n typeof options?.channelId === 'string' && options.channelId.length > 0\n const hasSectionId =\n typeof options?.sectionId === 'string' && options.sectionId.length > 0\n\n if (!hasChannelId && !hasSectionId) {\n throw new TypeError(\n 'searchArticles requires either channelId or sectionId'\n )\n }\n\n if (hasChannelId && hasSectionId) {\n throw new TypeError(\n 'searchArticles accepts either channelId or sectionId, not both'\n )\n }\n\n const endpoint = hasChannelId\n ? `/channels/${options.channelId}/articles`\n : `/sections/${options.sectionId}/articles`\n\n const query = { ...options }\n const date = query.date\n delete query.channelId\n delete query.sectionId\n delete query.date\n\n return this.#request('GET', endpoint, {\n date,\n query\n })\n }\n\n /**\n * @param {Object} options\n * @param {string} options.channelId\n * @param {Record<string, unknown>} options.article\n * @param {Record<string, ArticleBundleFile>} [options.bundleFiles]\n * @param {boolean} [options.isPreview=true]\n * @param {boolean} [options.isSponsored=false]\n * @param {boolean} [options.isPaid=false]\n * @param {string[]} [options.sections]\n * @param {'KIDS'|'MATURE'|'GENERAL'} [options.maturityRating]\n * @param {string|Date} [options.date]\n */\n async createArticle(options) {\n assertId(options?.channelId, 'channelId')\n\n const multipart = buildArticleMultipartBody({\n article: options.article,\n metadata: buildMetadata(options, this.host),\n bundleFiles: options.bundleFiles\n })\n\n return this.#request('POST', `/channels/${options.channelId}/articles`, {\n date: options.date,\n contentType: multipart.contentType,\n body: multipart.body\n })\n }\n\n /**\n * @param {Object} options\n * @param {string} options.articleId\n * @param {string} options.revision\n * @param {Record<string, unknown>} options.article\n * @param {Record<string, ArticleBundleFile>} [options.bundleFiles]\n * @param {boolean} [options.isPreview=true]\n * @param {boolean} [options.isSponsored=false]\n * @param {boolean} [options.isPaid=false]\n * @param {string[]} [options.sections]\n * @param {'KIDS'|'MATURE'|'GENERAL'} [options.maturityRating]\n * @param {string|Date} [options.date]\n */\n async updateArticle(options) {\n assertId(options?.articleId, 'articleId')\n assertId(options?.revision, 'revision')\n\n const multipart = buildArticleMultipartBody({\n article: options.article,\n metadata: buildMetadata(options, this.host, true),\n bundleFiles: options.bundleFiles\n })\n\n return this.#request('POST', `/articles/${options.articleId}`, {\n date: options.date,\n contentType: multipart.contentType,\n body: multipart.body\n })\n }\n\n /**\n * @param {Object} options\n * @param {string} options.channelId\n * @param {string|Date} [options.date]\n */\n async readChannelQuota(options) {\n assertId(options?.channelId, 'channelId')\n return this.#request(\n 'GET',\n `/channels/${options.channelId}/quota`,\n getSharedOptions(options)\n )\n }\n\n /**\n * @param {Object} options\n * @param {string} options.sectionId\n * @param {string[]} options.articleIds\n * @param {string|Date} [options.date]\n */\n async promoteArticles(options) {\n assertId(options?.sectionId, 'sectionId')\n\n if (!Array.isArray(options?.articleIds)) {\n throw new TypeError('articleIds is required and must be an array')\n }\n\n const body = JSON.stringify({\n data: { promotedArticles: options.articleIds }\n })\n\n return this.#request(\n 'POST',\n `/sections/${options.sectionId}/promotedArticles`,\n {\n date: options.date,\n contentType: 'application/json',\n body\n }\n )\n }\n\n /**\n * @param {string} method\n * @param {string} endpoint\n * @param {Object} [options={}]\n * @param {string|Date} [options.date]\n * @param {Record<string, string|number|boolean|undefined>} [options.query]\n * @param {string} [options.contentType]\n * @param {string|Buffer|Uint8Array|null} [options.body]\n */\n async #request(method, endpoint, options = {}) {\n return requestSigned({\n apiId: this.apiId,\n apiSecret: this.apiSecret,\n host: this.host,\n method,\n endpoint,\n date: options.date,\n query: options.query,\n contentType: options.contentType,\n body: options.body\n })\n }\n}\n", "/**\n * @typedef BundleFetchResult\n * @property {Buffer} data\n * @property {string} mimeType\n */\n\n/**\n * @param {Record<string, string>} urlMap\n * @returns {Promise<Record<string, BundleFetchResult>>}\n */\nexport async function fetchBundleFiles(urlMap) {\n const result = {}\n\n for (const [filename, url] of Object.entries(urlMap)) {\n const response = await fetch(url)\n\n if (!response.ok) {\n throw new Error(\n `Failed to fetch bundle file ${filename} from ${url}: ${response.status}`\n )\n }\n\n const arrayBuffer = await response.arrayBuffer()\n const data = Buffer.from(arrayBuffer)\n const mimeType =\n response.headers.get('content-type') ?? 'application/octet-stream'\n\n result[filename] = {\n data,\n mimeType\n }\n }\n\n return result\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import { createHmac } from 'node:crypto'\n\n/**\n * @param {Date} date\n * @returns {string}\n */\nexport function formatAppleDate(date) {\n const iso = date.toISOString()\n return iso.replace(/\\.\\d{3}Z$/, 'Z')\n}\n\n/**\n * @param {string|Uint8Array|Buffer|undefined|null} value\n * @returns {Buffer}\n */\nfunction toBuffer(value) {\n if (value == null) {\n return Buffer.alloc(0)\n }\n\n if (Buffer.isBuffer(value)) {\n return value\n }\n\n if (value instanceof Uint8Array) {\n return Buffer.from(value)\n }\n\n return Buffer.from(String(value), 'utf8')\n}\n\n/**\n * @typedef BuildCanonicalRequestInput\n * @property {string} method\n * @property {string} url\n * @property {string} date\n * @property {string} [contentType]\n * @property {string|Uint8Array|Buffer|undefined|null} [body]\n */\n\n/**\n * Build canonical payload for Apple HHMAC signing.\n *\n * @param {BuildCanonicalRequestInput} input\n * @returns {Buffer}\n */\nexport function buildCanonicalRequest(input) {\n const method = input.method.toUpperCase()\n const contentType = input.contentType ?? ''\n const prefix = `${method}${input.url}${input.date}${contentType}`\n const prefixBuffer = Buffer.from(prefix, 'utf8')\n const bodyBuffer = toBuffer(input.body)\n\n if (bodyBuffer.length === 0) {\n return prefixBuffer\n }\n\n return Buffer.concat([prefixBuffer, bodyBuffer])\n}\n\n/**\n * @param {string} apiSecret Base64 encoded Apple News API secret\n * @param {Buffer} canonicalPayload\n * @returns {string}\n */\nexport function signCanonicalRequest(apiSecret, canonicalPayload) {\n const key = Buffer.from(apiSecret, 'base64')\n\n return createHmac('sha256', key).update(canonicalPayload).digest('base64')\n}\n\n/**\n * @param {{ apiId: string, signature: string, date: string }} input\n * @returns {string}\n */\nexport function buildAuthorizationHeader(input) {\n return `HHMAC; key=\"${input.apiId}\"; signature=\"${input.signature}\"; date=\"${input.date}\"`\n}\n\n/**\n * @typedef CreateSignedHeadersInput\n * @property {string} apiId\n * @property {string} apiSecret\n * @property {string} method\n * @property {string} url\n * @property {string|Date} [date]\n * @property {string} [contentType]\n * @property {string|Uint8Array|Buffer|undefined|null} [body]\n */\n\n/**\n * @param {CreateSignedHeadersInput} input\n * @returns {{ headers: Record<string, string>, date: string, signature: string, canonicalPayload: Buffer }}\n */\nexport function createSignedHeaders(input) {\n const date =\n typeof input.date === 'string'\n ? input.date\n : formatAppleDate(input.date ?? new Date())\n const contentType = input.contentType ?? ''\n\n const canonicalPayload = buildCanonicalRequest({\n method: input.method,\n url: input.url,\n date,\n contentType,\n body: input.body\n })\n\n const signature = signCanonicalRequest(input.apiSecret, canonicalPayload)\n const authorization = buildAuthorizationHeader({\n apiId: input.apiId,\n signature,\n date\n })\n\n const headers = {\n Accept: 'application/json',\n Authorization: authorization\n }\n\n if (contentType) {\n headers['Content-Type'] = contentType\n }\n\n return {\n headers,\n date,\n signature,\n canonicalPayload\n }\n}\n", "import { createSignedHeaders } from './auth.js'\n\n/**\n * @typedef RequestOptions\n * @property {string} apiId\n * @property {string} apiSecret\n * @property {string} method\n * @property {string} endpoint\n * @property {string} [host]\n * @property {Record<string, string | number | boolean | undefined>} [query]\n * @property {string} [contentType]\n * @property {string|Buffer|Uint8Array|null} [body]\n * @property {string|Date} [date]\n */\n\nexport class AppleNewsApiError extends Error {\n /**\n * @param {string} message\n * @param {Object} details\n * @param {number} details.status\n * @param {string} details.method\n * @param {string} details.url\n * @param {unknown[]} [details.apiErrors]\n * @param {unknown} [details.responseBody]\n */\n constructor(message, details) {\n super(message)\n this.name = 'AppleNewsApiError'\n this.status = details.status\n this.method = details.method\n this.url = details.url\n this.apiErrors = details.apiErrors\n this.responseBody = details.responseBody\n }\n}\n\n/**\n * @param {string} host\n * @param {string} endpoint\n * @param {Record<string, string | number | boolean | undefined>} [query]\n * @returns {string}\n */\nexport function buildRequestUrl(host, endpoint, query) {\n const url = new URL(`https://${host}${endpoint}`)\n\n if (query) {\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined) {\n continue\n }\n\n url.searchParams.set(key, String(value))\n }\n }\n\n return url.toString()\n}\n\n/**\n * @param {unknown} value\n * @returns {value is Record<string, unknown>}\n */\nfunction isObject(value) {\n return value !== null && typeof value === 'object'\n}\n\n/**\n * @param {unknown} parsed\n * @returns {unknown[]|undefined}\n */\nfunction getApiErrors(parsed) {\n if (!isObject(parsed)) {\n return undefined\n }\n\n const maybeErrors = parsed.errors\n if (!Array.isArray(maybeErrors)) {\n return undefined\n }\n\n return maybeErrors\n}\n\n/**\n * @param {unknown} parsed\n * @returns {unknown}\n */\nfunction unwrapData(parsed) {\n if (!isObject(parsed)) {\n return parsed\n }\n\n if ('data' in parsed) {\n return parsed.data\n }\n\n return parsed\n}\n\n/**\n * Execute one signed Apple News API request.\n *\n * @param {RequestOptions} options\n * @returns {Promise<unknown>}\n */\nexport async function requestSigned(options) {\n const host = options.host ?? 'news-api.apple.com'\n\n const url = buildRequestUrl(host, options.endpoint, options.query)\n const signed = createSignedHeaders({\n apiId: options.apiId,\n apiSecret: options.apiSecret,\n method: options.method,\n url,\n date: options.date,\n contentType: options.contentType,\n body: options.body\n })\n\n const response = await fetch(url, {\n method: options.method,\n headers: signed.headers,\n body: options.body ?? undefined\n })\n\n const rawBody = await response.text()\n let parsedBody = null\n\n if (rawBody.length > 0) {\n try {\n parsedBody = JSON.parse(rawBody)\n } catch {\n parsedBody = rawBody\n }\n }\n\n if (!response.ok) {\n const apiErrors = getApiErrors(parsedBody)\n throw new AppleNewsApiError(\n `${options.method.toUpperCase()} ${options.endpoint} failed with status ${response.status}`,\n {\n status: response.status,\n method: options.method.toUpperCase(),\n url,\n apiErrors,\n responseBody: parsedBody\n }\n )\n }\n\n if (parsedBody === null) {\n return null\n }\n\n return unwrapData(parsedBody)\n}\n", "const CRLF = '\\r\\n'\n\n/**\n * @typedef BundleFile\n * @property {Buffer|Uint8Array|string} data\n * @property {string} [mimeType]\n */\n\n/**\n * @param {Buffer|Uint8Array|string} data\n * @returns {Buffer}\n */\nfunction toBuffer(data) {\n if (Buffer.isBuffer(data)) {\n return data\n }\n\n if (data instanceof Uint8Array) {\n return Buffer.from(data)\n }\n\n return Buffer.from(String(data), 'utf8')\n}\n\n/**\n * @param {string} boundary\n * @param {string} name\n * @param {Buffer} value\n * @param {string} contentType\n * @param {string} [filename]\n * @returns {Buffer}\n */\nfunction buildPart(boundary, name, value, contentType, filename) {\n const lines = [`--${boundary}`]\n\n if (filename) {\n lines.push(\n `Content-Disposition: form-data; name=\"${name}\"; filename=\"${filename}\"`\n )\n } else {\n lines.push(`Content-Disposition: form-data; name=\"${name}\"`)\n }\n\n lines.push(`Content-Type: ${contentType}`)\n lines.push('')\n\n return Buffer.concat([\n Buffer.from(lines.join(CRLF) + CRLF, 'utf8'),\n value,\n Buffer.from(CRLF, 'utf8')\n ])\n}\n\n/**\n * @param {Record<string, BundleFile>|undefined} bundleFiles\n */\nfunction validateBundleFiles(bundleFiles) {\n if (!bundleFiles) {\n return\n }\n\n for (const name of Object.keys(bundleFiles)) {\n if (name === 'article.json' || name === 'metadata') {\n throw new TypeError(\n 'bundleFiles cannot contain reserved names: article.json or metadata'\n )\n }\n }\n}\n\n/**\n * @param {Object} input\n * @param {Record<string, unknown>} input.article\n * @param {Record<string, unknown>} [input.metadata]\n * @param {Record<string, BundleFile>} [input.bundleFiles]\n * @param {string} [input.boundary]\n * @returns {{ contentType: string, body: Buffer }}\n */\nexport function buildArticleMultipartBody(input) {\n if (!input.article || typeof input.article !== 'object') {\n throw new TypeError('article is required and must be an object')\n }\n\n validateBundleFiles(input.bundleFiles)\n\n const boundary = input.boundary ?? `apple-news-${Date.now().toString(16)}`\n const metadata = input.metadata ?? {}\n\n const parts = []\n parts.push(\n buildPart(\n boundary,\n 'article.json',\n Buffer.from(JSON.stringify(input.article), 'utf8'),\n 'application/json',\n 'article.json'\n )\n )\n\n parts.push(\n buildPart(\n boundary,\n 'metadata',\n Buffer.from(JSON.stringify({ data: metadata }), 'utf8'),\n 'application/json'\n )\n )\n\n if (input.bundleFiles) {\n let index = 0\n for (const [filename, file] of Object.entries(input.bundleFiles)) {\n const fieldName = `file${index}`\n index += 1\n parts.push(\n buildPart(\n boundary,\n fieldName,\n toBuffer(file.data),\n file.mimeType ?? 'application/octet-stream',\n filename\n )\n )\n }\n }\n\n parts.push(Buffer.from(`--${boundary}--${CRLF}`, 'utf8'))\n\n return {\n contentType: `multipart/form-data; boundary=${boundary}`,\n body: Buffer.concat(parts)\n }\n}\n", "import { requestSigned } from './request.js'\nimport { buildArticleMultipartBody } from './multipart.js'\n\n/**\n * @typedef AppleNewsClientConfig\n * @property {string} apiId\n * @property {string} apiSecret\n * @property {string} [host]\n */\n\nfunction assertRequired(value, name) {\n if (!value || typeof value !== 'string') {\n throw new TypeError(`${name} is required and must be a non-empty string`)\n }\n}\n\nfunction assertId(value, name) {\n if (!value || typeof value !== 'string') {\n throw new TypeError(`${name} is required and must be a non-empty string`)\n }\n}\n\n/**\n * @typedef {Object} ArticleBundleFile\n * @property {Buffer|Uint8Array|string} data\n * @property {string} [mimeType]\n */\n\n/**\n * @param {Object} options\n * @param {boolean} [options.isPreview=true]\n * @param {boolean} [options.isSponsored=false]\n * @param {boolean} [options.isPaid=false]\n * @param {string[]} [options.sections]\n * @param {'KIDS'|'MATURE'|'GENERAL'} [options.maturityRating]\n * @param {string} [options.revision]\n * @param {string} host\n * @param {boolean} includeRevision\n */\nfunction buildMetadata(options, host, includeRevision = false) {\n const metadata = {\n isPreview: options.isPreview ?? true,\n isSponsored: options.isSponsored ?? false,\n isPaid: options.isPaid ?? false\n }\n\n if (options.sections !== undefined) {\n metadata.links = {\n sections: options.sections.map(\n (sectionId) =>\n `https://${host}/sections/${encodeURIComponent(sectionId)}`\n )\n }\n }\n\n if (options.maturityRating !== undefined) {\n metadata.maturityRating = options.maturityRating\n }\n\n if (includeRevision) {\n metadata.revision = options.revision\n }\n\n return metadata\n}\n\n/**\n * @typedef {Object} SharedOptions\n * @property {string|Date} [date]\n */\n\n/**\n * @param {Record<string, unknown>|undefined} options\n * @returns {SharedOptions}\n */\nfunction getSharedOptions(options) {\n return {\n date: options?.date\n }\n}\n\nexport class AppleNewsClient {\n /**\n * @param {AppleNewsClientConfig} config\n */\n constructor(config) {\n assertRequired(config?.apiId, 'apiId')\n assertRequired(config?.apiSecret, 'apiSecret')\n\n this.apiId = config.apiId\n this.apiSecret = config.apiSecret\n this.host = config.host ?? 'news-api.apple.com'\n }\n\n /**\n * @param {Object} options\n * @param {string} options.channelId\n * @param {string|Date} [options.date]\n */\n async readChannel(options) {\n assertId(options?.channelId, 'channelId')\n return this.#request(\n 'GET',\n `/channels/${options.channelId}`,\n getSharedOptions(options)\n )\n }\n\n /**\n * @param {Object} options\n * @param {string} options.channelId\n * @param {string|Date} [options.date]\n */\n async listSections(options) {\n assertId(options?.channelId, 'channelId')\n return this.#request(\n 'GET',\n `/channels/${options.channelId}/sections`,\n getSharedOptions(options)\n )\n }\n\n /**\n * @param {Object} options\n * @param {string} options.sectionId\n * @param {string|Date} [options.date]\n */\n async readSection(options) {\n assertId(options?.sectionId, 'sectionId')\n return this.#request(\n 'GET',\n `/sections/${options.sectionId}`,\n getSharedOptions(options)\n )\n }\n\n /**\n * @param {Object} options\n * @param {string} options.articleId\n * @param {string|Date} [options.date]\n */\n async readArticle(options) {\n assertId(options?.articleId, 'articleId')\n return this.#request(\n 'GET',\n `/articles/${options.articleId}`,\n getSharedOptions(options)\n )\n }\n\n /**\n * @param {Object} options\n * @param {string} options.articleId\n * @param {string|Date} [options.date]\n */\n async deleteArticle(options) {\n assertId(options?.articleId, 'articleId')\n return this.#request(\n 'DELETE',\n `/articles/${options.articleId}`,\n getSharedOptions(options)\n )\n }\n\n /**\n * @param {Record<string, unknown>} options\n * @param {string} [options.channelId]\n * @param {string} [options.sectionId]\n * @param {string|Date} [options.date]\n */\n async searchArticles(options) {\n const hasChannelId =\n typeof options?.channelId === 'string' && options.channelId.length > 0\n const hasSectionId =\n typeof options?.sectionId === 'string' && options.sectionId.length > 0\n\n if (!hasChannelId && !hasSectionId) {\n throw new TypeError(\n 'searchArticles requires either channelId or sectionId'\n )\n }\n\n if (hasChannelId && hasSectionId) {\n throw new TypeError(\n 'searchArticles accepts either channelId or sectionId, not both'\n )\n }\n\n const endpoint = hasChannelId\n ? `/channels/${options.channelId}/articles`\n : `/sections/${options.sectionId}/articles`\n\n const query = { ...options }\n const date = query.date\n delete query.channelId\n delete query.sectionId\n delete query.date\n\n return this.#request('GET', endpoint, {\n date,\n query\n })\n }\n\n /**\n * @param {Object} options\n * @param {string} options.channelId\n * @param {Record<string, unknown>} options.article\n * @param {Record<string, ArticleBundleFile>} [options.bundleFiles]\n * @param {boolean} [options.isPreview=true]\n * @param {boolean} [options.isSponsored=false]\n * @param {boolean} [options.isPaid=false]\n * @param {string[]} [options.sections]\n * @param {'KIDS'|'MATURE'|'GENERAL'} [options.maturityRating]\n * @param {string|Date} [options.date]\n */\n async createArticle(options) {\n assertId(options?.channelId, 'channelId')\n\n const multipart = buildArticleMultipartBody({\n article: options.article,\n metadata: buildMetadata(options, this.host),\n bundleFiles: options.bundleFiles\n })\n\n return this.#request('POST', `/channels/${options.channelId}/articles`, {\n date: options.date,\n contentType: multipart.contentType,\n body: multipart.body\n })\n }\n\n /**\n * @param {Object} options\n * @param {string} options.articleId\n * @param {string} options.revision\n * @param {Record<string, unknown>} options.article\n * @param {Record<string, ArticleBundleFile>} [options.bundleFiles]\n * @param {boolean} [options.isPreview=true]\n * @param {boolean} [options.isSponsored=false]\n * @param {boolean} [options.isPaid=false]\n * @param {string[]} [options.sections]\n * @param {'KIDS'|'MATURE'|'GENERAL'} [options.maturityRating]\n * @param {string|Date} [options.date]\n */\n async updateArticle(options) {\n assertId(options?.articleId, 'articleId')\n assertId(options?.revision, 'revision')\n\n const multipart = buildArticleMultipartBody({\n article: options.article,\n metadata: buildMetadata(options, this.host, true),\n bundleFiles: options.bundleFiles\n })\n\n return this.#request('POST', `/articles/${options.articleId}`, {\n date: options.date,\n contentType: multipart.contentType,\n body: multipart.body\n })\n }\n\n /**\n * @param {Object} options\n * @param {string} options.channelId\n * @param {string|Date} [options.date]\n */\n async readChannelQuota(options) {\n assertId(options?.channelId, 'channelId')\n return this.#request(\n 'GET',\n `/channels/${options.channelId}/quota`,\n getSharedOptions(options)\n )\n }\n\n /**\n * @param {Object} options\n * @param {string} options.sectionId\n * @param {string[]} options.articleIds\n * @param {string|Date} [options.date]\n */\n async promoteArticles(options) {\n assertId(options?.sectionId, 'sectionId')\n\n if (!Array.isArray(options?.articleIds)) {\n throw new TypeError('articleIds is required and must be an array')\n }\n\n const body = JSON.stringify({\n data: { promotedArticles: options.articleIds }\n })\n\n return this.#request(\n 'POST',\n `/sections/${options.sectionId}/promotedArticles`,\n {\n date: options.date,\n contentType: 'application/json',\n body\n }\n )\n }\n\n /**\n * @param {string} method\n * @param {string} endpoint\n * @param {Object} [options={}]\n * @param {string|Date} [options.date]\n * @param {Record<string, string|number|boolean|undefined>} [options.query]\n * @param {string} [options.contentType]\n * @param {string|Buffer|Uint8Array|null} [options.body]\n */\n async #request(method, endpoint, options = {}) {\n return requestSigned({\n apiId: this.apiId,\n apiSecret: this.apiSecret,\n host: this.host,\n method,\n endpoint,\n date: options.date,\n query: options.query,\n contentType: options.contentType,\n body: options.body\n })\n }\n}\n", "/**\n * @typedef BundleFetchResult\n * @property {Buffer} data\n * @property {string} mimeType\n */\n\n/**\n * @param {Record<string, string>} urlMap\n * @returns {Promise<Record<string, BundleFetchResult>>}\n */\nexport async function fetchBundleFiles(urlMap) {\n const result = {}\n\n for (const [filename, url] of Object.entries(urlMap)) {\n const response = await fetch(url)\n\n if (!response.ok) {\n throw new Error(\n `Failed to fetch bundle file ${filename} from ${url}: ${response.status}`\n )\n }\n\n const arrayBuffer = await response.arrayBuffer()\n const data = Buffer.from(arrayBuffer)\n const mimeType =\n response.headers.get('content-type') ?? 'application/octet-stream'\n\n result[filename] = {\n data,\n mimeType\n }\n }\n\n return result\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["toBuffer"]
|
|
7
7
|
}
|