import apisearch, {Query, Repository, Result} from "apisearch"; import {EventEmitter} from "events"; import {APISEARCH_DISPATCHER} from "./Constants"; import container from "./Container"; /** * Flux pattern store class */ class Store extends EventEmitter { private dirty: boolean; private readonly withHash: boolean = false; private readonly urlHash: string; private currentQuery: Query; private currentResult: Result; private currentVisibleResults: boolean; private readonly window: Window; private readonly isUnderIframe: boolean; private doNotCleanUrlHashAtFirst: boolean = false; private site: string; private device: string; private initialState: any; /** * @param coordinate * @param minScore * @param hash * @param userId * @param site * @param device * @param generateRandomSessionUUID * @param initialState */ constructor( coordinate: { lat: number, lon: number, }, minScore: number, hash: string, userId: string, site: string, device: string, generateRandomSessionUUID: boolean, initialState: any, ) { super(); this.dirty = true; this.site = site; this.device = device; this.initialState = initialState; const initialQuery = Store.loadInitialQuery(coordinate, userId, site, device); this.window = window.top; this.isUnderIframe = (window !== window.top); if ((typeof hash === "string")) { this.withHash = true; this.urlHash = (hash === "") ? "{}" : hash; if (this.urlHash.charAt(0) === "#") { this.urlHash = this.urlHash.substr(1); } } if (minScore) { initialQuery.setMinScore(minScore); } /** * Data received */ this.setEmptyResult(); this.currentVisibleResults = false; if (generateRandomSessionUUID) { initialQuery.setMetadataValue("session_uid", Store.createUID(16)); } this.setCurrentQuery(initialQuery); } /** * Is dirty * * @return {any} */ public isDirty(): boolean { return this.dirty; } /** * */ public getSite(): string { return this.site; } /** * */ public getDevice(): string { return this.device; } /** * Get current query * * @return {Query} */ public getCurrentQuery(): Query { return this.currentQuery; } /** * @param query */ public setCurrentQuery(query: Query) { this.currentQuery = query; } /** * Get current result * * @return {Result} */ public getCurrentResult(): Result { return this.currentResult; } /** * @param result */ public setCurrentResult(result: Result) { this.currentResult = result; } /** * */ public setEmptyResult() { this.currentResult = apisearch.createEmptyResult(); } /** * Get current result * * @return {boolean} */ public hasProperResult(): boolean { return this.currentResult.getTotalItems() > 0; } /** * Results are visible * * @return {boolean} */ public resultsAreVisible(): boolean { return this.currentVisibleResults; } /** * @param payload */ public updateApisearchSetup(payload: any) { this.currentQuery = payload.query; } /** * @param payload */ public renderInitialData(payload: any) { const { result, query, _ } = payload; this.dirty = false; this.currentResult = result; this.currentQuery = query; this.currentVisibleResults = query !== undefined; this.emit("render"); this.replaceUrl( query, result, this.currentVisibleResults, ); } /** * @param payload */ public renderFetchedData(payload: any) { const { result, query, visibleResults } = payload; this.dirty = false; this.currentResult = result; this.currentQuery = query; if (visibleResults !== undefined) { this.currentVisibleResults = visibleResults; } this.emit("render"); this.replaceUrl( query, result, visibleResults, ); } /** * Create an uid */ public static createUID(length) { let result = ""; const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; const charactersLength = characters.length; for ( let i = 0; i < length; i++ ) { result += characters.charAt(Math.floor(Math.random() * charactersLength)); } return result; } /** * @param environmentId * @param repository * @param loadQuery */ public fetchInitialQuery( environmentId: string, repository: Repository, loadQuery: boolean, ) { const dispatcher = container.get(`${APISEARCH_DISPATCHER}__${environmentId}`); this.currentQuery = loadQuery ? this.loadQuery(this.currentQuery) : this.currentQuery; dispatcher.dispatch("NORMALIZE_QUERY", { query: this.currentQuery, }); /** * In initial query, we must delete user */ const queryAsArray = this.currentQuery.toArray(); queryAsArray.user = null; repository .query(Query.createFromArray(queryAsArray)) .then((result) => { dispatcher.dispatch("RENDER_INITIAL_DATA", { query: this.currentQuery, result, }); }); } /** * @param coordinate * @param userId * @param site * @param device * * @private */ private static loadInitialQuery( coordinate: { lat: number, lon: number, }, userId: string, site: string, device: string, ): Query { const withCoordinate = ( coordinate && coordinate.lat !== undefined && coordinate.lon !== undefined ); const q: any = {}; if (withCoordinate) { q.coordinate = coordinate; } if (userId !== "") { q.user = {id: userId}; } if (q.metadata === undefined) { q.metadata = {device}; } if (site !== "") { q.metadata.site = site; } return Query.createFromArray(q); } /** * @param query */ private loadQuery(query: Query): Query { if (!this.withHash) { return query; } const queryAsObject = query.toArray(); let urlObject = {}; let didTakeQueryFromUrl = false; if (this.urlHash.match("q=.*") !== null) { const urlHashQuery = decodeURI(this.urlHash.slice(2)); urlObject = {q: urlHashQuery}; this.emit("fromUrlObject", urlObject, queryAsObject); didTakeQueryFromUrl = true; } else { try { urlObject = ( this.urlHash !== undefined && this.urlHash !== null && this.urlHash !== "" && this.urlHash !== "/" ) ? JSON.parse(decodeURI(this.urlHash)) : {}; if (Object.keys(urlObject).length > 0) { this.emit("fromUrlObject", urlObject, queryAsObject); didTakeQueryFromUrl = true; } } catch (e) { // Silent pass this.doNotCleanUrlHashAtFirst = true; } } if ( !didTakeQueryFromUrl && Object.keys(this.initialState).length > 0 ) { this.emit("fromUrlObject", this.initialState, queryAsObject); } return Query.createFromArray(queryAsObject); } /** * * @param query * @param result * @param visibleResults */ private replaceUrl( query: Query, result: Result, visibleResults: boolean|undefined, ) { if (!this.withHash) { return; } const queryAsObject = query.toArray(); const urlObject: any = {}; this.emit("toUrlObject", queryAsObject, urlObject); let objectAsJson; if ( Object.keys(urlObject).length === 1 && typeof urlObject.q !== "undefined" ) { objectAsJson = "q=" + urlObject.q; } else { objectAsJson = decodeURI(JSON.stringify(urlObject)); objectAsJson = (objectAsJson === "{}") ? "" : objectAsJson; objectAsJson = encodeURI(objectAsJson); } if (!this.isUnderIframe) { const path = window.location.href; const pathWithoutHash = path.split("#", 2)[0]; history.replaceState("", "", pathWithoutHash + "#" + objectAsJson); if (objectAsJson === "") { history.replaceState("", "", pathWithoutHash); } } else { if (!this.doNotCleanUrlHashAtFirst) { this.window.postMessage({ name: "apisearch_replace_hash", hash: objectAsJson, }, "*"); } this.doNotCleanUrlHashAtFirst = false; } } } export default Store;