@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 +89 -15
- package/dist/core/index.d.ts +40 -4
- package/dist/core/index.js +75 -15
- package/dist/core/index.js.map +1 -1
- package/dist/index.js +535 -130
- package/dist/index.js.map +1 -1
- package/dist/webxr/index.d.ts +23 -5
- package/dist/webxr/index.js +461 -116
- package/dist/webxr/index.js.map +1 -1
- package/package.json +2 -2
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 `
|
|
28
|
-
- **WebXR-capable device**: Android device with ARCore
|
|
29
|
-
- **Modern browser**: Chrome
|
|
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:** `
|
|
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: '
|
|
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.
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
|
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
|
-
- **`
|
|
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.
|
|
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!.
|
|
466
|
+
const result = await controllerRef.current!.localizeFrame();
|
|
393
467
|
if (result?.localizeData?.poseFound) {
|
|
394
468
|
console.log('Localized!', result.localizeData.position);
|
|
395
469
|
}
|
package/dist/core/index.d.ts
CHANGED
|
@@ -17,8 +17,11 @@ interface ILocalizeResponse {
|
|
|
17
17
|
retrieval_scores: number[];
|
|
18
18
|
num_matches: number[];
|
|
19
19
|
confidence: number;
|
|
20
|
-
|
|
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;
|
package/dist/core/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
2
|
|
|
3
|
-
// src/lib/core/
|
|
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.
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
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(
|
|
176
|
+
async fetchMapDetails(mapCode) {
|
|
117
177
|
try {
|
|
118
178
|
const response = await axios.get(
|
|
119
|
-
`${this.endpoints.mapDetailsUrl}${
|
|
179
|
+
`${this.endpoints.mapDetailsUrl}${mapCode}`,
|
|
120
180
|
{
|
|
121
181
|
headers: {
|
|
122
182
|
Authorization: `Bearer ${this.accessToken}`
|
package/dist/core/index.js.map
CHANGED
|
@@ -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"]}
|