proximiio-js-library 1.17.4 → 1.17.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -446,6 +446,15 @@ const map = new Proximiio.Map({
446
446
  // If set to true only features inside defined time range in metadata.dateStart and metadata.dateEnd will be shown
447
447
  useTimerangeData: false,
448
448
 
449
+ // If set to true, routing will avoid paths that are closed according to their working hours
450
+ // (properties._workingHoursEnabled + properties.workingHours), evaluated against the device's local time.
451
+ // Paths marked properties.available: false are always avoided, regardless of this option.
452
+ useWorkingHours: false,
453
+
454
+ // If set to true (together with useWorkingHours), routing to a destination POI that is currently
455
+ // closed according to its working hours will be refused instead of computed
456
+ excludeClosedPois: false,
457
+
449
458
  // If enabled we automatically send analytics from routing to our API, default: true
450
459
  sendAnalytics: true,
451
460
 
@@ -205,6 +205,8 @@ export interface Options {
205
205
  blockFeatureClickWhileRouting?: boolean;
206
206
  hiddenAmenities?: string[];
207
207
  useTimerangeData?: boolean;
208
+ useWorkingHours?: boolean;
209
+ excludeClosedPois?: boolean;
208
210
  sendAnalytics?: boolean;
209
211
  defaultFilter?: {
210
212
  key: string;
@@ -250,6 +250,8 @@ export class Map {
250
250
  routeWithDetails: true,
251
251
  blockFeatureClickWhileRouting: false,
252
252
  useTimerangeData: false,
253
+ useWorkingHours: false,
254
+ excludeClosedPois: false,
253
255
  sendAnalytics: true,
254
256
  autoLevelChange: false,
255
257
  autoRestartAnimationAfterFloorChange: false,
@@ -310,6 +312,7 @@ export class Map {
310
312
  this.throttledHandleFilterChange = throttle(this.handleFilterChange, 5000); // Adjust the delay as needed
311
313
  this.handleControllerError = (err) => {
312
314
  this.onMapFailedListener.next({ message: err.message ? err.message : JSON.stringify(err) });
315
+ return undefined;
313
316
  };
314
317
  this.InjectCSS = ({ id, css }) => {
315
318
  // Create the css
@@ -695,10 +698,19 @@ export class Map {
695
698
  });
696
699
  }
697
700
  }
701
+ else {
702
+ // Required data (places/floors/style) could not be loaded - e.g. offline with an empty
703
+ // cache. Without it the map is never created and no map-ready event is ever emitted, so
704
+ // emit an explicit failure instead of leaving the consumer stuck on a loading screen.
705
+ const missing = [!places && 'places', !floors && 'floors', !style && 'style'].filter(Boolean).join(', ');
706
+ this.onMapFailedListener.next({
707
+ message: `Map initialization aborted: required data (${missing}) could not be loaded.`,
708
+ });
709
+ }
698
710
  });
699
711
  }
700
712
  fetch() {
701
- var _a;
713
+ var _a, _b, _c;
702
714
  return __awaiter(this, void 0, void 0, function* () {
703
715
  const useBundle = !!this.defaultOptions.bundleUrl;
704
716
  const features = useBundle
@@ -752,7 +764,7 @@ export class Map {
752
764
  const ads = useBundle
753
765
  ? yield getAdsBundle({ bundleUrl: this.defaultOptions.bundleUrl }).catch((error) => this.handleControllerError(error))
754
766
  : yield getAds().catch((error) => this.handleControllerError(error));
755
- if (features && kiosks && ads) {
767
+ if (features) {
756
768
  const optimizedFeatures = new FeatureCollection({
757
769
  features: optimizeFeatures(features.modifiedFeatures.features),
758
770
  });
@@ -762,11 +774,22 @@ export class Map {
762
774
  this.routingSource.setLevelChangers(levelChangers);
763
775
  this.routingSource.setFloors(this.state.floors);
764
776
  this.geojsonSource.fetch(optimizedFeatures);
765
- this.routingSource.routing.setData(features.originalFeatures);
777
+ this.routingSource.routing.setData(features.originalFeatures, {
778
+ useWorkingHours: this.defaultOptions.useWorkingHours,
779
+ excludeClosedPois: this.defaultOptions.excludeClosedPois,
780
+ });
766
781
  this.prepareStyle(this.state.style);
767
- this.state = Object.assign(Object.assign({}, this.state), { initializing: false, kiosks: kiosks.data, amenities, features: features.modifiedFeatures, ads: ads.data, optimizedFeatures, allFeatures: new FeatureCollection(features.modifiedFeatures), levelChangers: new FeatureCollection({ features: levelChangers }), zoom: this.defaultOptions.zoomLevel ? this.defaultOptions.zoomLevel : (_a = this.defaultOptions.mapboxOptions) === null || _a === void 0 ? void 0 : _a.zoom });
782
+ this.state = Object.assign(Object.assign({}, this.state), { initializing: false, kiosks: (_a = kiosks === null || kiosks === void 0 ? void 0 : kiosks.data) !== null && _a !== void 0 ? _a : [], amenities, features: features.modifiedFeatures, ads: (_b = ads === null || ads === void 0 ? void 0 : ads.data) !== null && _b !== void 0 ? _b : [], optimizedFeatures, allFeatures: new FeatureCollection(features.modifiedFeatures), levelChangers: new FeatureCollection({ features: levelChangers }), zoom: this.defaultOptions.zoomLevel ? this.defaultOptions.zoomLevel : (_c = this.defaultOptions.mapboxOptions) === null || _c === void 0 ? void 0 : _c.zoom });
768
783
  this.onDataFetchedListener.next(true);
769
784
  }
785
+ else {
786
+ // Features are required to build the map data; kiosks/ads are optional and default to empty
787
+ // above so a single missing optional bundle (e.g. offline) no longer blocks the whole step.
788
+ // If features themselves failed there is nothing to render - surface it explicitly.
789
+ this.onMapFailedListener.next({
790
+ message: `Map data could not be loaded: features are unavailable.`,
791
+ });
792
+ }
770
793
  });
771
794
  }
772
795
  onMapReady(e) {
@@ -1,14 +1,24 @@
1
1
  import Feature from '../../models/feature';
2
2
  import { FeatureCollection } from '@turf/helpers';
3
3
  import { WayfindingConfigModel } from '../../models/wayfinding';
4
+ export interface RoutingWorkingHoursOptions {
5
+ useWorkingHours?: boolean;
6
+ excludeClosedPois?: boolean;
7
+ /** Override "now" for previewing a route at a different time. Defaults to the device's current time. */
8
+ datetime?: Date;
9
+ }
4
10
  export default class Routing {
5
11
  data: FeatureCollection;
12
+ rawData: FeatureCollection;
6
13
  wayfinding: any;
7
14
  forceFloorLevel: number;
8
15
  routeWithDetails: boolean;
9
16
  config: WayfindingConfigModel;
17
+ workingHoursOptions: RoutingWorkingHoursOptions;
10
18
  constructor();
11
- setData(collection: FeatureCollection): void;
19
+ private filterRoutableFeatures;
20
+ setData(collection: FeatureCollection, options?: RoutingWorkingHoursOptions): void;
21
+ setWorkingHoursOptions(options: RoutingWorkingHoursOptions): void;
12
22
  toggleOnlyAccessible(onlyAccessible: any): void;
13
23
  setConfig(config: WayfindingConfigModel): void;
14
24
  route({ start, finish, stops, stepsNavigation, priorityEntrance, }: {
@@ -18,6 +28,9 @@ export default class Routing {
18
28
  stepsNavigation?: 'disabled' | 'simple' | 'simple-levelChangers' | 'full' | 'full-levelChangers' | 'landmark' | 'landmark-levelChangers';
19
29
  priorityEntrance?: Feature;
20
30
  }): {
31
+ destinationOpen: boolean;
32
+ } | {
33
+ destinationOpen: boolean;
21
34
  paths: any;
22
35
  points: any;
23
36
  route: {};
@@ -12,14 +12,33 @@ import { Wayfinding } from '../../../lib/assets/wayfinding';
12
12
  import Feature from '../../models/feature';
13
13
  import { lineString, point } from '@turf/helpers';
14
14
  import osrmTextInstructionsPackage from 'osrm-text-instructions';
15
+ import { isOpenAt, nowClientTime } from './workingHours';
15
16
  const version = 'v5';
16
17
  const osrmTextInstructions = osrmTextInstructionsPackage(version);
17
18
  export default class Routing {
18
19
  constructor() {
20
+ this.workingHoursOptions = {};
19
21
  this.data = {};
20
22
  }
21
- setData(collection) {
22
- this.data = collection;
23
+ filterRoutableFeatures(collection) {
24
+ const time = nowClientTime(this.workingHoursOptions.datetime);
25
+ const features = (collection.features || []).filter((f) => {
26
+ var _a;
27
+ if (((_a = f.properties) === null || _a === void 0 ? void 0 : _a.class) !== 'path')
28
+ return true;
29
+ // Unavailable paths are always avoided, independently of any option.
30
+ if (f.properties.available === false)
31
+ return false;
32
+ if (this.workingHoursOptions.useWorkingHours && !isOpenAt(f.properties, time))
33
+ return false;
34
+ return true;
35
+ });
36
+ return Object.assign(Object.assign({}, collection), { features });
37
+ }
38
+ setData(collection, options) {
39
+ this.rawData = collection;
40
+ this.workingHoursOptions = Object.assign(Object.assign({}, this.workingHoursOptions), options);
41
+ this.data = this.filterRoutableFeatures(this.rawData);
23
42
  this.wayfinding = new Wayfinding(this.data);
24
43
  this.wayfinding.preprocess();
25
44
  // pathfinding.load(neighbourList, wallOffsets);
@@ -29,6 +48,14 @@ export default class Routing {
29
48
  // avoidRevolvingDoors: true
30
49
  // });
31
50
  }
51
+ setWorkingHoursOptions(options) {
52
+ this.workingHoursOptions = Object.assign(Object.assign({}, this.workingHoursOptions), options);
53
+ if (this.rawData) {
54
+ this.data = this.filterRoutableFeatures(this.rawData);
55
+ this.wayfinding = new Wayfinding(this.data);
56
+ this.wayfinding.preprocess();
57
+ }
58
+ }
32
59
  toggleOnlyAccessible(onlyAccessible) {
33
60
  if (onlyAccessible) {
34
61
  this.wayfinding.setConfiguration({
@@ -62,6 +89,11 @@ export default class Routing {
62
89
  this.wayfinding.setConfiguration(config);
63
90
  }
64
91
  route({ start, finish, stops, stepsNavigation = 'full', priorityEntrance, }) {
92
+ const time = nowClientTime(this.workingHoursOptions.datetime);
93
+ const destinationOpen = this.workingHoursOptions.useWorkingHours && finish ? isOpenAt(finish.properties, time) : undefined;
94
+ if (destinationOpen === false && this.workingHoursOptions.excludeClosedPois) {
95
+ return { destinationOpen: false };
96
+ }
65
97
  const isMultipoint = stops && stops.length > 1;
66
98
  let points = null;
67
99
  let details = null;
@@ -397,7 +429,10 @@ export default class Routing {
397
429
  }
398
430
  levelPoints[point.properties.level].push(point);
399
431
  });
400
- return { paths, points, route: {}, fullPath: {}, levelPaths, levelPoints, details };
432
+ return Object.assign({ paths,
433
+ points, route: {}, fullPath: {}, levelPaths,
434
+ levelPoints,
435
+ details }, (destinationOpen !== undefined && { destinationOpen }));
401
436
  }
402
437
  cityRoute({ start, finish, language = 'en' }) {
403
438
  return __awaiter(this, void 0, void 0, function* () {
@@ -1,6 +1,6 @@
1
1
  import DataSource from './data_source';
2
2
  import Feature from '../../../models/feature';
3
- import Routing from '../routing';
3
+ import Routing, { RoutingWorkingHoursOptions } from '../routing';
4
4
  import { GuidanceStep, WayfindingConfigModel } from '../../../models/wayfinding';
5
5
  import { FloorModel } from '../../../models/floor';
6
6
  interface ChangeContainer {
@@ -45,6 +45,7 @@ export default class RoutingSource extends DataSource {
45
45
  constructor();
46
46
  toggleAccessible(value: any): void;
47
47
  setConfig(config: WayfindingConfigModel): void;
48
+ setWorkingHoursOptions(options: RoutingWorkingHoursOptions): void;
48
49
  setNavigationType(type: 'mall' | 'city' | 'combined'): void;
49
50
  setStepsNavigation(value: 'disabled' | 'simple' | 'simple-levelChangers' | 'full' | 'full-levelChangers' | 'landmark' | 'landmark-levelChangers'): void;
50
51
  setInitialBearing(initialBearing: number): void;
@@ -28,6 +28,9 @@ export default class RoutingSource extends DataSource {
28
28
  setConfig(config) {
29
29
  this.routing.setConfig(config);
30
30
  }
31
+ setWorkingHoursOptions(options) {
32
+ this.routing.setWorkingHoursOptions(options);
33
+ }
31
34
  setNavigationType(type) {
32
35
  this.navigationType = type;
33
36
  }
@@ -108,6 +111,10 @@ export default class RoutingSource extends DataSource {
108
111
  }
109
112
  }
110
113
  // @ts-ignore
114
+ if ((route === null || route === void 0 ? void 0 : route.destinationOpen) === false) {
115
+ this.notify('destination-closed');
116
+ }
117
+ // @ts-ignore
111
118
  let paths = route === null || route === void 0 ? void 0 : route.paths;
112
119
  // @ts-ignore
113
120
  this.route = route === null || route === void 0 ? void 0 : route.paths;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Working-hours support for client-side routing.
3
+ *
4
+ * Features may carry on `properties`:
5
+ * _workingHoursEnabled: true
6
+ * workingHours: [ ["14:00","22:00"], ... ] // 7 entries, index 0 = SUNDAY
7
+ *
8
+ * This runs on the end user's own device, so "now" is simply the browser's
9
+ * local clock — unlike a server serving clients across timezones, there is
10
+ * no offset to strip: `Date#getDay/getHours/getMinutes` already reflect the
11
+ * device's local wall-clock time.
12
+ *
13
+ * Malformed or missing data fails OPEN: bad venue data must never make a
14
+ * feature unroutable.
15
+ */
16
+ export type ClientTime = {
17
+ /** 0 = Sunday … 6 = Saturday, matching the workingHours array order. */
18
+ weekday: number;
19
+ /** Minutes since local midnight, 0–1439. */
20
+ minutes: number;
21
+ };
22
+ /**
23
+ * Derive weekday + wall-clock minutes from a local Date (defaults to now).
24
+ */
25
+ export declare function nowClientTime(date?: Date): ClientTime;
26
+ /**
27
+ * Is a feature open at the given client time?
28
+ *
29
+ * - Features without `_workingHoursEnabled: true` (or with malformed
30
+ * `workingHours`) are always open.
31
+ * - A window with `from < to` covers [from, to) that day.
32
+ * - An overnight window (`from > to`, e.g. 22:00–06:00) covers [from, 24:00)
33
+ * that day plus [00:00, to) of the NEXT day — so we also check whether
34
+ * yesterday's window spills past midnight into now.
35
+ * - `from === to` means closed all day (zero-length window).
36
+ */
37
+ export declare function isOpenAt(properties: any, time: ClientTime): boolean;
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Working-hours support for client-side routing.
3
+ *
4
+ * Features may carry on `properties`:
5
+ * _workingHoursEnabled: true
6
+ * workingHours: [ ["14:00","22:00"], ... ] // 7 entries, index 0 = SUNDAY
7
+ *
8
+ * This runs on the end user's own device, so "now" is simply the browser's
9
+ * local clock — unlike a server serving clients across timezones, there is
10
+ * no offset to strip: `Date#getDay/getHours/getMinutes` already reflect the
11
+ * device's local wall-clock time.
12
+ *
13
+ * Malformed or missing data fails OPEN: bad venue data must never make a
14
+ * feature unroutable.
15
+ */
16
+ const HH_MM = /^(\d{1,2}):(\d{2})$/;
17
+ const MINUTES_PER_DAY = 24 * 60;
18
+ /**
19
+ * Derive weekday + wall-clock minutes from a local Date (defaults to now).
20
+ */
21
+ export function nowClientTime(date = new Date()) {
22
+ return { weekday: date.getDay(), minutes: date.getHours() * 60 + date.getMinutes() };
23
+ }
24
+ function toMinutes(value) {
25
+ if (typeof value !== 'string')
26
+ return undefined;
27
+ const m = HH_MM.exec(value.trim());
28
+ if (!m)
29
+ return undefined;
30
+ const hours = Number(m[1]);
31
+ const minutes = Number(m[2]);
32
+ if (hours > 24 || minutes > 59)
33
+ return undefined;
34
+ return Math.min(hours * 60 + minutes, MINUTES_PER_DAY);
35
+ }
36
+ function dayWindow(workingHours, weekday) {
37
+ if (!Array.isArray(workingHours) || workingHours.length !== 7)
38
+ return undefined;
39
+ const day = workingHours[weekday];
40
+ if (!Array.isArray(day) || day.length < 2)
41
+ return undefined;
42
+ const from = toMinutes(day[0]);
43
+ const to = toMinutes(day[1]);
44
+ if (from === undefined || to === undefined)
45
+ return undefined;
46
+ return { from, to };
47
+ }
48
+ /**
49
+ * Is a feature open at the given client time?
50
+ *
51
+ * - Features without `_workingHoursEnabled: true` (or with malformed
52
+ * `workingHours`) are always open.
53
+ * - A window with `from < to` covers [from, to) that day.
54
+ * - An overnight window (`from > to`, e.g. 22:00–06:00) covers [from, 24:00)
55
+ * that day plus [00:00, to) of the NEXT day — so we also check whether
56
+ * yesterday's window spills past midnight into now.
57
+ * - `from === to` means closed all day (zero-length window).
58
+ */
59
+ export function isOpenAt(properties, time) {
60
+ if ((properties === null || properties === void 0 ? void 0 : properties._workingHoursEnabled) !== true)
61
+ return true;
62
+ const workingHours = properties.workingHours;
63
+ if (!Array.isArray(workingHours) || workingHours.length !== 7)
64
+ return true;
65
+ const today = dayWindow(workingHours, time.weekday);
66
+ if (today === undefined)
67
+ return true;
68
+ if (today.from < today.to && time.minutes >= today.from && time.minutes < today.to)
69
+ return true;
70
+ if (today.from > today.to && time.minutes >= today.from)
71
+ return true;
72
+ const yesterday = dayWindow(workingHours, (time.weekday + 6) % 7);
73
+ if (yesterday !== undefined && yesterday.from > yesterday.to && time.minutes < yesterday.to)
74
+ return true;
75
+ return false;
76
+ }
@@ -35,7 +35,7 @@ export const login = (email, password) => __awaiter(void 0, void 0, void 0, func
35
35
  const loginRes = yield axios.post(`core_auth/login`, authData);
36
36
  if (loginRes.data) {
37
37
  console.info(`Logged in successfully as ${loginRes.data.user.email}`);
38
- axios.defaults.headers.common.Authorization = loginRes.data.token;
38
+ axios.defaults.headers.common.Authorization = `Bearer ${loginRes.data.token}`;
39
39
  loggedUser = loginRes;
40
40
  }
41
41
  return loginRes;
@@ -63,7 +63,7 @@ export const loginWithToken = (token) => __awaiter(void 0, void 0, void 0, funct
63
63
  if (!token) {
64
64
  throw new Error(`Please provide your token`);
65
65
  }
66
- axios.defaults.headers.common.Authorization = token;
66
+ axios.defaults.headers.common.Authorization = `Bearer ${token}`;
67
67
  const currentUser = yield axios.get(`core/current_user`);
68
68
  if (currentUser.data) {
69
69
  console.info(`Logged in successfully as ${currentUser.data.email}`);
@@ -86,7 +86,7 @@ export const loginWithToken = (token) => __awaiter(void 0, void 0, void 0, funct
86
86
  * });
87
87
  */
88
88
  export const setToken = (token) => __awaiter(void 0, void 0, void 0, function* () {
89
- axios.defaults.headers.common.Authorization = token;
89
+ axios.defaults.headers.common.Authorization = `Bearer ${token}`;
90
90
  return `Token set successfully: ${token}`;
91
91
  });
92
92
  /**