@multisetai/vps 1.0.6 → 1.0.7-beta.1

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
@@ -24,11 +24,13 @@ npm install @multisetai/vps three
24
24
 
25
25
  ### Runtime Requirements
26
26
 
27
- - **HTTPS**: WebXR requires a secure context. Use HTTPS in production or `https://localhost` for local development.
28
- - **WebXR-capable device**: Android device with ARCore or iOS device with ARKit (via WebXR Viewer or Safari)
29
- - **Modern browser**: Chrome/Edge (Android) or Safari (iOS 15+)
27
+ - **HTTPS**: WebXR requires a secure context. Use HTTPS in production or `http://localhost` for local development.
28
+ - **WebXR-capable device**: Android device with ARCore
29
+ - **Modern browser**: Chrome or Edge on Android
30
30
  - **Three.js**: Version 0.176.0 or higher (peer dependency)
31
31
 
32
+ > **iOS is not supported.** This SDK requires the `camera-access` WebXR feature to capture frames for localization. Safari on iOS does not implement `camera-access`, so the AR session will fail to start on any iOS browser.
33
+
32
34
  ### Development Requirements
33
35
 
34
36
  - Node.js 16+ and npm
@@ -55,7 +57,7 @@ Since this SDK makes direct API calls to MultiSet servers from browser-based app
55
57
  Add entries for both your local development and production environments:
56
58
 
57
59
  **Local Development:**
58
- - **Format:** `http://localhost:PORT` (e.g., `http://localhost:3000`, `http://localhost:5173`)
60
+ - **Format:** `https://localhost:PORT` (e.g., `https://localhost:3000`, `https://localhost:5173`)
59
61
  - **Note:** Use the exact port your development server runs on
60
62
 
61
63
  **Production:**
