2022-08-18 18:38:42 +03:00
|
|
|
|
/**
|
2022-08-19 23:39:18 +03:00
|
|
|
|
* @typedef {object} AttributionResource
|
|
|
|
|
* @prop {string|null} id
|
2022-08-22 18:16:18 +03:00
|
|
|
|
* @prop {string|null} url (absolute URL)
|
2023-06-01 11:18:11 +03:00
|
|
|
|
* @prop {'page'|'post'|'author'|'tag'|'url'|null} type
|
2022-08-19 23:39:18 +03:00
|
|
|
|
* @prop {string|null} title
|
2022-09-23 18:19:51 +03:00
|
|
|
|
* @prop {string|null} referrerSource
|
|
|
|
|
* @prop {string|null} referrerMedium
|
|
|
|
|
* @prop {string|null} referrerUrl
|
2022-08-18 18:38:42 +03:00
|
|
|
|
*/
|
|
|
|
|
|
2022-08-19 23:39:18 +03:00
|
|
|
|
class Attribution {
|
2023-05-02 23:43:47 +03:00
|
|
|
|
/** @type {import('./UrlTranslator')} */
|
2022-08-19 23:39:18 +03:00
|
|
|
|
#urlTranslator;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param {object} data
|
|
|
|
|
* @param {string|null} [data.id]
|
2022-08-22 18:16:18 +03:00
|
|
|
|
* @param {string|null} [data.url] Relative to subdirectory
|
2023-06-01 11:18:11 +03:00
|
|
|
|
* @param {'page'|'post'|'author'|'tag'|'url'|null} [data.type]
|
2022-09-27 22:28:06 +03:00
|
|
|
|
* @param {string|null} [data.referrerSource]
|
|
|
|
|
* @param {string|null} [data.referrerMedium]
|
|
|
|
|
* @param {string|null} [data.referrerUrl]
|
2022-08-19 23:39:18 +03:00
|
|
|
|
*/
|
2022-09-19 09:45:52 +03:00
|
|
|
|
constructor({
|
2022-09-27 22:28:06 +03:00
|
|
|
|
id, url, type, referrerSource, referrerMedium, referrerUrl
|
2022-09-19 09:45:52 +03:00
|
|
|
|
}, {urlTranslator}) {
|
2022-08-19 23:39:18 +03:00
|
|
|
|
this.id = id;
|
|
|
|
|
this.url = url;
|
|
|
|
|
this.type = type;
|
2022-09-27 22:28:06 +03:00
|
|
|
|
this.referrerSource = referrerSource;
|
|
|
|
|
this.referrerMedium = referrerMedium;
|
|
|
|
|
this.referrerUrl = referrerUrl;
|
2022-08-19 23:39:18 +03:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @private
|
|
|
|
|
*/
|
|
|
|
|
this.#urlTranslator = urlTranslator;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2022-08-24 17:11:25 +03:00
|
|
|
|
* Converts the instance to a parsed instance with more information about the resource included.
|
2022-08-19 23:39:18 +03:00
|
|
|
|
* It does:
|
2022-08-24 17:11:25 +03:00
|
|
|
|
* - Uses the passed model and adds a title to the attribution
|
|
|
|
|
* - If the resource exists and has a new url, it updates the url if possible
|
2022-08-19 23:39:18 +03:00
|
|
|
|
* - Returns an absolute URL instead of a relative one
|
2022-08-24 17:11:25 +03:00
|
|
|
|
* @param {Object|null} [model] The Post/User/Tag model of the resource associated with this attribution
|
|
|
|
|
* @returns {AttributionResource}
|
2022-08-19 23:39:18 +03:00
|
|
|
|
*/
|
2022-08-24 17:11:25 +03:00
|
|
|
|
getResource(model) {
|
|
|
|
|
if (!this.id || this.type === 'url' || !this.type || !model) {
|
2023-06-01 11:18:11 +03:00
|
|
|
|
if (!this.url) {
|
|
|
|
|
return {
|
|
|
|
|
id: null,
|
|
|
|
|
type: null,
|
|
|
|
|
url: null,
|
|
|
|
|
title: null,
|
|
|
|
|
referrerSource: this.referrerSource,
|
|
|
|
|
referrerMedium: this.referrerMedium,
|
|
|
|
|
referrerUrl: this.referrerUrl
|
|
|
|
|
};
|
|
|
|
|
}
|
2022-08-19 23:39:18 +03:00
|
|
|
|
return {
|
|
|
|
|
id: null,
|
|
|
|
|
type: 'url',
|
2022-08-22 18:16:18 +03:00
|
|
|
|
url: this.#urlTranslator.relativeToAbsolute(this.url),
|
2022-09-23 18:19:51 +03:00
|
|
|
|
title: this.#urlTranslator.getUrlTitle(this.url),
|
2022-09-27 22:28:06 +03:00
|
|
|
|
referrerSource: this.referrerSource,
|
|
|
|
|
referrerMedium: this.referrerMedium,
|
|
|
|
|
referrerUrl: this.referrerUrl
|
2022-08-19 23:39:18 +03:00
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-24 17:11:25 +03:00
|
|
|
|
const updatedUrl = this.#urlTranslator.getUrlByResourceId(this.id, {absolute: true});
|
2022-08-24 17:11:25 +03:00
|
|
|
|
|
2022-08-24 18:01:47 +03:00
|
|
|
|
return {
|
2022-08-24 17:11:25 +03:00
|
|
|
|
id: model.id,
|
|
|
|
|
type: this.type,
|
|
|
|
|
url: updatedUrl,
|
2022-09-23 18:19:51 +03:00
|
|
|
|
title: model.get('title') ?? model.get('name') ?? this.#urlTranslator.getUrlTitle(this.url),
|
2022-09-27 22:28:06 +03:00
|
|
|
|
referrerSource: this.referrerSource,
|
|
|
|
|
referrerMedium: this.referrerMedium,
|
|
|
|
|
referrerUrl: this.referrerUrl
|
2022-08-24 18:01:47 +03:00
|
|
|
|
};
|
2022-08-24 17:11:25 +03:00
|
|
|
|
}
|
2022-08-24 17:11:25 +03:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Same as getResource, but fetches the model by ID instead of passing it as a parameter
|
|
|
|
|
*/
|
|
|
|
|
async fetchResource() {
|
|
|
|
|
if (!this.id || this.type === 'url' || !this.type) {
|
|
|
|
|
// No fetch required
|
|
|
|
|
return this.getResource();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fetch model
|
2024-05-07 12:08:56 +03:00
|
|
|
|
const model = await this.#urlTranslator.getResourceById(this.id, this.type);
|
2022-08-24 17:11:25 +03:00
|
|
|
|
return this.getResource(model);
|
|
|
|
|
}
|
2022-08-19 23:39:18 +03:00
|
|
|
|
}
|
|
|
|
|
|
2022-08-18 18:38:42 +03:00
|
|
|
|
/**
|
|
|
|
|
* Convert a UrlHistory to an attribution object
|
|
|
|
|
*/
|
|
|
|
|
class AttributionBuilder {
|
2023-05-02 23:43:47 +03:00
|
|
|
|
/** @type {import('./UrlTranslator')} */
|
2022-09-14 22:50:54 +03:00
|
|
|
|
urlTranslator;
|
2023-05-02 23:43:47 +03:00
|
|
|
|
/** @type {import('./ReferrerTranslator')} */
|
2022-09-27 22:28:06 +03:00
|
|
|
|
referrerTranslator;
|
2022-09-14 22:50:54 +03:00
|
|
|
|
|
2022-08-18 18:38:42 +03:00
|
|
|
|
/**
|
|
|
|
|
*/
|
2022-09-19 09:45:52 +03:00
|
|
|
|
constructor({urlTranslator, referrerTranslator}) {
|
2022-08-18 18:38:42 +03:00
|
|
|
|
this.urlTranslator = urlTranslator;
|
2022-09-19 09:45:52 +03:00
|
|
|
|
this.referrerTranslator = referrerTranslator;
|
2022-08-18 18:38:42 +03:00
|
|
|
|
}
|
|
|
|
|
|
2022-08-19 23:39:18 +03:00
|
|
|
|
/**
|
|
|
|
|
* Creates an Attribution object with the dependencies injected
|
|
|
|
|
*/
|
2022-09-27 22:28:06 +03:00
|
|
|
|
build({id, url, type, referrerSource, referrerMedium, referrerUrl}) {
|
2022-08-19 23:39:18 +03:00
|
|
|
|
return new Attribution({
|
|
|
|
|
id,
|
|
|
|
|
url,
|
2022-09-19 09:45:52 +03:00
|
|
|
|
type,
|
2022-09-27 22:28:06 +03:00
|
|
|
|
referrerSource,
|
|
|
|
|
referrerMedium,
|
|
|
|
|
referrerUrl
|
2022-08-19 23:39:18 +03:00
|
|
|
|
}, {urlTranslator: this.urlTranslator});
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-18 18:38:42 +03:00
|
|
|
|
/**
|
|
|
|
|
* Last Post Algorithm™️
|
2023-05-02 23:43:47 +03:00
|
|
|
|
* @param {import('./UrlHistory').UrlHistoryArray} history
|
2022-09-14 22:50:54 +03:00
|
|
|
|
* @returns {Promise<Attribution>}
|
2022-08-18 18:38:42 +03:00
|
|
|
|
*/
|
2022-09-14 22:50:54 +03:00
|
|
|
|
async getAttribution(history) {
|
2022-08-18 18:38:42 +03:00
|
|
|
|
if (history.length === 0) {
|
2022-08-19 23:39:18 +03:00
|
|
|
|
return this.build({
|
2022-08-18 18:38:42 +03:00
|
|
|
|
id: null,
|
|
|
|
|
url: null,
|
2022-09-19 09:45:52 +03:00
|
|
|
|
type: null,
|
2022-09-27 22:28:06 +03:00
|
|
|
|
referrerSource: null,
|
|
|
|
|
referrerMedium: null,
|
|
|
|
|
referrerUrl: null
|
2022-08-19 23:39:18 +03:00
|
|
|
|
});
|
2022-08-18 18:38:42 +03:00
|
|
|
|
}
|
|
|
|
|
|
2022-09-19 09:45:52 +03:00
|
|
|
|
const referrerData = this.referrerTranslator.getReferrerDetails(history) || {
|
2022-09-27 22:28:06 +03:00
|
|
|
|
referrerSource: null,
|
|
|
|
|
referrerMedium: null,
|
|
|
|
|
referrerUrl: null
|
2022-09-19 09:45:52 +03:00
|
|
|
|
};
|
2022-09-14 22:50:54 +03:00
|
|
|
|
|
|
|
|
|
// Start at the end. Return the first post we find
|
|
|
|
|
const resources = [];
|
2022-09-19 09:45:52 +03:00
|
|
|
|
|
|
|
|
|
// Note: history iterator is ordered from recent to oldest!
|
2022-08-22 18:16:18 +03:00
|
|
|
|
for (const item of history) {
|
2022-09-14 22:50:54 +03:00
|
|
|
|
const resource = await this.urlTranslator.getResourceDetails(item);
|
2022-08-22 18:16:18 +03:00
|
|
|
|
|
2022-09-14 22:50:54 +03:00
|
|
|
|
if (resource && resource.type === 'post') {
|
2022-09-19 09:45:52 +03:00
|
|
|
|
return this.build({
|
|
|
|
|
...resource,
|
|
|
|
|
...referrerData
|
|
|
|
|
});
|
2022-09-14 22:50:54 +03:00
|
|
|
|
}
|
2022-08-18 18:38:42 +03:00
|
|
|
|
|
2022-09-14 22:50:54 +03:00
|
|
|
|
// Store to avoid that we need to look it up again
|
|
|
|
|
if (resource) {
|
|
|
|
|
resources.push(resource);
|
2022-08-18 18:38:42 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// No post found?
|
2022-09-14 22:50:54 +03:00
|
|
|
|
// Return first with an id (page, tag, author)
|
|
|
|
|
for (const resource of resources) {
|
|
|
|
|
if (resource.id) {
|
2022-09-19 09:45:52 +03:00
|
|
|
|
return this.build({
|
|
|
|
|
...resource,
|
|
|
|
|
...referrerData
|
|
|
|
|
});
|
2022-08-18 18:38:42 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-14 22:50:54 +03:00
|
|
|
|
// No post/page/tag/author found?
|
|
|
|
|
// Return the last path that was visited
|
|
|
|
|
if (resources.length > 0) {
|
2022-09-19 09:45:52 +03:00
|
|
|
|
return this.build({
|
|
|
|
|
...referrerData,
|
|
|
|
|
...resources[0]
|
|
|
|
|
});
|
2022-09-14 22:50:54 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We only have history items without a path that have invalid ids
|
2022-08-19 23:39:18 +03:00
|
|
|
|
return this.build({
|
2022-09-19 09:45:52 +03:00
|
|
|
|
...referrerData,
|
2022-08-18 18:38:42 +03:00
|
|
|
|
id: null,
|
2022-09-14 22:50:54 +03:00
|
|
|
|
url: null,
|
|
|
|
|
type: null
|
2022-08-19 23:39:18 +03:00
|
|
|
|
});
|
2022-08-18 18:38:42 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
module.exports = AttributionBuilder;
|