hal-browser
Advanced tools
Comparing version 0.8.6 to 0.9.0
Changelog | ||
========= | ||
0.9.0 (2020-08-16) (alpha tag) | ||
------------------------------ | ||
* Using Ketting for all format parsing, which means it will be easier in the | ||
future to add support for Siren, Collection+JSON, JSON:API and other formats. | ||
* Switch to ESlint. | ||
0.8.6 (2020-01-05) | ||
@@ -5,0 +13,0 @@ ------------------ |
@@ -1,2 +0,3 @@ | ||
import { Link, SureOptions } from '../types'; | ||
import { Link } from 'ketting'; | ||
import { SureOptions } from '../types'; | ||
export default function renderAlternate(links: Link[], options: SureOptions): string; |
@@ -46,5 +46,4 @@ "use strict"; | ||
// If the url is relative, we're adding our secret argument to make the Accept header work. | ||
if (href.match(/^\/[^\/]/) !== null) { | ||
if (href.match(/^\/[^/]/) !== null && link.type) { | ||
const urlObj = url_1.default.parse(href, true); | ||
// @ts-ignore. TS hates this line. | ||
urlObj.query['_browser-accept'] = link.type; | ||
@@ -51,0 +50,0 @@ delete urlObj.search; |
@@ -1,2 +0,2 @@ | ||
import { Context } from '@curveball/core'; | ||
export default function csvBody(ctx: Context, body: any): Promise<string>; | ||
import { State } from 'ketting'; | ||
export default function csvBody(state: State): Promise<string>; |
@@ -10,8 +10,8 @@ "use strict"; | ||
const parse = util_1.promisify(csv_parse_1.default); | ||
async function csvBody(ctx, body) { | ||
async function csvBody(state) { | ||
let html = ` <h2>Contents</h2> | ||
<table class="body-csv"> | ||
`; | ||
// @ts-ignore | ||
const data = await parse(body); | ||
// @ts-expect-error csv-parse types are broken AF. | ||
const data = await parse(state.data); | ||
let first = true; | ||
@@ -18,0 +18,0 @@ for (const row of data) { |
@@ -0,3 +1,4 @@ | ||
import { SureOptions } from '../types'; | ||
import { State } from 'ketting'; | ||
import { Context } from '@curveball/core'; | ||
import { SureOptions } from '../types'; | ||
export default function embedded(ctx: Context, body: any, options: SureOptions): Promise<string>; | ||
export default function embedded(ctx: Context, state: State, options: SureOptions): Promise<string>; |
@@ -8,16 +8,6 @@ "use strict"; | ||
const resource_1 = __importDefault(require("./resource")); | ||
async function embedded(ctx, body, options) { | ||
if (!body || !body._embedded) { | ||
return ''; | ||
} | ||
async function embedded(ctx, state, options) { | ||
let html = '<h2>Embedded</h2>'; | ||
for (const [rel, linkOrList] of Object.entries(body._embedded)) { | ||
if (Array.isArray(linkOrList)) { | ||
for (const link of linkOrList) { | ||
html += await renderEmbedded(ctx, rel, link, options); | ||
} | ||
} | ||
else { | ||
html += await renderEmbedded(ctx, rel, linkOrList, options); | ||
} | ||
for (const embeddedState of state.getEmbedded()) { | ||
html += await renderEmbedded(ctx, embeddedState, options); | ||
} | ||
@@ -27,9 +17,8 @@ return html; | ||
exports.default = embedded; | ||
async function renderEmbedded(ctx, rel, body, options) { | ||
const selfLink = body._links.self.href; | ||
const summary = rel + ': ' + selfLink; | ||
async function renderEmbedded(ctx, state, options) { | ||
const selfLink = state.links.get('self'); | ||
return ` | ||
<details> | ||
<summary>${util_1.h(summary)}</summary> | ||
${await resource_1.default(ctx, body, util_1.getHalLinks(body), options)} | ||
<summary>${util_1.h(selfLink.href)}</summary> | ||
${await resource_1.default(ctx, state, options)} | ||
</details> | ||
@@ -36,0 +25,0 @@ `; |
import { Context } from '@curveball/core'; | ||
import { Link, SureOptions } from '../types'; | ||
export default function forms(ctx: Context, links: Link[], options: SureOptions): string; | ||
import { SureOptions } from '../types'; | ||
import { State } from 'ketting'; | ||
export default function forms(ctx: Context, state: State, options: SureOptions): string; |
@@ -8,5 +8,5 @@ "use strict"; | ||
const templated_links_1 = __importDefault(require("./forms/templated-links")); | ||
function forms(ctx, links, options) { | ||
function forms(ctx, state, options) { | ||
let formHtml = ''; | ||
formHtml += templated_links_1.default(links, options); | ||
formHtml += templated_links_1.default(state, options); | ||
const target = '_htarget' in ctx.request.query ? ctx.request.query._htarget : ctx.request.path; | ||
@@ -13,0 +13,0 @@ if (ctx.response.is('application/prs.hal-forms+json')) { |
@@ -1,2 +0,3 @@ | ||
import { Link, SureOptions } from '../../types'; | ||
export default function parseTemplatedLinks(links: Link[], options: SureOptions): string; | ||
import { SureOptions } from '../../types'; | ||
import { State } from 'ketting'; | ||
export default function parseTemplatedLinks(state: State, options: SureOptions): string; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const util_1 = require("../../util"); | ||
function parseTemplatedLinks(links, options) { | ||
function parseTemplatedLinks(state, options) { | ||
let formHtml = ''; | ||
for (const link of links) { | ||
for (const link of state.links.getAll()) { | ||
if (options.hiddenRels.includes(link.rel) || link.rel in options.navigationLinks) { | ||
@@ -8,0 +8,0 @@ continue; |
@@ -0,2 +1,3 @@ | ||
import { State } from 'ketting'; | ||
import { Context } from '@curveball/core'; | ||
export default function halBody(ctx: Context, body: any): string; | ||
export default function halBody(ctx: Context, state: State): string; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const util_1 = require("../util"); | ||
function halBody(ctx, body) { | ||
const tmpBody = Object.assign({}, body); | ||
function halBody(ctx, state) { | ||
const tmpBody = Object.assign({}, state.data); | ||
if (!('_browser-fullbody' in ctx.query)) { | ||
@@ -7,0 +7,0 @@ delete tmpBody._links; |
@@ -1,2 +0,3 @@ | ||
import { Link, SureOptions } from '../types'; | ||
export default function linksTable(links: Link[], options: SureOptions): string; | ||
import { SureOptions } from '../types'; | ||
import { State } from 'ketting'; | ||
export default function linksTable(state: State, options: SureOptions): string; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const util_1 = require("../util"); | ||
function linksTable(links, options) { | ||
function linksTable(state, options) { | ||
let linkHtml = ''; | ||
// Grouping links by rel. | ||
const groups = {}; | ||
for (const link of links) { | ||
for (const link of state.links.getAll()) { | ||
if (options.hiddenRels.includes(link.rel) || link.rel in options.navigationLinks || link.rendered) { | ||
@@ -10,0 +10,0 @@ continue; |
@@ -1,2 +0,2 @@ | ||
import { Context } from '@curveball/core'; | ||
export default function markdownBody(ctx: Context, body: any): string; | ||
import { State } from 'ketting'; | ||
export default function markdownBody(state: State): string; |
@@ -8,3 +8,3 @@ "use strict"; | ||
const markdown_it_1 = __importDefault(require("markdown-it")); | ||
function markdownBody(ctx, body) { | ||
function markdownBody(state) { | ||
let html = '<section class="body-markdown">'; | ||
@@ -20,3 +20,3 @@ html += markdown_it_1.default({ | ||
} | ||
}).render(body); | ||
}).render(state.data); | ||
html += '</section>'; | ||
@@ -23,0 +23,0 @@ return html; |
@@ -1,2 +0,3 @@ | ||
import { Link, SureOptions } from '../types'; | ||
import { Link } from 'ketting'; | ||
import { SureOptions } from '../types'; | ||
export default function navigation(links: Link[], options: SureOptions): string; |
@@ -1,2 +0,3 @@ | ||
import { Link, SureOptions } from '../types'; | ||
export default function generatePager(links: Link[], options: SureOptions): string; | ||
import { State } from 'ketting'; | ||
import { SureOptions } from '../types'; | ||
export default function generatePager(state: State, options: SureOptions): string; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const util_1 = require("../util"); | ||
function generatePager(links, options) { | ||
function generatePager(state, options) { | ||
const html = []; | ||
for (const link of util_1.getNavLinks(links, options, 'pager')) { | ||
for (const link of util_1.getNavLinks(state.links.getAll(), options, 'pager')) { | ||
html.push(`<a href="${util_1.h(link.href)}" rel="${util_1.h(link.rel)}" title="${util_1.h(link.title)}">` + | ||
@@ -8,0 +8,0 @@ `<img src="${util_1.h(link.icon)}" /> ${util_1.h(link.title)}</a>`); |
import { Context } from '@curveball/core'; | ||
import { Link, SureOptions } from '../types'; | ||
import { SureOptions } from '../types'; | ||
import { State } from 'ketting'; | ||
/** | ||
* This component renders an entire resource. | ||
*/ | ||
export default function resource(ctx: Context, body: any, links: Link[], options: SureOptions): Promise<string>; | ||
export default function resource(ctx: Context, state: State, options: SureOptions): Promise<string>; |
@@ -16,28 +16,29 @@ "use strict"; | ||
*/ | ||
async function resource(ctx, body, links, options) { | ||
const formsHtml = forms_1.default(ctx, links, options); | ||
const linksHtml = links_table_1.default(links, options); | ||
async function resource(ctx, state, options) { | ||
const formsHtml = forms_1.default(ctx, state, options); | ||
const linksHtml = links_table_1.default(state, options); | ||
return ` | ||
${linksHtml} | ||
${formsHtml} | ||
${await parseBody(ctx, body)} | ||
${await embedded_1.default(ctx, body, options)} | ||
${pager_1.default(links, options)} | ||
${await parseBody(ctx, state)} | ||
${await embedded_1.default(ctx, state, options)} | ||
${pager_1.default(state, options)} | ||
`; | ||
} | ||
exports.default = resource; | ||
async function parseBody(ctx, body) { | ||
if (!body) { | ||
async function parseBody(ctx, state) { | ||
if (!state.data) { | ||
// Ignore empty bodies | ||
return ''; | ||
} | ||
switch (ctx.response.type) { | ||
const contentType = state.contentHeaders().get('Content-Type'); | ||
switch (contentType) { | ||
case 'application/json': | ||
case 'application/problem+json': | ||
case 'application/hal+json': | ||
return hal_body_1.default(ctx, body); | ||
return hal_body_1.default(ctx, state); | ||
case 'text/markdown': | ||
return markdown_body_1.default(ctx, body); | ||
return markdown_body_1.default(state); | ||
case 'text/csv': | ||
return csv_body_1.default(ctx, body); | ||
return csv_body_1.default(state); | ||
default: | ||
@@ -44,0 +45,0 @@ return ''; |
@@ -1,2 +0,3 @@ | ||
import { Link, SureOptions } from '../types'; | ||
import { Link } from 'ketting'; | ||
import { SureOptions } from '../types'; | ||
export default function generateSearch(links: Link[], options: SureOptions): string; |
@@ -13,10 +13,9 @@ "use strict"; | ||
async function generateHtmlIndex(ctx, options) { | ||
checkFormat(ctx); | ||
const body = ctx.response.body; | ||
const links = util_1.fetchLinks(ctx, options); | ||
const state = await util_1.contextToState(ctx); | ||
const links = state.links.getAll(); | ||
const navHtml = navigation_1.default(links, options); | ||
const alternateHtml = alternate_1.default(links, options); | ||
const [headTitle, bodyTitle] = generateTitle(links, ctx, options); | ||
const [headTitle, bodyTitle] = generateTitle(state, options); | ||
const searchHtml = search_1.default(links, options); | ||
const resourceHtml = await resource_1.default(ctx, body, links, options); | ||
const resourceHtml = await resource_1.default(ctx, state, options); | ||
const stylesheets = options.stylesheets.map(ss => { | ||
@@ -53,4 +52,4 @@ return ` <link rel="stylesheet" href="${util_1.h(url_1.default.resolve(options.assetBaseUrl, ss))}" type="text/css" />\n`; | ||
exports.default = generateHtmlIndex; | ||
function generateTitle(links, ctx, options) { | ||
const selfLink = links.find(link => link.rel === 'self'); | ||
function generateTitle(state, options) { | ||
const selfLink = state.links.get('self'); | ||
let title; | ||
@@ -63,5 +62,5 @@ let href; | ||
else { | ||
href = ctx.path; | ||
href = state.uri; | ||
} | ||
const body = ctx.response.body; | ||
const body = state.data; | ||
if (body && typeof body === 'object') { | ||
@@ -83,13 +82,16 @@ if (!title && body.title) { | ||
} | ||
function checkFormat(ctx) { | ||
if ((typeof ctx.response.body === 'string' || ctx.response.body instanceof Buffer) && | ||
ctx.response.is('json')) { | ||
if (ctx.response.body instanceof Buffer) { | ||
ctx.response.body = JSON.parse(ctx.response.body.toString('utf-8')); | ||
} | ||
else { | ||
ctx.response.body = JSON.parse(ctx.response.body); | ||
} | ||
/* | ||
function checkFormat(ctx: Context) { | ||
if ((typeof ctx.response.body === 'string' || ctx.response.body instanceof Buffer) && | ||
ctx.response.is('json') | ||
) { | ||
if (ctx.response.body instanceof Buffer) { | ||
ctx.response.body = JSON.parse(ctx.response.body.toString('utf-8')); | ||
} else { | ||
ctx.response.body = JSON.parse(ctx.response.body); | ||
} | ||
} | ||
} | ||
}*/ | ||
//# sourceMappingURL=html-index.js.map |
@@ -6,2 +6,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.supportedContentTypes = void 0; | ||
const html_index_1 = __importDefault(require("./html-index")); | ||
@@ -8,0 +9,0 @@ const serve_asset_1 = __importDefault(require("./serve-asset")); |
@@ -0,1 +1,2 @@ | ||
import { Link } from 'ketting'; | ||
/** | ||
@@ -57,15 +58,1 @@ * Options that may be passed to the middleware. | ||
}; | ||
/** | ||
* Represents some link. | ||
*/ | ||
export declare type Link = { | ||
rel: string; | ||
href: string; | ||
type?: string; | ||
title?: string; | ||
templated?: boolean; | ||
rendered?: boolean; | ||
hints?: { | ||
status?: 'deprecated' | 'gone'; | ||
}; | ||
}; |
@@ -0,3 +1,5 @@ | ||
import { Link } from 'ketting'; | ||
import { NavigationLink, NavigationPosition, SureOptions } from './types'; | ||
import { State } from 'ketting'; | ||
import { Context } from '@curveball/core'; | ||
import { Link, NavigationLink, NavigationPosition, SureOptions } from './types'; | ||
export declare function h(input?: string): string; | ||
@@ -12,5 +14,10 @@ /** | ||
/** | ||
* Grab all links from the body. | ||
* We use Ketting for a lot of the parsing. | ||
* | ||
* The main object that contains a response in Ketting is a 'State' object, | ||
* to get a State, we need a Response object, as it's part of the fetch() | ||
* specification. | ||
* | ||
* So we go from ctx.response, to Response, to State. | ||
*/ | ||
export declare function fetchLinks(ctx: Context, options: SureOptions): Link[]; | ||
export declare function getHalLinks(body: any): Link[]; | ||
export declare function contextToState(ctx: Context): Promise<State>; |
@@ -6,5 +6,6 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.contextToState = exports.highlightJson = exports.getNavLinks = exports.h = void 0; | ||
const highlight_js_1 = __importDefault(require("highlight.js")); | ||
const http_link_header_1 = __importDefault(require("http-link-header")); | ||
const url_1 = __importDefault(require("url")); | ||
const ketting_1 = require("ketting"); | ||
function h(input = '') { | ||
@@ -42,2 +43,3 @@ const map = { | ||
rel: link.rel, | ||
context: link.context, | ||
href: link.href, | ||
@@ -59,53 +61,34 @@ title: link.title ? link.title : (nl.defaultTitle ? nl.defaultTitle : link.rel), | ||
/** | ||
* Grab all links from the body. | ||
* We use Ketting for a lot of the parsing. | ||
* | ||
* The main object that contains a response in Ketting is a 'State' object, | ||
* to get a State, we need a Response object, as it's part of the fetch() | ||
* specification. | ||
* | ||
* So we go from ctx.response, to Response, to State. | ||
*/ | ||
function fetchLinks(ctx, options) { | ||
const result = Array.from(options.defaultLinks); | ||
result.push(...getHalLinks(ctx.response.body)); | ||
const linkHeader = ctx.response.headers.get('Link'); | ||
if (linkHeader) { | ||
const parsed = http_link_header_1.default.parse(linkHeader); | ||
for (const link of parsed.refs) { | ||
result.push({ | ||
rel: link.rel, | ||
href: link.uri, | ||
title: link.title, | ||
type: link.type | ||
}); | ||
async function contextToState(ctx) { | ||
/** | ||
* We need a fake bookmark url | ||
*/ | ||
const client = new ketting_1.Client('http://hal-browser.test'); | ||
const headers = {}; | ||
for (const [name, value] of Object.entries(ctx.response.headers.getAll())) { | ||
if (typeof value === 'number') { | ||
headers[name] = value.toString(); | ||
} | ||
} | ||
return result; | ||
} | ||
exports.fetchLinks = fetchLinks; | ||
function getHalLinks(body) { | ||
if (!body || !body._links) { | ||
return []; | ||
} | ||
const result = []; | ||
for (const rel of Object.keys(body._links)) { | ||
let linksTmp; | ||
if (Array.isArray(body._links[rel])) { | ||
linksTmp = body._links[rel]; | ||
else if (Array.isArray(value)) { | ||
headers[name] = value.join(', '); | ||
} | ||
else { | ||
linksTmp = [body._links[rel]]; | ||
headers[name] = value; | ||
} | ||
for (const link of linksTmp) { | ||
if (!link.href) { | ||
// tslint:disable:no-console | ||
console.warn('Incorrect format for HAL link with rel: ' + rel); | ||
} | ||
result.push({ | ||
rel: rel, | ||
href: link.href, | ||
type: link.type, | ||
title: link.title, | ||
templated: link.templated, | ||
hints: link.hints | ||
}); | ||
} | ||
} | ||
return result; | ||
const response = new Response(ctx.response.body, { | ||
status: ctx.status, | ||
headers, | ||
}); | ||
return client.getStateForResponse(ctx.path, response); | ||
} | ||
exports.getHalLinks = getHalLinks; | ||
exports.contextToState = contextToState; | ||
//# sourceMappingURL=util.js.map |
{ | ||
"name": "hal-browser", | ||
"version": "0.8.6", | ||
"version": "0.9.0", | ||
"description": "A HAL browser middleware", | ||
@@ -38,17 +38,18 @@ "main": "dist/index.js", | ||
"devDependencies": { | ||
"@curveball/core": "^0.10.0", | ||
"@types/chai": "^4.2.7", | ||
"@types/highlight.js": "^9.12.3", | ||
"@types/http-link-header": "^1.0.1", | ||
"@types/markdown-it": "0.0.9", | ||
"@types/mocha": "^5.2.7", | ||
"@types/node": "^10.17.13", | ||
"@types/sinon": "^7.5.1", | ||
"@curveball/core": "^0.14.2", | ||
"@types/chai": "^4.2.12", | ||
"@types/highlight.js": "^9.12.4", | ||
"@types/markdown-it": "^10.0.2", | ||
"@types/mocha": "^8.0.2", | ||
"@types/node": "^10.17.28", | ||
"@types/sinon": "^9.0.4", | ||
"@typescript-eslint/eslint-plugin": "^3.9.0", | ||
"@typescript-eslint/parser": "^3.9.0", | ||
"chai": "^4.2.0", | ||
"mocha": "^7.0.0", | ||
"nyc": "^15.0.0", | ||
"sinon": "^8.0.2", | ||
"ts-node": "^8.5.4", | ||
"tslint": "^5.20.1", | ||
"typescript": "^3.7.4" | ||
"eslint": "^7.7.0", | ||
"mocha": "^8.1.1", | ||
"nyc": "^15.1.0", | ||
"sinon": "^9.0.3", | ||
"ts-node": "^8.10.2", | ||
"typescript": "^3.9.7" | ||
}, | ||
@@ -62,10 +63,19 @@ "types": "dist/", | ||
"dependencies": { | ||
"csv-parse": "^4.8.3", | ||
"highlight.js": "^9.17.1", | ||
"http-link-header": "^1.0.2", | ||
"markdown-it": "^10.0.0" | ||
"csv-parse": "^4.12.0", | ||
"highlight.js": "^10.1.2", | ||
"ketting": "^6.0.0-beta.2", | ||
"markdown-it": "^11.0.0" | ||
}, | ||
"peerDependencies": { | ||
"@curveball/core": ">=0.9.1 <1" | ||
}, | ||
"mocha": { | ||
"require": "ts-node/register", | ||
"recursive": true, | ||
"extension": [ | ||
"ts", | ||
"js", | ||
"tsx" | ||
] | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
88
0
99978
16
1915
+ Addedketting@^6.0.0-beta.2
+ Addedfetch-mw-oauth2@0.7.7(transitive)
+ Addedhal-types@1.9.0(transitive)
+ Addedhighlight.js@10.7.3(transitive)
+ Addedketting@6.2.0(transitive)
+ Addedlinkify-it@3.0.3(transitive)
+ Addedmarkdown-it@11.0.1(transitive)
+ Addednode-fetch@2.7.0(transitive)
+ Addedpct-encode@1.0.3(transitive)
+ Addedquerystring-browser@1.0.4(transitive)
+ Addedsax@1.4.1(transitive)
+ Addedtr46@0.0.3(transitive)
+ Addeduri-template@1.0.3(transitive)
+ Addedwebidl-conversions@3.0.1(transitive)
+ Addedwhatwg-url@5.0.0(transitive)
- Removedhttp-link-header@^1.0.2
- Removedhighlight.js@9.18.5(transitive)
- Removedlinkify-it@2.2.0(transitive)
- Removedmarkdown-it@10.0.0(transitive)
Updatedcsv-parse@^4.12.0
Updatedhighlight.js@^10.1.2
Updatedmarkdown-it@^11.0.0