@@ -81,7 +83,7 @@ import { WebxrController } from '@multisetai/vps/webxr';
81
83
  const client = new MultisetClient({
82
84
  clientId: 'CLIENT_ID',
83
85
  clientSecret: 'CLIENT_SECRET',
84
- code: 'MAP_OUR_MAPSET_CODE',
86
+ code: 'MAP_OR_MAPSET_CODE',
85
87
  mapType: 'map', // or 'map-set'
86
88
  endpoints: DEFAULT_ENDPOINTS,
87
89
  onAuthorize: (token) => console.log('Authorized:', token),
@@ -108,7 +110,7 @@ await controller.initialize();
108
110
  ### 4. Capture and localize
109
111
 
110
112
  ```typescript
111
- const result = await controller.captureFrame();
113
+ const result = await controller.localizeFrame();
112
114
  if (result?.localizeData?.poseFound) {
113
115
  console.log('Position:', result.localizeData.position);
114
116
  console.log('Rotation:', result.localizeData.rotation);
@@ -131,11 +133,42 @@ new MultisetClient(config: IMultisetSdkConfig)
131
133
 
132
134
  ```typescript
133
135
  interface IMultisetSdkConfig {
136
+ // Core
134
137
  clientId: string; // Your MultiSet client ID
135
138
  clientSecret: string; // Your MultiSet client secret
136
- code: string; // Map code (e.g., 'MAP_XXXXX')
137
- mapType: 'map' | 'map-set'; // Type of map to use
138
- endpoints?: Partial<IMultisetSdkEndpoints>; // Optional custom endpoints
139
+ /** Map or map-set code used for localization. */
140
+ code: string; // e.g., 'MAP_XXXXX'
141
+ /** Map or map-set type ('map' or 'map-set'). */
142
+ mapType: 'map' | 'map-set';
143
+ /** Override default API endpoints if needed. */
144
+ endpoints?: Partial<IMultisetSdkEndpoints>;
145
+ /** If true, show the VPS map mesh in the AR session (map-only). */
146
+ showMesh?: boolean;
147
+
148
+ // Localization behavior
149
+ /** If true, automatically start a localization run when the AR session starts. */
150
+ autoLocalize?: boolean;
151
+ /** If true, automatically re-localize when tracking is lost and then recovered. */
152
+ relocalization?: boolean;
153
+ /** When enabled, only accept a localization result if confidence >= confidenceThreshold. */
154
+ confidenceCheck?: boolean;
155
+ /** Minimum confidence (0.2–0.8) required when confidenceCheck is enabled. */
156
+ confidenceThreshold?: number;
157
+ /** Total single-frame attempts per localization run (1–5). */
158
+ requestAttempts?: number;
159
+ /** Time in seconds between attempts in a localization run (1–5). */
160
+ localizationInterval?: number;
161
+ /** Include device geo pose as a hint in localization requests. */
162
+ passGeoPose?: boolean;
163
+ /** Request geo coordinates in the localization response (if supported by backend). */
164
+ geoCoordinatesInResponse?: boolean;
165
+
166
+ // Localization lifecycle callbacks
167
+ onLocalizationInit?: () => void;
168
+ onLocalizationSuccess?: (result: ILocalizeAndMapDetails) => void;
169
+ onLocalizationFailure?: (reason?: string) => void;
170
+
171
+ // Core callbacks
139
172
  onAuthorize?: (token: string) => void;
140
173
  onFrameCaptured?: (payload: IFrameCaptureEvent) => void;
141
174
  onCameraIntrinsics?: (intrinsics: ICameraIntrinsicsEvent) => void;
@@ -156,15 +189,28 @@ const token = await client.authorize();
156
189
 
157
190
  **Returns**: The access token as a string.
158
191
 
159
- #### Events
192
+ #### Events and callbacks
160
193
 
161
- The client emits events through callback functions:
194
+ The client and controller emit events through callback functions:
162
195
 
163
196
  - **`onAuthorize`**: Called when authorization succeeds with the access token
164
197
  - **`onFrameCaptured`**: Called when a camera frame is captured for localization
165
198
  - **`onCameraIntrinsics`**: Called with camera intrinsic parameters
166
199
  - **`onPoseResult`**: Called with localization results (pose found/not found)
167
- - **`onError`**: Called when any error occurs
200
+ - **`onLocalizationInit`**: Called at the start of a localization run
201
+ - **`onLocalizationSuccess`**: Called when a localization run succeeds (optionally after confidence checks)
202
+ - **`onLocalizationFailure`**: Called when a localization run fails or the best result is below the confidence threshold
203
+ - **`onError`**: Called when any error occurs during authorization or localization
204
+
205
+ ### Advanced localization behavior
206
+
207
+ `MultisetClient` together with `WebxrController` supports a higher-level localization flow similar to the Unity SingleFrameLocalizationManager:
208
+
209
+ - **Multiple attempts per run**: `requestAttempts` and `localizationInterval` control how many single-frame attempts are made and how long to wait between them.
210
+ - **Confidence-based acceptance**: When `confidenceCheck` is `true`, only results with `confidence >= confidenceThreshold` (0.2–0.8) are treated as successful.
211
+ - **Auto-localize**: When `autoLocalize` is `true`, a localization run is started automatically when the AR session starts.
212
+ - **Re-localize on tracking loss**: When `relocalization` is `true`, the controller automatically tries to re-localize after tracking has been lost for a short period.
213
+ - **Geo hint**: When `passGeoPose` is `true`, the SDK uses the browser Geolocation API to compute a `geoHint` string `"lat,lon,alt"` and sends it with the localization request. When `geoCoordinatesInResponse` is `true`, the backend may include geo coordinates in the response (if supported).
168
214
 
169
215
  ## WebXR Controller API
170
216
 
@@ -216,7 +262,26 @@ if (result?.localizeData?.poseFound) {
216
262
  }
217
263
  ```
218
264
 
219
- **Returns**: Object with `localizeData` and optional `mapDetails`, or `null` if capture fails.
265
+ **Returns**: Object with `localizeData` and optional `mapDetails`, or `null` if no pose is found or capture fails.
266
+
267
+ ##### `localizeFrame(): Promise<ILocalizeAndMapDetails | null>`
268
+
269
+ Runs a full localization cycle using the configured `requestAttempts`, `localizationInterval`,
270
+ `confidenceCheck`, and `confidenceThreshold`:
271
+
272
+ ```typescript
273
+ const result = await controller.localizeFrame();
274
+ if (result?.localizeData?.poseFound) {
275
+ console.log('Localized at:', result.localizeData.position);
276
+ }
277
+ ```
278
+
279
+ This method:
280
+
281
+ - Fires `onLocalizationInit` at the start of the run
282
+ - Performs multiple attempts and picks the best result by confidence
283
+ - Applies the confidence threshold when `confidenceCheck` is enabled
284
+ - Calls `onLocalizationSuccess` or `onLocalizationFailure` accordingly
220
285
 
221
286
  ##### `getScene(): THREE.Scene`
222
287
 
@@ -308,6 +373,15 @@ const client = new MultisetClient({
308
373
  code: 'MAP_OR_MAPSET_CODE',
309
374
  mapType: 'map',
310
375
  endpoints: DEFAULT_ENDPOINTS,
376
+ // Optional advanced behavior
377
+ autoLocalize: true,
378
+ relocalization: true,
379
+ confidenceCheck: true,
380
+ confidenceThreshold: 0.5,
381
+ requestAttempts: 3,
382
+ localizationInterval: 2,
383
+ passGeoPose: true,
384
+ geoCoordinatesInResponse: true,
311
385
  onAuthorize: (token) => console.log('Authorized:', token),
312
386
  onError: (error) => console.error('Error:', error),
313
387
  });
@@ -334,7 +408,7 @@ cube.position.set(0, 0, -0.4);
334
408
  scene.add(cube);
335
409
 
336
410
  // Capture and localize
337
- const result = await controller.captureFrame();
411
+ const result = await controller.localizeFrame();
338
412
  if (result?.localizeData?.poseFound) {
339
413
  console.log('Position:', result.localizeData.position);
340
414
  }
@@ -389,7 +463,7 @@ export default function App() {
389
463
  };
390
464
 
391
465
  const handleCapture = async () => {
392
- const result = await controllerRef.current!.captureFrame();
466
+ const result = await controllerRef.current!.localizeFrame();
393
467
  if (result?.localizeData?.poseFound) {
394
468
  console.log('Localized!', result.localizeData.position);
395
469
  }
@@ -17,8 +17,11 @@ interface ILocalizeResponse {
17
17
  retrieval_scores: number[];
18
18
  num_matches: number[];
19
19
  confidence: number;
20
- retrieved_imgs: string[];
20
+ retreived_imgs: string[];
21
+ retrieved_imgs?: string[];
21
22
  mapIds: string[];
23
+ mapCodes: string[];
24
+ responseTime: number;
22
25
  }
23
26
  interface IMapLocation {
24
27
  type: string;
@@ -130,13 +133,44 @@ interface ILocalizeAndMapDetails {
130
133
  interface IMultisetSdkConfig {
131
134
  clientId: string;
132
135
  clientSecret: string;
136
+ /** Map or map-set code used for localization. */
133
137
  code: string;
138
+ /** Map or map-set type ('map' or 'map-set'). */
134
139
  mapType: MapType;
135
140
  endpoints?: Partial<IMultisetSdkEndpoints>;
141
+ /** If true, show the mesh in the AR session. Default is false. */
142
+ showMesh?: boolean;
143
+ /** If true, automatically start a localization run when the AR session starts. */
144
+ autoLocalize?: boolean;
145
+ /** If true, automatically re-localize when tracking is lost and then recovered. */
146
+ relocalization?: boolean;
147
+ /** When enabled, only accept a localization result if confidence >= confidenceThreshold. */
148
+ confidenceCheck?: boolean;
149
+ /** Minimum confidence (0.2–0.8) required when confidenceCheck is enabled. */
150
+ confidenceThreshold?: number;
151
+ /** Total single-frame attempts per localization run (1–5). */
152
+ requestAttempts?: number;
153
+ /** Time in seconds between attempts in a localization run (1–5). */
154
+ localizationInterval?: number;
155
+ /** Include device geo pose as a hint in localization requests. */
156
+ passGeoPose?: boolean;
157
+ /** Request geo coordinates in the localization response (if supported by backend). */
158
+ geoCoordinatesInResponse?: boolean;
159
+ /** Invoked at the start of a localization run. */
160
+ onLocalizationInit?: () => void;
161
+ /** Invoked after a successful localization that meets confidence criteria (if enabled). */
162
+ onLocalizationSuccess?: (result: ILocalizeAndMapDetails) => void;
163
+ /** Invoked when all attempts fail or the best result is below the confidence threshold. */
164
+ onLocalizationFailure?: (reason?: string) => void;
165
+ /** Called after a successful authorization with the access token. */
136
166
  onAuthorize?: (token: string) => void;
167
+ /** Called whenever a camera frame is captured and sent for localization. */
137
168
  onFrameCaptured?: (payload: IFrameCaptureEvent) => void;
169
+ /** Called with the camera intrinsics used for a localization request. */
138
170
  onCameraIntrinsics?: (intrinsics: ICameraIntrinsicsEvent) => void;
171
+ /** Called with the raw pose/localization result returned by the backend. */
139
172
  onPoseResult?: (payload: IPoseResultEvent) => void;
173
+ /** Called when any error occurs during authorization or localization. */
140
174
  onError?: (error: unknown) => void;
141
175
  }
142
176
  interface IMultisetSdkEndpoints {
@@ -181,18 +215,20 @@ interface ILocalizeResultEvent {
181
215
  response: ILocalizeAndMapDetails | null;
182
216
  }
183
217
  declare const DEFAULT_ENDPOINTS: IMultisetSdkEndpoints;
184
- /**
185
- * Placeholder class to be implemented by porting logic from multiset-webxr-sdk.
186
- */
218
+
187
219
  declare class MultisetClient {
188
220
  private readonly config;
189
221
  private readonly endpoints;
190
222
  private accessToken;
223
+ private mapDetailsCache;
191
224
  constructor(config: IMultisetSdkConfig);
192
225
  get token(): string | null;
226
+ getConfig(): IMultisetSdkConfig;
227
+ downloadFile(key: string): Promise<string>;
193
228
  authorize(): Promise<string>;
194
229
  private handleError;
195
230
  localizeWithFrame(frame: IFrameCaptureEvent, intrinsics: ICameraIntrinsicsEvent): Promise<ILocalizeAndMapDetails | null>;
231
+ private getGeoPoseComponents;
196
232
  private queryLocalization;
197
233
  private fetchMapDetails;
198
234
  private fetchMapSetDetails;
@@ -1,6 +1,6 @@
1
1
  import axios from 'axios';
2
2
 
3
- // src/lib/core/index.ts
3
+ // src/lib/core/config.ts
4
4
  var DEFAULT_ENDPOINTS = {
5
5
  authUrl: "https://api.multiset.ai/v1/m2m/token",
6
6
  queryUrl: "https://api.multiset.ai/v1/vps/map/query-form",
@@ -12,6 +12,7 @@ var MultisetClient = class {
12
12
  constructor(config) {
13
13
  this.config = config;
14
14
  this.accessToken = null;
15
+ this.mapDetailsCache = {};
15
16
  this.config = config;
16
17
  this.endpoints = {
17
18
  ...DEFAULT_ENDPOINTS,
@@ -21,6 +22,28 @@ var MultisetClient = class {
21
22
  get token() {
22
23
  return this.accessToken;
23
24
  }
25
+ getConfig() {
26
+ return this.config;
27
+ }
28
+ async downloadFile(key) {
29
+ if (!this.accessToken || !key) {
30
+ return "";
31
+ }
32
+ try {
33
+ const response = await axios.get(
34
+ `${this.endpoints.fileDownloadUrl}?key=${encodeURIComponent(key)}`,
35
+ {
36
+ headers: {
37
+ Authorization: `Bearer ${this.accessToken}`
38
+ }
39
+ }
40
+ );
41
+ return response.status === 200 ? response.data.url : "";
42
+ } catch (error) {
43
+ this.handleError(error);
44
+ return "";
45
+ }
46
+ }
24
47
  async authorize() {
25
48
  var _a, _b, _c, _d, _e;
26
49
  try {
@@ -68,22 +91,52 @@ var MultisetClient = class {
68
91
  }
69
92
  return queryResult;
70
93
  }
94
+ async getGeoPoseComponents() {
95
+ if (typeof navigator === "undefined" || !("geolocation" in navigator) || !navigator.geolocation) {
96
+ return null;
97
+ }
98
+ try {
99
+ const position = await new Promise((resolve, reject) => {
100
+ navigator.geolocation.getCurrentPosition(resolve, reject, {
101
+ enableHighAccuracy: true,
102
+ timeout: 1e4,
103
+ maximumAge: 0
104
+ });
105
+ });
106
+ const { latitude, longitude, altitude } = position.coords;
107
+ const safeAltitude = typeof altitude === "number" && !Number.isNaN(altitude) ? altitude : 0;
108
+ return { latitude, longitude, altitude: safeAltitude };
109
+ } catch {
110
+ return null;
111
+ }
112
+ }
71
113
  async queryLocalization(frame, intrinsics) {
72
114
  var _a;
73
115
  const formData = new FormData();
74
- formData.append("isRightHanded", "true");
75
- formData.append("width", `${frame.width}`);
76
- formData.append("height", `${frame.height}`);
77
- formData.append("px", `${intrinsics.px}`);
78
- formData.append("py", `${intrinsics.py}`);
79
- formData.append("fx", `${intrinsics.fx}`);
80
- formData.append("fy", `${intrinsics.fy}`);
81
- formData.append("queryImage", frame.blob);
82
116
  if (this.config.mapType === "map") {
83
117
  formData.append("mapCode", this.config.code);
84
118
  } else {
85
119
  formData.append("mapSetCode", this.config.code);
86
120
  }
121
+ formData.append("isRightHanded", "true");
122
+ formData.append("fx", `${intrinsics.fx}`);
123
+ formData.append("fy", `${intrinsics.fy}`);
124
+ formData.append("px", `${intrinsics.px}`);
125
+ formData.append("py", `${intrinsics.py}`);
126
+ formData.append("width", `${frame.width}`);
127
+ formData.append("height", `${frame.height}`);
128
+ formData.append("queryImage", frame.blob);
129
+ if (this.config.geoCoordinatesInResponse) {
130
+ formData.append("geoCoordinatesInResponse", "true");
131
+ }
132
+ if (this.config.passGeoPose) {
133
+ const components = await this.getGeoPoseComponents();
134
+ if (components) {
135
+ const { latitude, longitude, altitude } = components;
136
+ const geoHint = `${latitude},${longitude},${altitude}`;
137
+ formData.append("geoHint", geoHint);
138
+ }
139
+ }
87
140
  try {
88
141
  const response = await axios.post(
89
142
  this.endpoints.queryUrl,
@@ -101,10 +154,17 @@ var MultisetClient = class {
101
154
  const result = {
102
155
  localizeData: data
103
156
  };
104
- if ((_a = data.mapIds) == null ? void 0 : _a.length) {
105
- const mapDetails = await this.fetchMapDetails(data.mapIds[0]);
106
- if (mapDetails) {
107
- result.mapDetails = mapDetails;
157
+ if (this.config.showMesh && this.config.mapType === "map" && ((_a = data.mapCodes) == null ? void 0 : _a.length)) {
158
+ const mapCode = data.mapCodes[0];
159
+ const cached = this.mapDetailsCache[mapCode];
160
+ if (cached) {
161
+ result.mapDetails = cached;
162
+ } else {
163
+ const mapDetails = await this.fetchMapDetails(mapCode);
164
+ if (mapDetails) {
165
+ this.mapDetailsCache[mapCode] = mapDetails;
166
+ result.mapDetails = mapDetails;
167
+ }
108
168
  }
109
169
  }
110
170
  return result;
@@ -113,10 +173,10 @@ var MultisetClient = class {
113
173
  return null;
114
174
  }
115
175
  }
116
- async fetchMapDetails(mapId) {
176
+ async fetchMapDetails(mapCode) {
117
177
  try {
118
178
  const response = await axios.get(
119
- `${this.endpoints.mapDetailsUrl}${mapId}`,
179
+ `${this.endpoints.mapDetailsUrl}${mapCode}`,
120
180
  {
121
181
  headers: {
122
182
  Authorization: `Bearer ${this.accessToken}`
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/lib/core/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import axios, { AxiosError } from 'axios';\nimport type {\n ILocalizeAndMapDetails,\n ILocalizeResponse,\n MapType,\n IGetMapsDetailsResponse,\n IMapSetMapsResponse,\n} from './types';\n\nexport interface IMultisetSdkConfig {\n clientId: string;\n clientSecret: string;\n code: string;\n mapType: MapType;\n endpoints?: Partial<IMultisetSdkEndpoints>;\n onAuthorize?: (token: string) => void;\n onFrameCaptured?: (payload: IFrameCaptureEvent) => void;\n onCameraIntrinsics?: (intrinsics: ICameraIntrinsicsEvent) => void;\n onPoseResult?: (payload: IPoseResultEvent) => void;\n onError?: (error: unknown) => void;\n}\n\nexport interface IMultisetSdkEndpoints {\n authUrl: string;\n queryUrl: string;\n mapDetailsUrl: string;\n fileDownloadUrl: string;\n mapSetDetailsUrl: string;\n}\n\nexport interface IFrameCaptureEvent {\n blob: Blob;\n width: number;\n height: number;\n}\n\nexport interface ICameraIntrinsicsEvent {\n fx: number;\n fy: number;\n px: number;\n py: number;\n width: number;\n height: number;\n}\n\nexport interface IPoseResultEvent {\n poseFound: boolean;\n position: {\n x: number;\n y: number;\n z: number;\n };\n rotation: {\n x: number;\n y: number;\n z: number;\n w: number;\n };\n mapIds: string[];\n confidence?: number;\n}\n\nexport interface ILocalizeResultEvent {\n frame: IFrameCaptureEvent;\n intrinsics: ICameraIntrinsicsEvent;\n response: ILocalizeAndMapDetails | null;\n}\n\nexport const DEFAULT_ENDPOINTS: IMultisetSdkEndpoints = {\n authUrl: 'https://api.multiset.ai/v1/m2m/token',\n queryUrl: 'https://api.multiset.ai/v1/vps/map/query-form',\n mapDetailsUrl: 'https://api.multiset.ai/v1/vps/map/',\n mapSetDetailsUrl: 'https://api.multiset.ai/v1/vps/map-set/',\n fileDownloadUrl: 'https://api.multiset.ai/v1/file',\n};\n\n/**\n * Placeholder class to be implemented by porting logic from multiset-webxr-sdk.\n */\nexport class MultisetClient {\n private readonly endpoints: IMultisetSdkEndpoints;\n private accessToken: string | null = null;\n\n constructor(private readonly config: IMultisetSdkConfig) {\n this.config = config;\n this.endpoints = {\n ...DEFAULT_ENDPOINTS,\n ...config.endpoints,\n };\n }\n\n get token(): string | null {\n return this.accessToken;\n }\n\n async authorize(): Promise<string> {\n try {\n const response = await axios.post(\n this.endpoints.authUrl,\n {},\n {\n auth: {\n username: this.config.clientId,\n password: this.config.clientSecret,\n },\n }\n );\n\n const token: string | undefined =\n response.data?.token ?? response.data?.access_token;\n\n if (!token) {\n throw new Error('Authorization succeeded but no token was returned.');\n }\n\n this.accessToken = token;\n this.config.onAuthorize?.(token);\n return token;\n } catch (error) {\n this.handleError(error);\n throw error;\n }\n }\n\n private handleError(error: unknown): void {\n if (axios.isAxiosError(error)) {\n const axiosError = error as AxiosError;\n this.config.onError?.(axiosError);\n } else {\n this.config.onError?.(error);\n }\n }\n\n async localizeWithFrame(\n frame: IFrameCaptureEvent,\n intrinsics: ICameraIntrinsicsEvent\n ): Promise<ILocalizeAndMapDetails | null> {\n if (!this.accessToken) {\n throw new Error('Access token is missing. Call authorize() first.');\n }\n\n this.config.onFrameCaptured?.(frame);\n this.config.onCameraIntrinsics?.(intrinsics);\n\n const queryResult = await this.queryLocalization(frame, intrinsics);\n\n if (queryResult?.localizeData?.poseFound) {\n this.config.onPoseResult?.(queryResult.localizeData);\n }\n\n return queryResult;\n }\n\n private async queryLocalization(\n frame: IFrameCaptureEvent,\n intrinsics: ICameraIntrinsicsEvent\n ): Promise<ILocalizeAndMapDetails | null> {\n const formData = new FormData();\n formData.append('isRightHanded', 'true');\n formData.append('width', `${frame.width}`);\n formData.append('height', `${frame.height}`);\n formData.append('px', `${intrinsics.px}`);\n formData.append('py', `${intrinsics.py}`);\n formData.append('fx', `${intrinsics.fx}`);\n formData.append('fy', `${intrinsics.fy}`);\n formData.append('queryImage', frame.blob);\n\n if (this.config.mapType === 'map') {\n formData.append('mapCode', this.config.code);\n } else {\n formData.append('mapSetCode', this.config.code);\n }\n\n try {\n const response = await axios.post<ILocalizeResponse>(\n this.endpoints.queryUrl,\n formData,\n {\n headers: {\n Authorization: `Bearer ${this.accessToken}`,\n },\n }\n );\n\n const data = response.data;\n if (!data.poseFound) {\n return null;\n }\n\n const result: ILocalizeAndMapDetails = {\n localizeData: data,\n };\n\n if (data.mapIds?.length) {\n const mapDetails = await this.fetchMapDetails(data.mapIds[0]);\n if (mapDetails) {\n result.mapDetails = mapDetails;\n }\n }\n\n return result;\n } catch (error) {\n this.handleError(error);\n return null;\n }\n }\n\n private async fetchMapDetails(mapId: string): Promise<IGetMapsDetailsResponse | null> {\n try {\n const response = await axios.get<IGetMapsDetailsResponse>(\n `${this.endpoints.mapDetailsUrl}${mapId}`,\n {\n headers: {\n Authorization: `Bearer ${this.accessToken}`,\n },\n }\n );\n return response.data;\n } catch (error) {\n this.handleError(error);\n return null;\n }\n }\n\n private async fetchMapSetDetails(mapSetId: string): Promise<IMapSetMapsResponse | null> {\n try {\n const response = await axios.get<IMapSetMapsResponse>(\n `${this.endpoints.mapSetDetailsUrl}${mapSetId}`,\n {\n headers: {\n Authorization: `Bearer ${this.accessToken}`,\n },\n }\n );\n return response.data;\n } catch (error) {\n this.handleError(error);\n return null;\n }\n }\n}\n\nexport type { IMultisetSdkConfig as IMultisetClientOptions };\nexport type {\n MapType,\n ILocalizeResponse,\n ILocalizeAndMapDetails,\n IGetMapsDetailsResponse,\n IMapSetMapsResponse,\n} from './types';\n\n"]}
1
+ {"version":3,"sources":["../../src/lib/core/config.ts","../../src/lib/core/client.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { ILocalizeAndMapDetails, MapType } from './types';\n\nexport interface IMultisetSdkConfig {\n clientId: string;\n clientSecret: string;\n /** Map or map-set code used for localization. */\n code: string;\n /** Map or map-set type ('map' or 'map-set'). */\n mapType: MapType;\n endpoints?: Partial<IMultisetSdkEndpoints>;\n /** If true, show the mesh in the AR session. Default is false. */\n showMesh?: boolean;\n /** If true, automatically start a localization run when the AR session starts. */\n autoLocalize?: boolean;\n /** If true, automatically re-localize when tracking is lost and then recovered. */\n relocalization?: boolean;\n /** When enabled, only accept a localization result if confidence >= confidenceThreshold. */\n confidenceCheck?: boolean;\n /** Minimum confidence (0.2–0.8) required when confidenceCheck is enabled. */\n confidenceThreshold?: number;\n /** Total single-frame attempts per localization run (1–5). */\n requestAttempts?: number;\n /** Time in seconds between attempts in a localization run (1–5). */\n localizationInterval?: number;\n /** Include device geo pose as a hint in localization requests. */\n passGeoPose?: boolean;\n /** Request geo coordinates in the localization response (if supported by backend). */\n geoCoordinatesInResponse?: boolean;\n\n /** Invoked at the start of a localization run. */\n onLocalizationInit?: () => void;\n /** Invoked after a successful localization that meets confidence criteria (if enabled). */\n onLocalizationSuccess?: (result: ILocalizeAndMapDetails) => void;\n /** Invoked when all attempts fail or the best result is below the confidence threshold. */\n onLocalizationFailure?: (reason?: string) => void;\n\n /** Called after a successful authorization with the access token. */\n onAuthorize?: (token: string) => void;\n /** Called whenever a camera frame is captured and sent for localization. */\n onFrameCaptured?: (payload: IFrameCaptureEvent) => void;\n /** Called with the camera intrinsics used for a localization request. */\n onCameraIntrinsics?: (intrinsics: ICameraIntrinsicsEvent) => void;\n /** Called with the raw pose/localization result returned by the backend. */\n onPoseResult?: (payload: IPoseResultEvent) => void;\n /** Called when any error occurs during authorization or localization. */\n onError?: (error: unknown) => void;\n}\n\nexport interface IMultisetSdkEndpoints {\n authUrl: string;\n queryUrl: string;\n mapDetailsUrl: string;\n fileDownloadUrl: string;\n mapSetDetailsUrl: string;\n}\n\nexport interface IFrameCaptureEvent {\n blob: Blob;\n width: number;\n height: number;\n}\n\nexport interface ICameraIntrinsicsEvent {\n fx: number;\n fy: number;\n px: number;\n py: number;\n width: number;\n height: number;\n}\n\nexport interface IPoseResultEvent {\n poseFound: boolean;\n position: {\n x: number;\n y: number;\n z: number;\n };\n rotation: {\n x: number;\n y: number;\n z: number;\n w: number;\n };\n mapIds: string[];\n confidence?: number;\n}\n\nexport interface ILocalizeResultEvent {\n frame: IFrameCaptureEvent;\n intrinsics: ICameraIntrinsicsEvent;\n response: ILocalizeAndMapDetails | null;\n}\n\nexport const DEFAULT_ENDPOINTS: IMultisetSdkEndpoints = {\n authUrl: 'https://api.multiset.ai/v1/m2m/token',\n queryUrl: 'https://api.multiset.ai/v1/vps/map/query-form',\n mapDetailsUrl: 'https://api.multiset.ai/v1/vps/map/',\n mapSetDetailsUrl: 'https://api.multiset.ai/v1/vps/map-set/',\n fileDownloadUrl: 'https://api.multiset.ai/v1/file',\n};\n","import axios, { AxiosError } from 'axios';\nimport type {\n IMultisetSdkConfig,\n IMultisetSdkEndpoints,\n IFrameCaptureEvent,\n ICameraIntrinsicsEvent,\n} from './config';\nimport { DEFAULT_ENDPOINTS } from './config';\nimport type {\n ILocalizeAndMapDetails,\n ILocalizeResponse,\n IGetMapsDetailsResponse,\n IMapSetMapsResponse,\n} from './types';\n\nexport class MultisetClient {\n private readonly endpoints: IMultisetSdkEndpoints;\n private accessToken: string | null = null;\n private mapDetailsCache: Record<string, IGetMapsDetailsResponse> = {};\n\n constructor(private readonly config: IMultisetSdkConfig) {\n this.config = config;\n this.endpoints = {\n ...DEFAULT_ENDPOINTS,\n ...config.endpoints,\n };\n }\n\n get token(): string | null {\n return this.accessToken;\n }\n\n getConfig(): IMultisetSdkConfig {\n return this.config;\n }\n\n async downloadFile(key: string): Promise<string> {\n if (!this.accessToken || !key) {\n return '';\n }\n\n try {\n const response = await axios.get<{ url: string }>(\n `${this.endpoints.fileDownloadUrl}?key=${encodeURIComponent(key)}`,\n {\n headers: {\n Authorization: `Bearer ${this.accessToken}`,\n },\n }\n );\n\n return response.status === 200 ? response.data.url : '';\n } catch (error) {\n this.handleError(error);\n return '';\n }\n }\n\n async authorize(): Promise<string> {\n try {\n const response = await axios.post(\n this.endpoints.authUrl,\n {},\n {\n auth: {\n username: this.config.clientId,\n password: this.config.clientSecret,\n },\n }\n );\n\n const token: string | undefined =\n response.data?.token ?? response.data?.access_token;\n\n if (!token) {\n throw new Error('Authorization succeeded but no token was returned.');\n }\n\n this.accessToken = token;\n this.config.onAuthorize?.(token);\n return token;\n } catch (error) {\n this.handleError(error);\n throw error;\n }\n }\n\n private handleError(error: unknown): void {\n if (axios.isAxiosError(error)) {\n const axiosError = error as AxiosError;\n this.config.onError?.(axiosError);\n } else {\n this.config.onError?.(error);\n }\n }\n\n async localizeWithFrame(\n frame: IFrameCaptureEvent,\n intrinsics: ICameraIntrinsicsEvent\n ): Promise<ILocalizeAndMapDetails | null> {\n if (!this.accessToken) {\n throw new Error('Access token is missing. Call authorize() first.');\n }\n\n this.config.onFrameCaptured?.(frame);\n this.config.onCameraIntrinsics?.(intrinsics);\n\n const queryResult = await this.queryLocalization(frame, intrinsics);\n\n if (queryResult?.localizeData?.poseFound) {\n this.config.onPoseResult?.(queryResult.localizeData);\n }\n\n return queryResult;\n }\n\n private async getGeoPoseComponents(): Promise<\n { latitude: number, longitude: number, altitude: number } | null\n > {\n if (\n typeof navigator === 'undefined' ||\n !('geolocation' in navigator) ||\n !navigator.geolocation\n ) {\n return null;\n }\n\n try {\n const position = await new Promise<GeolocationPosition>((resolve, reject) => {\n navigator.geolocation.getCurrentPosition(resolve, reject, {\n enableHighAccuracy: true,\n timeout: 10000,\n maximumAge: 0,\n });\n });\n\n const { latitude, longitude, altitude } = position.coords;\n const safeAltitude =\n typeof altitude === 'number' && !Number.isNaN(altitude) ? altitude : 0.0;\n\n return { latitude, longitude, altitude: safeAltitude };\n } catch {\n return null;\n }\n }\n\n private async queryLocalization(\n frame: IFrameCaptureEvent,\n intrinsics: ICameraIntrinsicsEvent\n ): Promise<ILocalizeAndMapDetails | null> {\n const formData = new FormData();\n\n if (this.config.mapType === 'map') {\n formData.append('mapCode', this.config.code);\n } else {\n formData.append('mapSetCode', this.config.code);\n }\n formData.append('isRightHanded', 'true');\n formData.append('fx', `${intrinsics.fx}`);\n formData.append('fy', `${intrinsics.fy}`);\n formData.append('px', `${intrinsics.px}`);\n formData.append('py', `${intrinsics.py}`);\n formData.append('width', `${frame.width}`);\n formData.append('height', `${frame.height}`);\n formData.append('queryImage', frame.blob);\n if (this.config.geoCoordinatesInResponse) {\n formData.append('geoCoordinatesInResponse', 'true');\n }\n if (this.config.passGeoPose) {\n const components = await this.getGeoPoseComponents();\n if (components) {\n const { latitude, longitude, altitude } = components;\n const geoHint = `${latitude},${longitude},${altitude}`;\n formData.append('geoHint', geoHint);\n }\n }\n\n try {\n const response = await axios.post<ILocalizeResponse>(\n this.endpoints.queryUrl,\n formData,\n {\n headers: {\n Authorization: `Bearer ${this.accessToken}`,\n },\n }\n );\n\n const data = response.data;\n if (!data.poseFound) {\n return null;\n }\n\n const result: ILocalizeAndMapDetails = {\n localizeData: data,\n };\n\n if (\n this.config.showMesh &&\n this.config.mapType === 'map' &&\n data.mapCodes?.length\n ) {\n const mapCode = data.mapCodes[0];\n const cached = this.mapDetailsCache[mapCode];\n if (cached) {\n result.mapDetails = cached;\n } else {\n const mapDetails = await this.fetchMapDetails(mapCode);\n if (mapDetails) {\n this.mapDetailsCache[mapCode] = mapDetails;\n result.mapDetails = mapDetails;\n }\n }\n }\n\n return result;\n } catch (error) {\n this.handleError(error);\n return null;\n }\n }\n\n private async fetchMapDetails(mapCode: string): Promise<IGetMapsDetailsResponse | null> {\n try {\n const response = await axios.get<IGetMapsDetailsResponse>(\n `${this.endpoints.mapDetailsUrl}${mapCode}`,\n {\n headers: {\n Authorization: `Bearer ${this.accessToken}`,\n },\n }\n );\n return response.data;\n } catch (error) {\n this.handleError(error);\n return null;\n }\n }\n\n private async fetchMapSetDetails(mapSetId: string): Promise<IMapSetMapsResponse | null> {\n try {\n const response = await axios.get<IMapSetMapsResponse>(\n `${this.endpoints.mapSetDetailsUrl}${mapSetId}`,\n {\n headers: {\n Authorization: `Bearer ${this.accessToken}`,\n },\n }\n );\n return response.data;\n } catch (error) {\n this.handleError(error);\n return null;\n }\n }\n}\n"]}