diff --git a/src/follower.ts b/src/follower.ts index b0179e42..45895de1 100644 --- a/src/follower.ts +++ b/src/follower.ts @@ -7,12 +7,18 @@ import { LinkVariables } from './types'; abstract class Follower implements PromiseLike { protected prefetchEnabled: boolean; + protected preferPushEnabled: boolean; preFetch(): this { this.prefetchEnabled = true; return this; } + preferPush(): this { + this.preferPushEnabled = true; + return this; + } + abstract then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): PromiseLike; abstract catch(onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): PromiseLike; @@ -103,6 +109,9 @@ export class FollowerOne extends Follower> { private async fetchLinkedResource(): Promise> { const resource = await this.resource; + if (this.preferPushEnabled) { + resource.addNextRefreshHeader('Prefer-Push', this.rel); + } const link = await resource.link(this.rel); let href; @@ -172,6 +181,10 @@ export class FollowerMany extends Follower>> { private async fetchLinkedResources(): Promise>> { const resource = await this.resource; + if (this.preferPushEnabled) { + resource.addNextRefreshHeader('Prefer-Push', this.rel); + } + const links = await resource.links(this.rel); let href; diff --git a/src/resource.ts b/src/resource.ts index d27079ce..b5163f72 100644 --- a/src/resource.ts +++ b/src/resource.ts @@ -43,18 +43,10 @@ export default class Resource> { * be used to set this value. * * It's also possible for resources to get a mimetype through a link. - * - * If the mimetype was "null" when doing the request, the chosen mimetype - * will come from the first item in Client.resourceTypes */ contentType: string | null; - private inFlightRefresh: Promise | null = null; - - /** - * A list of rels that should be added to a Prefer-Push header. - */ - private preferPushRels: Set; + private inFlightRefresh: Promise | null = null; constructor(client: Ketting, uri: string, contentType: string | null = null) { @@ -62,7 +54,7 @@ export default class Resource> { this.uri = uri; this.repr = null; this.contentType = contentType; - this.preferPushRels = new Set(); + this.nextRefreshHeaders = {}; } @@ -171,70 +163,58 @@ export default class Resource> { * This function will return the a parsed JSON object, like the get * function does. */ - async refresh(): Promise { + refresh(): Promise { - let response: Response; - let body: string; + if (this.inFlightRefresh) { + return this.inFlightRefresh; + } - // If 2 systems request a 'refresh' at the exact same time, this mechanism - // will coalesc them into one. - if (!this.inFlightRefresh) { + const refreshFunc = async (): Promise => { const headers: { [name: string]: string } = { - Accept: this.contentType ? this.contentType : this.client.representorHelper.getAcceptHeader() + Accept: this.contentType ? this.contentType : this.client.representorHelper.getAcceptHeader(), + ...this.nextRefreshHeaders, }; - if (this.preferPushRels.size > 0) { - headers['Prefer-Push'] = Array.from(this.preferPushRels).join(' '); - headers.Prefer = 'transclude="' + Array.from(this.preferPushRels).join(';') + '"'; - } - - this.inFlightRefresh = this.fetchAndThrow({ + const response = await this.fetchAndThrow({ method: 'GET' , headers - }).then( result1 => { - response = result1; - return response.text(); - }) - .then( result2 => { - body = result2; - return [response, body]; }); - try { - await this.inFlightRefresh; - } finally { - this.inFlightRefresh = null; - } + this.nextRefreshHeaders = {}; + this.inFlightRefresh = null; - } else { - // Something else asked for refresh, so we piggypack on it. - [response, body] = await this.inFlightRefresh; + const body = await response.text(); - } + this.repr = this.client.representorHelper.createFromResponse( + this.uri, + response, + body, + ) as any as Representator; - this.repr = this.client.representorHelper.createFromResponse( - this.uri, - response!, - body!, - ) as any as Representator; + if (!this.contentType) { + this.contentType = this.repr.contentType; + } - if (!this.contentType) { - this.contentType = this.repr.contentType; - } + for (const [subUri, subBody] of Object.entries(this.repr.getEmbedded())) { + const subResource = this.go(subUri); + subResource.repr = this.client.representorHelper.create( + subUri, + this.repr.contentType, + null, + new Map(), + ); + subResource.repr.setBody(subBody); + } - for (const [subUri, subBody] of Object.entries(this.repr.getEmbedded())) { - const subResource = this.go(subUri); - subResource.repr = this.client.representorHelper.create( - subUri, - this.repr.contentType, - null, - new Map(), - ); - subResource.repr.setBody(subBody); - } + return this.repr.getBody(); + + }; - return this.repr.getBody(); + const refreshResult = refreshFunc(); + this.inFlightRefresh = refreshResult; + + return refreshResult; } @@ -247,11 +227,6 @@ export default class Resource> { async links(rel?: string): Promise { const r = await this.representation(); - - // After we got a representation, it no longer makes sense to remember - // the rels we want to add to Prefer-Push. - this.preferPushRels = new Set(); - return r.getLinks(rel); } @@ -269,11 +244,6 @@ export default class Resource> { async link(rel: string): Promise { const r = await this.representation(); - - // After we got a representation, it no longer makes sense to remember - // the rels we want to add to Prefer-Push. - this.preferPushRels = new Set(); - return r.getLink(rel); } @@ -437,4 +407,23 @@ export default class Resource> { } + + /** + * A set of HTTP headers that will be sent along with the next call to Refresh() + */ + private nextRefreshHeaders: { [name: string]: string }; + + /** + * When a HTTP header gets added here, it will automatically get sent along + * to the next call to refresh(). + * + * This means that the next time a GET request is done, these headers will be + * added. This list gets cleared after the GET request. + */ + addNextRefreshHeader(name: string, value: string): void { + + this.nextRefreshHeaders[name] = value; + + } + }