Skip to content

Latest commit

 

History

History
998 lines (787 loc) · 36.5 KB

API.md

File metadata and controls

998 lines (787 loc) · 36.5 KB

Modules

ims

Wrapper function to easily perform adobe IMS authentication

Usage:

const wrap = require('@adobe/helix-shared-wrap');
const bodyData = require('@adobe/helix-shared-body-data');
const ims = require('@adobe/helix-shared-ims');

async main(req, context) { // …my action code… if (context.ims.profile) { // do authenticated stuff } }

module.exports.main = wrap(main) .with(ims, { clientId: 'my-client' }) .with(bodyData) .with(logger);

wrap

Helper function to easily chain functions.

Usage:

const { wrap } = require('@adobe/helix-shared');

async main(params) { // …my action code… }

module.exports.main = wrap(main) .with(epsagon) .with(status) .with(logger);

Classes

ModifiersConfig

The modifiers class help manage the metadata and headers modifiers.

SchemaDerivedConfig

A Helix Config that is based on a (number of) JSON Schema(s).

GitUrl

Represents a GIT url.

Constants

nextTickpromise

Await the next tick;

NOTE: Internally this uses setImmediate, not process.nextTick. This is because process.nextTick and setImmediate are horribly named and their names should be swapped.

const mAsyncFn = () => {
  const page1 = await request('https://example.com/1');
  await nextTick();
  const page2 = await request('https://example.com/2');
  ...
};
isNodeType

Check whether the given type is the type of a dom node. Note that, in order to support various dom implementations, this function uses a heuristic and there might be some false positives.

assertNode

Ensure that the given node is a domNode. Checks with isNode()

nodeName

Determine the name of a node. The result is always in lower case.

ancestryNodesArray.<DomNode>

Retrieve all the parent nodes of a dom node.

equalizeNodeDomNode

Removes comments and redundant whitespace from dom trees and moves meaningful white space to a standardized location.

Adjacent text nodes are also merged, as https://developer.mozilla.org/en-US/docs/Web/API/Node/normalize does.

This function predominantly serves as a way to preprocess nodes given to nodeIsEquivalent, so these nodes can be compared using isEqualNode without insignificant whitespace changes standing in the way of equivalence.

normalizeDomWhitespace is supposed to turn equivalent dom treesturn equivalent dom trees into equal dom trees.

Motivation

The concept of equivalence is a bit fuzzy unfortunately. Some html minifiers like kangax's html-minifier even leave whitespace alone by default, because what transformations are permitted is so unclear. Going by isEqualNode, any two dom trees just differentiated by their whitespace content are unequal.

This function's ultimate goal is to introduce an equivalence concept which

  1. closely matches the mental model developers would have
  2. does not affect rendering

For instance, indenting dom nodes for improved readability should usually not affect equivalence, neither should inserting newline characters/replacing spaces with newlines because a line is growing too long or because dom elements should be one per line.

Whitespace in

 elements however should affect equivalence.

The given examples also adhere to the 'do not affect rendering' rules unless exotic javascript or CSS is added after the fact.

Precise semantics

The following rules are used by this function:

  1. Whitespace in
     tags and contained tags is left alone.
      In more precise terms, whitespace in any elements whose computed
      white-space style property starts with pre is left alone.
  2. Whitespace in other elements is compacted, meaning any combination of whitespace characters (newlines, spaces, tabs, etc) is replaced by a single space.
  3. Any whitespace before/after closing/opening tags is removed, unless the tag in question is inline. A tag is inline if it's computed style property display starts with inline or is set to content. This is the default behaviour for .
  4. Whitespace next to opening/closing tags is also collapsed; all space between text nodes across a tree of purely inline elements are collapsed into a single space character. The space character is placed in the closest common ancestor, between the ancestors of both text nodes.

Rule 3 and 4 are a bit verbose. Please take a look at the examples below.

See also: https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace_in_the_DOM https://drafts.csswg.org/css-text-3/#propdef-white-space

Examples

<div> </div> -> <div></div>

Rule 3 - div is not inline:

Hello <div> world </div> friend -> Hello<div>world</div>friend

Rule 4 - span is inline:

Hello <span> world </span> friend -> Hello <span>world</span> friend

Rule 4 – the whitespace between multiple inline elements is placed int the lowest common ancestor.

<a>Hello </a> \n <a> World</a> -> <a>Hello</a> <a>World</a> <a>Hello</a><a> World</a> -> <a>Hello</a> <a>World</a> <span><a>Hello</a></span><a> World</a> -> <span><a>Hello</a></span> <a>World</a>

CSS Handling

Note that this function does not manually check for dom nodes like

 or differentiate between  and 
. Instead the `display` and `white-space` computed css properties are used to determine how space should be compacted.

Since the computedStyle is used to determine how space compaction should be applied, the function can deal with css affecting rendering of white space: e.g. if white-space is set to pre, this will be detected by this function just as if a

 element had been used.
The same is true for the display property.

The only requirement for this to work is that the CSS in question is present in the dom tree.

So when JSDOM is used to provide the DOM, then the entire html document should be loaded (not just fragments) and loading external stylesheets should be enabled...

nodeIsEquivalentBoolean

Test whether two nodes are equivalent.

equals() over two dom nodes is an alias for this.

Invokes equalizeNode() on both given elements before invoking .isEqualNode. This means the equivalence model described in equalizeNode() is employed. Please refer to it's documentation to learn more

nodeMatchesBoolean

Node equivalence testing with wildcard support.

This is mostly like nodeIsEquivalent, except that the pattern may contain wildcard nodes. Wildcard nodes are nodes with the name match:any.

Wildcards in the pattern will lazily (meaning non greedily) match zero, one or many dom nodes in the given node to test.

<match:any></match:any> matches anything `` foo <div></div>

<match:any></match:any>Hello<match:any></match:any> matches any node that contains Hello as a child: HelloHello Foo Hello Foo <div></div> Foo Hello but not this example, because here hello is in a subnode. <div>Hello</div>

<div class='xxx' id='Borg'><matches:any></matches:any>Foo</div> matches: <div class='xxx' id='Borg'>Foo</div> <div class='xxx' id='Borg'>Hello Foo</div> <div id='Borg' class='xxx'>borg Foo</div> but not Foo <div id='Borg' class='xxx'></div>

assertEquivalentNode

Assert that two dom nodes are equivalent. The implementation mostly defers to .isEqualNode, but provides better error messages.

dumpDOM

prints dom in order for changes to be more discernible.

Functions

getData(request, [opts])Promise.<object>

Extracts the data from the given request. The data can be provided either as request parameters, url-encoded form data body, or a json body.

Note that for post body requests, the body is consumed from the request and is no longer available.

toMetaName(text)string

Converts all non-valid characters to -.

stripQuery(m, ...specialparams)object

Cleans up the URL by removing parameters that are deemed special. These special parameters will be returned in the return object instead.

getData(request, ...names)object

Exported only for testisg

match(globs, path, defaultValue)boolean

Return a flag indicating whether a particular path is matches all given glob patterns.

contains(cfg, path)boolean

Return a flag indicating whether a particular path is contained in the indexing configuration (include or exclude element). This is true if a path is included and not excluded.

getDOMValue(elements, expression, log, vars)

Return a value in the DOM by evaluating an expression

indexResource(path, response, config, log)object

Given a response, extract a value and evaluate an expression on it. The index contains the CSS selector that will select the value(s) to process. If we get multiple values, we return an array.

dequeue(queue)Generator.<*, void, *>

Simple dequeing iterator.

_request(target, input)any

Pass a request to the AWS secrets manager

reset()

reset the cache - for testing only

loadSecrets(ctx, [opts])Promise.<object>

Loads the secrets from the respective secrets manager.

multiline()

This is a helper for declaring multiline strings.

const s = multiline(`
    Foo
    Bar
    Baz
   Hello

Bang

`);

The function basically just takes a string and then strips the first & last lines if they are empty.

In order to remove indentation, we determine the common whitespace prefix length (number of space 0x20 characters at the start of the line). This prefix is simply removed from each line...

lookupBackendResponses(status)Object

A glorified lookup table that translates backend errors into the appropriate HTTP status codes and log levels for your service.

computeSurrogateKey(url)Promise.<string>

Computes the caching Surrogate-Key for the given url. The computation uses a hmac_sha256 with a fixed key: {@code "helix"}. the result is base64 encoded and truncated to 16 characters. This algorithm is chosen, because similar functionality exists in Fastly's VCL api:

declare local var.key STRING;
set var.key = digest.hmac_sha256_base64("helix", "input");
set var.key = regsub(var.key, "(.{16}).*", "\1");
propagateStatusCode(status)int

What is the appropriate status code to use in your service when your backend responds with status? This function provides a standardized lookup function to map backend responses to gateway responses, assuming you are implementing the gateway.

logLevelForStatusCode(status)string

What is the appropriate log level for logging HTTP responses you are getting from a backend when the backend responds with status? This function provides a standardized lookup function of backend status codes to log levels.

You can use it like this:

logger[logLevelForStatusCode(response.status)](response.message);
cleanupHeaderValue(value)

Cleans up a header value by stripping invalid characters and truncating to 1024 chars

hashContentBusId(value)

Compute an SHA digest from some string value.

ims

Wrapper function to easily perform adobe IMS authentication

Usage:

const wrap = require('@adobe/helix-shared-wrap');
const bodyData = require('@adobe/helix-shared-body-data');
const ims = require('@adobe/helix-shared-ims');

async main(req, context) {
  // …my action code…
  if (context.ims.profile) {
    // do authenticated stuff
  }
}

module.exports.main = wrap(main)
  .with(ims, { clientId: 'my-client' })
  .with(bodyData)
  .with(logger);

module.exports(func, [options]) ⇒ UniversalFunction

Wraps a function with an ims authorization middle ware. If the request is authenticated, the context.ims will contain a profile object, representing the authenticated user profile.

The wrapper claims several routes:

The IMSConfig.routeLogin (default '/login') is used to respond with a redirect to the IMS login page in 'no-prompt' mode. i.e. the IMS page will not provide username/password fields to login the user, but tries instead to silently login. After authentication the IMS login page redirects back to IMSConfig.routeLoginRedirect.

The IMSConfig.routeLoginRedirect (default '/login/ack') route handles the response from the first, silent login attempt. The the login was successful, it will respond with a redirect to the root /. if not successful, it will respond with a redirect again to the IMS login page in normal mode, i.e. where the IMS page provides means to login. After login, the IMS login page redirects back to IMSConfig.routeLoginRedirectPrompt.

The IMSConfig.routeLoginRedirectPrompt (default '/login/ack2') route handles the response from the second login attempt. The login was successful, it will respond with a redirect to the root /, otherwise the request remains unauthenticated.

After a successful login, a ims_access_token cookie is set on the response, which is then used for subsequent requests.

The IMSConfig.routeLogout (default '/logout') is used to logout the user. It sends a request to the IMS logout endpoint and subsequently clears the ims_access_token cookie. The response is always be a 200.

The IMS access token can either be provided via the ims_access_token cookie, or a request parameter with the same name.

Kind: Exported function
Returns: UniversalFunction - an universal function with the added middleware.

Param Type Description
func UniversalFunction the universal function
[options] IMSConfig Options

module.exports~redirectToLogin(ctx, noPrompt) ⇒ Response

Calculates the login redirect response

Kind: inner method of module.exports
Returns: Response - redirect response

Param Type Description
ctx UniversalContextWithIMS universal context
noPrompt boolean flag indicating if the login should be silent

module.exports~fetchProfile(ctx) ⇒ Promise.<(IMSProfile|null)>

Fetches the ims profile

Kind: inner method of module.exports

Param Type Description
ctx UniversalContextWithIMS the context of the universal serverless function

module.exports~logout(ctx) ⇒ Promise.<Response>

Sends the logout request to IMS and clears the access token cookie.

Kind: inner method of module.exports

Param Type Description
ctx UniversalContextWithIMS the context of the universal serverless function

wrap

Helper function to easily chain functions.

Usage:

const { wrap } = require('@adobe/helix-shared');

async main(params) {
  // …my action code…
}

module.exports.main = wrap(main)
  .with(epsagon)
  .with(status)
  .with(logger);

module.exports(fn) ⇒ WrappableFunction

A function that makes your function (i.e. main) wrappable, so that using with a number of wrappers can be applied. This allows you to export the result as a new function.

Usage:

async main(req, context) {
  //…my action code…
}

module.exports.main = wrap(main)
.with(epsagon)
.with(status)
.with(logger);

Note: the execution order is that the last wrapper added will be executed first.

Kind: Exported function
Returns: WrappableFunction - the same main function, now including a with method

Param Type Description
fn function the function to prepare for wrapping

nextTick ⇒ promise

Await the next tick;

NOTE: Internally this uses setImmediate, not process.nextTick. This is because process.nextTick and setImmediate are horribly named and their names should be swapped.

const mAsyncFn = () => {
  const page1 = await request('https://example.com/1');
  await nextTick();
  const page2 = await request('https://example.com/2');
  ...
};

Kind: global constant
Returns: promise - A promise that will resolve during the next tick.

isNodeType

Check whether the given type is the type of a dom node. Note that, in order to support various dom implementations, this function uses a heuristic and there might be some false positives.

Kind: global constant

assertNode

Ensure that the given node is a domNode. Checks with isNode()

Kind: global constant

nodeName

Determine the name of a node. The result is always in lower case.

Kind: global constant

ancestryNodes ⇒ Array.<DomNode>

Retrieve all the parent nodes of a dom node.

Kind: global constant
Returns: Array.<DomNode> - All the ancestor dom nodes to the given dom node, starting with the most distant dom node.

Param Type
node DomNode

equalizeNode ⇒ DomNode

Removes comments and redundant whitespace from dom trees and moves meaningful white space to a standardized location.

Adjacent text nodes are also merged, as https://developer.mozilla.org/en-US/docs/Web/API/Node/normalize does.

This function predominantly serves as a way to preprocess nodes given to nodeIsEquivalent, so these nodes can be compared using isEqualNode without insignificant whitespace changes standing in the way of equivalence.

normalizeDomWhitespace is supposed to turn equivalent dom treesturn equivalent dom trees into equal dom trees.

Motivation

The concept of equivalence is a bit fuzzy unfortunately. Some html minifiers like kangax's html-minifier even leave whitespace alone by default, because what transformations are permitted is so unclear. Going by isEqualNode, any two dom trees just differentiated by their whitespace content are unequal.

This function's ultimate goal is to introduce an equivalence concept which

  1. closely matches the mental model developers would have
  2. does not affect rendering

For instance, indenting dom nodes for improved readability should usually not affect equivalence, neither should inserting newline characters/replacing spaces with newlines because a line is growing too long or because dom elements should be one per line.

Whitespace in

 elements however should affect equivalence.

The given examples also adhere to the 'do not affect rendering' rules unless exotic javascript or CSS is added after the fact.

Precise semantics

The following rules are used by this function:

  1. Whitespace in
     tags and contained tags is left alone.
    In more precise terms, whitespace in any elements whose computed
    white-space style property starts with pre is left alone.
  2. Whitespace in other elements is compacted, meaning any combination of whitespace characters (newlines, spaces, tabs, etc) is replaced by a single space.
  3. Any whitespace before/after closing/opening tags is removed, unless the tag in question is inline. A tag is inline if it's computed style property display starts with inline or is set to content. This is the default behaviour for .
  4. Whitespace next to opening/closing tags is also collapsed; all space between text nodes across a tree of purely inline elements are collapsed into a single space character. The space character is placed in the closest common ancestor, between the ancestors of both text nodes.

Rule 3 and 4 are a bit verbose. Please take a look at the examples below.

See also: https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace_in_the_DOM https://drafts.csswg.org/css-text-3/#propdef-white-space

Examples

<div> </div> -> <div></div>

Rule 3 - div is not inline:

Hello <div> world </div> friend -> Hello<div>world</div>friend

Rule 4 - span is inline:

Hello <span> world </span> friend -> Hello <span>world</span> friend

Rule 4 – the whitespace between multiple inline elements is placed int the lowest common ancestor.

<a>Hello </a> \n <a> World</a> -> <a>Hello</a> <a>World</a> <a>Hello</a><a> World</a> -> <a>Hello</a> <a>World</a> <span><a>Hello</a></span><a> World</a> -> <span><a>Hello</a></span> <a>World</a>

CSS Handling

Note that this function does not manually check for dom nodes like

 or differentiate between  and 
. Instead the `display` and `white-space` computed css properties are used to determine how space should be compacted. Since the computedStyle is used to determine how space compaction should be applied, the function can deal with css affecting rendering of white space: e.g. if `white-space` is set to `pre`, this will be detected by this function just as if a
 element had been used.
The same is true for the `display` property.

The only requirement for this to work is that the CSS in question is
present in the dom tree.

So when JSDOM is used to provide the DOM, then the entire html document
should be loaded (not just fragments) and loading external stylesheets
should be enabled...

**Kind**: global constant  
**Returns**: DomNode - The node parameter; the node parameter was mutated by this
  function; a reference to it is returned in order to facilitate function chaining.  

| Param | Type | Description |
| --- | --- | --- |
| node | DomNode | The node to equalize; this value will be mutated! |



## nodeIsEquivalent ⇒ Boolean
Test whether two nodes are equivalent.

`equals()` over two dom nodes is an alias for this.

Invokes equalizeNode() on both given elements before
invoking .isEqualNode.
This means the equivalence model described in `equalizeNode()`
is employed. Please refer to it's documentation to learn more

**Kind**: global constant  

| Param | Type |
| --- | --- |
| a | DomNode | 
| b | DomNode | 



## nodeMatches ⇒ Boolean
Node equivalence testing with wildcard support.

This is mostly like nodeIsEquivalent, except that the
pattern may contain wildcard nodes. Wildcard nodes are nodes
with the name `match:any`.

Wildcards in the pattern will lazily (meaning non greedily)
match zero, one or many dom nodes in the given node to test.

`` matches anything
  ``
  `foo`
  `
` `Hello` matches any node that contains `Hello` as a child: `HelloHello` `Foo Hello Foo` `
Foo Hello` but not this example, because here hello is in a subnode. `
Hello
` `
Foo
` matches: `
Foo
` `
Hello Foo
` `
borg Foo
` but not `Foo` `
` **Kind**: global constant | Param | Type | | --- | --- | | node | DomNode | | pattern | DomNode | ## assertEquivalentNode Assert that two dom nodes are equivalent. The implementation mostly defers to .isEqualNode, but provides better error messages. **Kind**: global constant ## dumpDOM prints dom in order for changes to be more discernible. **Kind**: global constant | Param | Type | Description | | --- | --- | --- | | actual | object | node from original page | | expected | object | node from test domain page | | level | number | current level in recursion tree return dump of dom that is indented at every level by level*2 spaces | ## getData(request, [opts]) ⇒ Promise.<object> Extracts the _data_ from the given request. The data can be provided either as request parameters, url-encoded form data body, or a json body. Note that for post body requests, the body is consumed from the request and is no longer available. **Kind**: global function **Returns**: Promise.<object> - the parsed data object. | Param | Type | Description | | --- | --- | --- | | request | Request | The universal request | | [opts] | BodyDataOptions | Options | ## toMetaName(text) ⇒ string Converts all non-valid characters to `-`. **Kind**: global function **Returns**: string - the meta name | Param | Type | Description | | --- | --- | --- | | text | string | input text | ## stripQuery(m, ...specialparams) ⇒ object Cleans up the URL by removing parameters that are deemed special. These special parameters will be returned in the return object instead. **Kind**: global function **Returns**: object - an object with a clean URL and extracted parameters | Param | Type | Description | | --- | --- | --- | | m | object | the mount point | | m.url | string | mount point URL | | ...specialparams | string | list of special parameters that should be removed from the URL and returned in the object | ## getData(request, ...names) ⇒ object Exported only for testisg **Kind**: global function **Returns**: object - an object with the provided parameter names as keys | Param | Type | Description | | --- | --- | --- | | request | Request | a fetch-API Request | | ...names | string | the parameter names to extract | ## match(globs, path, defaultValue) ⇒ boolean Return a flag indicating whether a particular path is matches all given glob patterns. **Kind**: global function **Returns**: boolean - whether path matches the globs | Param | Type | Description | | --- | --- | --- | | globs | Array.<string> | globbing patterns | | path | string | path to check | | defaultValue | boolean | what to return if `globs` is undefined | ## contains(cfg, path) ⇒ boolean Return a flag indicating whether a particular path is contained in the indexing configuration (include or exclude element). This is true if a path is included and *not* excluded. **Kind**: global function **Returns**: boolean - whether path is included in configuration | Param | Type | Description | | --- | --- | --- | | cfg | Index | indexing configuration's | | path | string | path to check | ## getDOMValue(elements, expression, log, vars) Return a value in the DOM by evaluating an expression **Kind**: global function | Param | Type | | --- | --- | | elements | Array.<HTMLElement> | | expression | string | | log | Logger | | vars | object | ## indexResource(path, response, config, log) ⇒ object Given a response, extract a value and evaluate an expression on it. The index contains the CSS selector that will select the value(s) to process. If we get multiple values, we return an array. **Kind**: global function **Returns**: object - extracted properties | Param | Type | Description | | --- | --- | --- | | path | string | Path of document retrieved | | response | object | response containing body and headers | | config | Index | indexing configuration | | log | Logger | logger | ## dequeue(queue) ⇒ Generator.<\*, void, \*> Simple dequeing iterator. **Kind**: global function | Param | | --- | | queue | ## \_request(target, input) ⇒ any Pass a request to the AWS secrets manager **Kind**: global function **Returns**: any - response object | Param | Type | Description | | --- | --- | --- | | target | string | target method to invoke | | input | any | input that will be passed as JSON | ## reset() reset the cache - for testing only **Kind**: global function ## loadSecrets(ctx, [opts]) ⇒ Promise.<object> Loads the secrets from the respective secrets manager. **Kind**: global function **Returns**: Promise.<object> - the secrets or {@code null}. | Param | Type | Description | | --- | --- | --- | | ctx | UniversalContext | the context | | [opts] | SecretsOptions | Options | ## multiline() This is a helper for declaring multiline strings. ``` const s = multiline(` Foo Bar Baz Hello Bang `); ``` The function basically just takes a string and then strips the first & last lines if they are empty. In order to remove indentation, we determine the common whitespace prefix length (number of space 0x20 characters at the start of the line). This prefix is simply removed from each line... **Kind**: global function ## lookupBackendResponses(status) ⇒ Object A glorified lookup table that translates backend errors into the appropriate HTTP status codes and log levels for your service. **Kind**: global function **Returns**: Object - a pair of status code to return and log level to use in your code | Param | Type | Description | | --- | --- | --- | | status | int | the HTTP status code you've been getting from the backend | ## computeSurrogateKey(url) ⇒ Promise.<string> Computes the caching Surrogate-Key for the given url. The computation uses a hmac_sha256 with a fixed key: {@code "helix"}. the result is base64 encoded and truncated to 16 characters. This algorithm is chosen, because similar functionality exists in Fastly's VCL api: ``` declare local var.key STRING; set var.key = digest.hmac_sha256_base64("helix", "input"); set var.key = regsub(var.key, "(.{16}).*", "\1"); ``` **Kind**: global function **Returns**: Promise.<string> - A promise with the computed key. | Param | Type | Description | | --- | --- | --- | | url | \* | The input url. | ## propagateStatusCode(status) ⇒ int What is the appropriate status code to use in your service when your backend responds with `status`? This function provides a standardized lookup function to map backend responses to gateway responses, assuming you are implementing the gateway. **Kind**: global function **Returns**: int - the appropriate HTTP status code for your app | Param | Type | Description | | --- | --- | --- | | status | int | the backend HTTP status code | ## logLevelForStatusCode(status) ⇒ string What is the appropriate log level for logging HTTP responses you are getting from a backend when the backend responds with `status`? This function provides a standardized lookup function of backend status codes to log levels. You can use it like this: ```javascript logger[logLevelForStatusCode(response.status)](response.message); ``` **Kind**: global function **Returns**: string - the correct log level | Param | Type | Description | | --- | --- | --- | | status | int | the HTTP status code from your backend | ## cleanupHeaderValue(value) ⇒ Cleans up a header value by stripping invalid characters and truncating to 1024 chars **Kind**: global function **Returns**: a valid header value | Param | Type | Description | | --- | --- | --- | | value | string | a header value | ## hashContentBusId(value) ⇒ Compute an SHA digest from some string value. **Kind**: global function **Returns**: SHA256 digest of value, shortened to 59 characters | Param | Type | Description | | --- | --- | --- | | value | string | value to create digest for |