UNPKG

4.68 kBPlain TextView Raw
1import FollowablePromise from './followable-promise';
2import Representor from './representor/base';
3import HalRepresentor from './representor/hal';
4import HtmlRepresentor from './representor/html';
5import JsonApiRepresentor from './representor/jsonapi';
6import Resource from './resource';
7import { ContentType, KettingInit } from './types';
8import FetchHelper from './utils/fetch-helper';
9import './utils/fetch-polyfill';
10import { resolve } from './utils/url';
11
12/**
13 * The main Ketting client object.
14 */
15export default class Ketting {
16
17 /**
18 * The url from which all discovery starts.
19 */
20 bookMark: string;
21
22 /**
23 * Here we store all the resources that were ever requested. This will
24 * ensure that if the same resource is requested twice, the same object is
25 * returned.
26 */
27 resourceCache: { [url: string]: Resource };
28
29 /**
30 * Content-Type settings and mappings.
31 *
32 * See the constructor for an example of the structure.
33 */
34 contentTypes: ContentType[];
35
36 /**
37 * The helper class that calls fetch() for us
38 */
39 private fetchHelper: FetchHelper;
40
41 constructor(bookMark: string, options?: KettingInit) {
42
43 if (typeof options === 'undefined') {
44 options = {};
45 }
46
47 this.resourceCache = {};
48
49 this.contentTypes = [
50 {
51 mime: 'application/hal+json',
52 representor: 'hal',
53 q: '1.0',
54 },
55 {
56 mime: 'application/vnd.api+json',
57 representor: 'jsonapi',
58 q: '0.9',
59 },
60 {
61 mime: 'application/json',
62 representor: 'hal',
63 q: '0.8',
64 },
65 {
66 mime: 'text/html',
67 representor: 'html',
68 q: '0.7',
69 }
70 ];
71
72 this.bookMark = bookMark;
73 this.fetchHelper = new FetchHelper(options, this.beforeRequest.bind(this));
74
75 }
76
77 /**
78 * This function is a shortcut for getResource().follow(x);
79 */
80 follow(rel: string, variables?: object): FollowablePromise {
81
82 return this.getResource().follow(rel, variables);
83
84 }
85
86 /**
87 * Returns a resource by its uri.
88 *
89 * This function doesn't do any HTTP requests. The uri is optional. If it's
90 * not specified, it will return the bookmark resource.
91 *
92 * If a relative uri is passed, it will be resolved based on the bookmark
93 * uri.
94 */
95 go(uri?: string): Resource {
96
97 if (typeof uri === 'undefined') {
98 uri = '';
99 }
100 uri = resolve(this.bookMark, uri);
101
102 if (!this.resourceCache[uri]) {
103 this.resourceCache[uri] = new Resource(this, uri);
104 }
105
106 return this.resourceCache[uri];
107
108 }
109
110 /**
111 * Returns a resource by its uri.
112 *
113 * This function doesn't do any HTTP requests. The uri is optional. If it's
114 * not specified, it will return the bookmark resource.
115 */
116 getResource(uri?: string): Resource {
117
118 return this.go(uri);
119
120 }
121
122 /**
123 * This function does an arbitrary request using the fetch API.
124 *
125 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/GlobalFetch}
126 */
127 fetch(input: string|Request, init?: RequestInit): Promise<Response> {
128
129 return this.fetchHelper.fetch(
130 input,
131 init
132 );
133
134 }
135
136 /**
137 * This function returns a representor constructor for a mime type.
138 *
139 * For example, given text/html, this function might return the constructor
140 * stored in representor/html.
141 */
142 getRepresentor(contentType: string): typeof Representor {
143
144 if (contentType.indexOf(';') !== -1) {
145 contentType = contentType.split(';')[0];
146 }
147 contentType = contentType.trim();
148 const result = this.contentTypes.find(item => {
149 return item.mime === contentType;
150 });
151
152 if (!result) {
153 throw new Error('Could not find a representor for contentType: ' + contentType);
154 }
155
156 switch (result.representor) {
157 case 'html' :
158 return HtmlRepresentor;
159 case 'hal' :
160 return HalRepresentor;
161 case 'jsonapi' :
162 return JsonApiRepresentor;
163 default :
164 throw new Error('Unknown representor: ' + result.representor);
165
166 }
167
168 }
169
170
171 /**
172 * Generates an accept header string, based on registered Resource Types.
173 */
174 getAcceptHeader(): string {
175
176 return this.contentTypes
177 .map( contentType => {
178 let item = contentType.mime;
179 if (contentType.q) { item += ';q=' + contentType.q; }
180 return item;
181 } )
182 .join(', ');
183
184 }
185
186 beforeRequest(request: Request): void {
187
188 const safeMethods = ['GET', 'HEAD', 'OPTIONS', 'PRI', 'PROPFIND', 'REPORT', 'SEARCH', 'TRACE'];
189 if (safeMethods.includes(request.method)) {
190 return;
191 }
192
193 if (request.url in this.resourceCache) {
194 // Clear cache
195 this.resourceCache[request.url].clearCache();
196 }
197 }
198
199}