1 | /*
|
2 | * Copyright 2012-2013 the original author or authors
|
3 | * @license MIT, see LICENSE.txt for details
|
4 | *
|
5 | * @author Scott Andrews
|
6 | */
|
7 |
|
8 | (function (define) {
|
9 | ;
|
10 |
|
11 | define(function (require) {
|
12 |
|
13 | var interceptor, pathPrefix, rfc5988LinkParser, find;
|
14 |
|
15 | interceptor = require('../interceptor');
|
16 | pathPrefix = require('./pathPrefix');
|
17 | rfc5988LinkParser = require('../parsers/rfc5988');
|
18 | find = require('../util/find');
|
19 |
|
20 | /**
|
21 | * [Experimental]
|
22 | *
|
23 | * Supports 'Hypertext As The Engine Of Application State' style
|
24 | * services by indexing the 'links' property from the entity to make
|
25 | * accessing links via the 'rel' attribute easier.
|
26 | *
|
27 | * Links are index in two ways:
|
28 | * 1. as link's 'rel' which when accessed issues a request for the
|
29 | * linked resource. A promise for the related resourse is expected
|
30 | * to be returned.
|
31 | * 2. as link's 'rel' with 'Link' appended, as a reference to the link
|
32 | * object
|
33 | *
|
34 | * The 'Link' response header is also parsed for related resources
|
35 | * following rfc5988. The values parsed from the headers are indexed
|
36 | * into the response.links object.
|
37 | *
|
38 | * Also defines a 'clientFor' factory function that creates a new
|
39 | * client configured to communicate with a related resource.
|
40 | *
|
41 | * The client for the resoruce reference and the 'clientFor' function
|
42 | * can be provided by the 'client' config property.
|
43 | *
|
44 | * Index links are exposed by default on the entity. A child object may be
|
45 | * configed by the 'target' config property.
|
46 | *
|
47 | * @param {Client} [client] client to wrap
|
48 | * @param {string} [config.target=''] property to create on the entity and
|
49 | * parse links into. If empty, the response entity is used directly.
|
50 | * @param {Client} [config.client=request.originator] the parent client to
|
51 | * use when creating clients for a linked resources. Defaults to the
|
52 | * request's originator if available, otherwise the current interceptor's
|
53 | * client
|
54 | *
|
55 | * @returns {Client}
|
56 | */
|
57 | return interceptor({
|
58 | init: function (config) {
|
59 | config.target = config.target || '';
|
60 | return config;
|
61 | },
|
62 | response: function (response, config, meta) {
|
63 | var client;
|
64 |
|
65 | client = config.client || (response.request && response.request.originator) || meta.client;
|
66 |
|
67 | function apply(target, links) {
|
68 | links.forEach(function (link) {
|
69 | Object.defineProperty(target, link.rel + 'Link', {
|
70 | enumerable: false,
|
71 | configurable: true,
|
72 | value: link
|
73 | });
|
74 | Object.defineProperty(target, link.rel, {
|
75 | enumerable: false,
|
76 | configurable: true,
|
77 | get: function () {
|
78 | var response = client({ path: link.href });
|
79 | Object.defineProperty(target, link.rel, {
|
80 | enumerable: false,
|
81 | configurable: true,
|
82 | value: response
|
83 | });
|
84 | return response;
|
85 | }
|
86 | });
|
87 | });
|
88 |
|
89 | // if only Proxy was well supported...
|
90 | Object.defineProperty(target, 'clientFor', {
|
91 | enumerable: false,
|
92 | value: function clientFor(rel, parentClient) {
|
93 | return pathPrefix(
|
94 | parentClient || client,
|
95 | { prefix: target[rel + 'Link'].href }
|
96 | );
|
97 | }
|
98 | });
|
99 | }
|
100 |
|
101 | function parseLinkHeaders(headers) {
|
102 | var links = [];
|
103 | [].concat(headers).forEach(function (header) {
|
104 | try {
|
105 | links = links.concat(rfc5988LinkParser.parse(header));
|
106 | }
|
107 | catch (e) {
|
108 | // ignore
|
109 | // TODO consider a debug mode that logs
|
110 | }
|
111 | });
|
112 | return links;
|
113 | }
|
114 |
|
115 | if (response.headers && response.headers.Link) {
|
116 | response.links = response.links || {};
|
117 | apply(response.links, parseLinkHeaders(response.headers.Link));
|
118 | }
|
119 |
|
120 | find.findProperties(response.entity, 'links', function (obj, host) {
|
121 | var target;
|
122 |
|
123 | if (Array.isArray(host.links)) {
|
124 | if (config.target === '') {
|
125 | target = host;
|
126 | }
|
127 | else {
|
128 | target = {};
|
129 | Object.defineProperty(host, config.target, {
|
130 | enumerable: false,
|
131 | value: target
|
132 | });
|
133 | }
|
134 |
|
135 | apply(target, host.links);
|
136 | }
|
137 | });
|
138 |
|
139 | return response;
|
140 | }
|
141 | });
|
142 |
|
143 | });
|
144 |
|
145 | }(
|
146 | typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); }
|
147 | // Boilerplate for AMD and Node
|
148 | ));
|