@neurodyn/react-model-viewer 0.1.1 → 0.2.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/dist/index.d.ts CHANGED
@@ -3,15 +3,50 @@ import { JSX } from 'react/jsx-runtime';
3
3
  import type { default as React_2 } from 'react';
4
4
  import * as React_3 from 'react';
5
5
 
6
+ /**
7
+ * How long a tap may wait on a host's own AR before the native launch goes
8
+ * ahead instead.
9
+ *
10
+ * Chrome lets a page navigate on behalf of a tap for about five seconds, and
11
+ * the Scene Viewer intent is such a navigation: sent any later, the browser
12
+ * drops it as one nobody asked for. A host that loads its AR on the tap races
13
+ * the load against this, so a slow connection gets Scene Viewer for this tap
14
+ * and the host's AR for the next, the download having gone on meanwhile.
15
+ */
16
+ export declare const AR_TAP_WINDOW_MS = 3000;
17
+
6
18
  /** What the host needs to open its own AR experience for the shown model. */
7
19
  export declare interface ArLaunchTarget {
20
+ /**
21
+ * The API the viewer's widget session was issued by, when the host set one.
22
+ * A host continuing that session has to refresh it there.
23
+ */
24
+ apiBaseUrl?: string;
25
+ /**
26
+ * The public key the viewer runs on — including one a QR handoff code
27
+ * resolved, which the host page never saw.
28
+ */
29
+ apiKey?: string;
8
30
  /** Material collection currently shown, which may differ from the entry url. */
9
31
  hid?: string;
32
+ /** The viewer's UI language, so the host's AR speaks the same one. */
33
+ lang?: 'en' | 'ru';
34
+ /** The object's display name, for the host AR's accessible label. */
35
+ name?: string;
10
36
  tinuuid: string;
37
+ /**
38
+ * A live token of the viewer's widget session, so the host's AR continues
39
+ * that session instead of activating a second one.
40
+ */
41
+ widgetSessionToken?: string;
11
42
  }
12
43
 
13
44
  declare type DialogProps = React_3.ComponentProps<typeof DialogPrimitive.Root>
14
45
 
46
+ export declare function isIOSDevice(): boolean;
47
+
48
+ export declare function isMobileArDevice(): boolean;
49
+
15
50
  /**
16
51
  * Lets the host open its own AR experience instead of the native one.
17
52
  *
@@ -27,9 +62,9 @@ export declare type ModelViewerArExperience = 'advanced' | 'standard';
27
62
 
28
63
  declare type ModelViewerBackground = 'default' | 'transparent';
29
64
 
30
- export declare function ModelViewerDialog({ tinuuid, apiKey, apiBaseUrl, appClipBundleId, initialWidgetSessionToken, handoffUrl, hid, lang, mode, sizeType, dimensionAxes, cameraOrbit, minCameraOrbit, maxCameraOrbit, ...props }: ModelViewerDialogProps): JSX.Element;
65
+ export declare function ModelViewerDialog({ tinuuid, apiKey, apiBaseUrl, appClipBundleId, initialWidgetSessionToken, handoffUrl, hid, lang, launchAr, mode, sizeType, dimensionAxes, cameraOrbit, minCameraOrbit, maxCameraOrbit, ...props }: ModelViewerDialogProps): JSX.Element;
31
66
 
32
- export declare interface ModelViewerDialogProps extends Pick<ModelViewerRootProps, 'apiBaseUrl' | 'apiKey' | 'appClipBundleId' | 'cameraOrbit' | 'dimensionAxes' | 'handoffUrl' | 'hid' | 'initialWidgetSessionToken' | 'lang' | 'maxCameraOrbit' | 'minCameraOrbit' | 'mode' | 'sizeType' | 'tinuuid'>, Omit<DialogProps, 'children' | 'open' | 'defaultOpen' | 'onOpenChange' | 'modal'> {
67
+ export declare interface ModelViewerDialogProps extends Pick<ModelViewerRootProps, 'apiBaseUrl' | 'apiKey' | 'appClipBundleId' | 'cameraOrbit' | 'dimensionAxes' | 'handoffUrl' | 'hid' | 'initialWidgetSessionToken' | 'lang' | 'launchAr' | 'maxCameraOrbit' | 'minCameraOrbit' | 'mode' | 'sizeType' | 'tinuuid'>, Omit<DialogProps, 'children' | 'open' | 'defaultOpen' | 'onOpenChange' | 'modal'> {
33
68
  /** The controlled open state of the dialog. Use with `onOpenChange`. */
34
69
  open?: DialogProps['open'];
35
70
  /** The initial open state when the dialog is uncontrolled. */
@@ -99,4 +134,7 @@ export declare type SizeAxis = 'x' | 'y' | 'z';
99
134
 
100
135
  export declare type SizeType = 'cm' | 'inch';
101
136
 
137
+ /** The value, or `null` once the tap's window has passed; a rejection still rejects. */
138
+ export declare function withinTapWindow<T>(promise: Promise<T>, windowMs?: number): Promise<T | null>;
139
+
102
140
  export { }
package/dist/index.js CHANGED
@@ -494,6 +494,18 @@ var __webpack_modules__ = {
494
494
  inset-block: calc(var(--spacing) * 0);
495
495
  }
496
496
 
497
+ .start-2 {
498
+ inset-inline-start: calc(var(--spacing) * 2);
499
+ }
500
+
501
+ .end-2 {
502
+ inset-inline-end: calc(var(--spacing) * 2);
503
+ }
504
+
505
+ .end-4 {
506
+ inset-inline-end: calc(var(--spacing) * 4);
507
+ }
508
+
497
509
  .top-0 {
498
510
  top: calc(var(--spacing) * 0);
499
511
  }
@@ -542,10 +554,6 @@ var __webpack_modules__ = {
542
554
  right: calc(var(--spacing) * 3);
543
555
  }
544
556
 
545
- .right-4 {
546
- right: calc(var(--spacing) * 4);
547
- }
548
-
549
557
  .right-6 {
550
558
  right: calc(var(--spacing) * 6);
551
559
  }
@@ -558,15 +566,7 @@ var __webpack_modules__ = {
558
566
  left: calc(var(--spacing) * 0);
559
567
  }
560
568
 
561
- .left-1\\/2 {
562
- left: 50%;
563
- }
564
-
565
- .left-2 {
566
- left: calc(var(--spacing) * 2);
567
- }
568
-
569
- .left-\\[50\\%\\] {
569
+ .left-1\\/2, .left-\\[50\\%\\] {
570
570
  left: 50%;
571
571
  }
572
572
 
@@ -680,6 +680,14 @@ var __webpack_modules__ = {
680
680
  margin-inline-start: -1px;
681
681
  }
682
682
 
683
+ .ms-4 {
684
+ margin-inline-start: calc(var(--spacing) * 4);
685
+ }
686
+
687
+ .ms-auto {
688
+ margin-inline-start: auto;
689
+ }
690
+
683
691
  .mt-1\\.5 {
684
692
  margin-top: calc(var(--spacing) * 1.5);
685
693
  }
@@ -708,14 +716,6 @@ var __webpack_modules__ = {
708
716
  margin-left: calc(var(--spacing) * 1);
709
717
  }
710
718
 
711
- .ml-4 {
712
- margin-left: calc(var(--spacing) * 4);
713
- }
714
-
715
- .ml-auto {
716
- margin-left: auto;
717
- }
718
-
719
719
  .box-border {
720
720
  box-sizing: border-box;
721
721
  }
@@ -1854,6 +1854,26 @@ var __webpack_modules__ = {
1854
1854
  padding-block: calc(var(--spacing) * 14);
1855
1855
  }
1856
1856
 
1857
+ .ps-2 {
1858
+ padding-inline-start: calc(var(--spacing) * 2);
1859
+ }
1860
+
1861
+ .ps-3 {
1862
+ padding-inline-start: calc(var(--spacing) * 3);
1863
+ }
1864
+
1865
+ .ps-8 {
1866
+ padding-inline-start: calc(var(--spacing) * 8);
1867
+ }
1868
+
1869
+ .pe-2 {
1870
+ padding-inline-end: calc(var(--spacing) * 2);
1871
+ }
1872
+
1873
+ .pe-8 {
1874
+ padding-inline-end: calc(var(--spacing) * 8);
1875
+ }
1876
+
1857
1877
  .pt-1\\.5 {
1858
1878
  padding-top: calc(var(--spacing) * 1.5);
1859
1879
  }
@@ -1870,10 +1890,6 @@ var __webpack_modules__ = {
1870
1890
  padding-right: calc(var(--spacing) * 1);
1871
1891
  }
1872
1892
 
1873
- .pr-2 {
1874
- padding-right: calc(var(--spacing) * 2);
1875
- }
1876
-
1877
1893
  .pr-2\\.5 {
1878
1894
  padding-right: calc(var(--spacing) * 2.5);
1879
1895
  }
@@ -1886,26 +1902,14 @@ var __webpack_modules__ = {
1886
1902
  padding-right: calc(var(--spacing) * 5);
1887
1903
  }
1888
1904
 
1889
- .pr-8 {
1890
- padding-right: calc(var(--spacing) * 8);
1891
- }
1892
-
1893
1905
  .pb-\\[0\\.3125rem\\] {
1894
1906
  padding-bottom: 5px;
1895
1907
  }
1896
1908
 
1897
- .pl-2 {
1898
- padding-left: calc(var(--spacing) * 2);
1899
- }
1900
-
1901
1909
  .pl-3 {
1902
1910
  padding-left: calc(var(--spacing) * 3);
1903
1911
  }
1904
1912
 
1905
- .pl-8 {
1906
- padding-left: calc(var(--spacing) * 8);
1907
- }
1908
-
1909
1913
  .text-center {
1910
1914
  text-align: center;
1911
1915
  }
@@ -1914,6 +1918,10 @@ var __webpack_modules__ = {
1914
1918
  text-align: left;
1915
1919
  }
1916
1920
 
1921
+ .text-start {
1922
+ text-align: start;
1923
+ }
1924
+
1917
1925
  .align-middle {
1918
1926
  vertical-align: middle;
1919
1927
  }
@@ -4155,6 +4163,10 @@ var __webpack_modules__ = {
4155
4163
  }
4156
4164
  }
4157
4165
 
4166
+ .rtl\\:rotate-180:where(:dir(rtl), [dir="rtl"], [dir="rtl"] *) {
4167
+ rotate: 180deg;
4168
+ }
4169
+
4158
4170
  .dark\\:bg-input\\/30:is(.dark *) {
4159
4171
  background-color: var(--input);
4160
4172
  }
@@ -4734,8 +4746,17 @@ var __webpack_modules__ = {
4734
4746
  font-display: swap;
4735
4747
  }
4736
4748
 
4749
+ @font-face {
4750
+ font-family: __tw4_shd_noto_arabic_7x4q2;
4751
+ src: url("https://fonts.gstatic.com/s/notosansarabic/v33/nwpCtLGrOAZMl5nJ_wfgRg3DrWFZWsnVBJ_sS6tlqHHFlj4wv4r4xA.woff2") format("woff2");
4752
+ font-weight: 100 900;
4753
+ font-style: normal;
4754
+ font-display: swap;
4755
+ unicode-range: U+6??, U+750-77F, U+870-891, U+897-8E1, U+8E3-8FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE70-FE74, U+FE76-FEFC;
4756
+ }
4757
+
4737
4758
  :root {
4738
- --v-font-family: "__tw4_shd_inter_7x4q2", sans-serif;
4759
+ --v-font-family: "__tw4_shd_inter_7x4q2", "__tw4_shd_noto_arabic_7x4q2", sans-serif;
4739
4760
  font-family: var(--v-font-family);
4740
4761
  font-feature-settings: "liga" 1, "calt" 1;
4741
4762
  -webkit-font-smoothing: antialiased;
@@ -5374,6 +5395,15 @@ var __webpack_modules__ = {
5374
5395
  font-style: normal;
5375
5396
  font-display: swap;
5376
5397
  }
5398
+
5399
+ @font-face {
5400
+ font-family: __tw4_shd_noto_arabic_7x4q2;
5401
+ src: url("https://fonts.gstatic.com/s/notosansarabic/v33/nwpCtLGrOAZMl5nJ_wfgRg3DrWFZWsnVBJ_sS6tlqHHFlj4wv4r4xA.woff2") format("woff2");
5402
+ font-weight: 100 900;
5403
+ font-style: normal;
5404
+ font-display: swap;
5405
+ unicode-range: U+6??, U+750-77F, U+870-891, U+897-8E1, U+8E3-8FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE70-FE74, U+FE76-FEFC;
5406
+ }
5377
5407
  `,
5378
5408
  ""
5379
5409
  ]);
@@ -5831,7 +5861,7 @@ function DialogContent({ className, children, contentInsideOverlay = false, show
5831
5861
  children,
5832
5862
  showCloseButton && /*#__PURE__*/ jsxs(react_dialog_Close, {
5833
5863
  "data-slot": "dialog-close",
5834
- className: "focus-visible:ring-ring/30 absolute top-4 right-4 inline-flex size-6 cursor-pointer items-center justify-center rounded-full text-icons-light transition-colors hover:text-icons-normal focus-visible:text-icons-dark focus-visible:ring-2 focus-visible:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0",
5864
+ className: "focus-visible:ring-ring/30 absolute top-4 end-4 inline-flex size-6 cursor-pointer items-center justify-center rounded-full text-icons-light transition-colors hover:text-icons-normal focus-visible:text-icons-dark focus-visible:ring-2 focus-visible:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0",
5835
5865
  children: [
5836
5866
  /*#__PURE__*/ jsx(XIcon, {
5837
5867
  className: "size-6"
@@ -6369,12 +6399,18 @@ function shouldRetryViewerQuery(failureCount, error) {
6369
6399
  return failureCount < 2;
6370
6400
  }
6371
6401
  const WIDGET_SESSION_TOKEN_HEADER = 'x-widget-session-token';
6402
+ const APP_TYPE_HEADER = 'x-app-type';
6372
6403
  const DEFAULT_REFRESH_THRESHOLD_MS = 30000;
6373
6404
  const ALLOWED_PRODUCT_TYPES = new Set([
6374
6405
  'core-ar',
6375
6406
  'core-ar-shopify'
6376
6407
  ]);
6377
6408
  const ASSUMED_HANDOFF_SESSION_TTL_SECONDS = 900;
6409
+ function getAppTypeHeaders(appType) {
6410
+ return appType ? {
6411
+ [APP_TYPE_HEADER]: appType
6412
+ } : {};
6413
+ }
6378
6414
  async function activateCoreArWidgetSession(mode, requestConfig) {
6379
6415
  const activation = await publicApiControllerActivateCoreAR({
6380
6416
  mode
@@ -6385,6 +6421,7 @@ async function activateCoreArWidgetSession(mode, requestConfig) {
6385
6421
  class ModelViewerWidgetSession {
6386
6422
  apiBaseUrl;
6387
6423
  apiKey;
6424
+ appTypeHeaders;
6388
6425
  issueWidgetToken;
6389
6426
  mode;
6390
6427
  now;
@@ -6394,6 +6431,7 @@ class ModelViewerWidgetSession {
6394
6431
  constructor(activationData, options){
6395
6432
  this.apiBaseUrl = options.apiBaseUrl;
6396
6433
  this.apiKey = options.apiKey;
6434
+ this.appTypeHeaders = getAppTypeHeaders(options.appType);
6397
6435
  this.issueWidgetToken = options.issueWidgetToken ?? publicApiControllerIssueWidgetToken;
6398
6436
  this.mode = options.mode;
6399
6437
  this.now = options.now ?? Date.now;
@@ -6419,6 +6457,7 @@ class ModelViewerWidgetSession {
6419
6457
  return {
6420
6458
  baseURL: this.apiBaseUrl,
6421
6459
  headers: {
6460
+ ...this.appTypeHeaders,
6422
6461
  [WIDGET_SESSION_TOKEN_HEADER]: await this.getTokenForRequest()
6423
6462
  },
6424
6463
  signal
@@ -6427,10 +6466,12 @@ class ModelViewerWidgetSession {
6427
6466
  async recoverFromExpiry(signal) {
6428
6467
  try {
6429
6468
  await this.refresh();
6430
- } catch {
6469
+ } catch (error) {
6470
+ if (!this.apiKey) throw error;
6431
6471
  this.session = this.toSnapshot(await activateCoreArWidgetSession(this.mode, {
6432
6472
  baseURL: this.apiBaseUrl,
6433
6473
  headers: {
6474
+ ...this.appTypeHeaders,
6434
6475
  'x-api-key': this.apiKey
6435
6476
  },
6436
6477
  signal
@@ -6445,7 +6486,12 @@ class ModelViewerWidgetSession {
6445
6486
  }, {
6446
6487
  baseURL: this.apiBaseUrl,
6447
6488
  headers: {
6448
- 'x-api-key': this.apiKey
6489
+ ...this.appTypeHeaders,
6490
+ ...this.apiKey ? {
6491
+ 'x-api-key': this.apiKey
6492
+ } : {
6493
+ [WIDGET_SESSION_TOKEN_HEADER]: this.session.token
6494
+ }
6449
6495
  }
6450
6496
  }).then((data)=>this.toSnapshot(parseWidgetSession(data))).then((session)=>{
6451
6497
  this.session = session;
@@ -6462,7 +6508,28 @@ class ModelViewerWidgetSession {
6462
6508
  };
6463
6509
  }
6464
6510
  }
6465
- async function createModelViewerWidgetSession({ apiBaseUrl, apiKey, initialWidgetSessionToken, mode, signal }) {
6511
+ async function createModelViewerWidgetSession({ apiBaseUrl, apiKey, appType, initialWidgetSessionToken, mode, signal }) {
6512
+ if (initialWidgetSessionToken && !apiKey) try {
6513
+ const exchanged = await publicApiControllerIssueWidgetToken({
6514
+ mode,
6515
+ token: initialWidgetSessionToken
6516
+ }, {
6517
+ baseURL: apiBaseUrl,
6518
+ headers: {
6519
+ ...getAppTypeHeaders(appType),
6520
+ [WIDGET_SESSION_TOKEN_HEADER]: initialWidgetSessionToken
6521
+ },
6522
+ signal
6523
+ });
6524
+ return new ModelViewerWidgetSession(parseWidgetSession(exchanged), {
6525
+ apiBaseUrl,
6526
+ apiKey,
6527
+ appType,
6528
+ mode
6529
+ });
6530
+ } catch (error) {
6531
+ if (signal?.aborted) throw error;
6532
+ }
6466
6533
  if (initialWidgetSessionToken) return new ModelViewerWidgetSession({
6467
6534
  expiresIn: ASSUMED_HANDOFF_SESSION_TTL_SECONDS,
6468
6535
  sid: initialWidgetSessionToken,
@@ -6470,11 +6537,13 @@ async function createModelViewerWidgetSession({ apiBaseUrl, apiKey, initialWidge
6470
6537
  }, {
6471
6538
  apiBaseUrl,
6472
6539
  apiKey,
6540
+ appType,
6473
6541
  mode
6474
6542
  });
6475
6543
  const activationData = await activateCoreArWidgetSession(mode, {
6476
6544
  baseURL: apiBaseUrl,
6477
6545
  headers: {
6546
+ ...getAppTypeHeaders(appType),
6478
6547
  'x-api-key': apiKey
6479
6548
  },
6480
6549
  signal
@@ -6482,6 +6551,7 @@ async function createModelViewerWidgetSession({ apiBaseUrl, apiKey, initialWidge
6482
6551
  return new ModelViewerWidgetSession(activationData, {
6483
6552
  apiBaseUrl,
6484
6553
  apiKey,
6554
+ appType,
6485
6555
  mode
6486
6556
  });
6487
6557
  }
@@ -6511,6 +6581,7 @@ async function sendModelViewerWidgetEvent(session, event, options = {}) {
6511
6581
  body: JSON.stringify({
6512
6582
  event,
6513
6583
  objectId: options.objectId ?? void 0,
6584
+ page_path: currentPagePath(),
6514
6585
  timestamp: new Date().toISOString(),
6515
6586
  variantId: options.variantId ?? void 0
6516
6587
  }),
@@ -6537,12 +6608,13 @@ async function issueWidgetHandoffCode(session, params) {
6537
6608
  expiresIn: data.expiresIn
6538
6609
  };
6539
6610
  }
6540
- async function resolveWidgetHandoffCode(apiBaseUrl, code) {
6611
+ async function resolveWidgetHandoffCode(apiBaseUrl, code, appType) {
6541
6612
  try {
6542
6613
  const data = await widgetHandoffControllerResolveHandoff({
6543
6614
  code
6544
6615
  }, {
6545
- baseURL: apiBaseUrl
6616
+ baseURL: apiBaseUrl,
6617
+ headers: getAppTypeHeaders(appType)
6546
6618
  });
6547
6619
  if (!data?.apiKey || !data.token) return null;
6548
6620
  return {
@@ -6558,6 +6630,10 @@ function parseWidgetSession(value) {
6558
6630
  if (!session || 'object' != typeof session || 'string' != typeof session.token || !session.token.trim() || 'string' != typeof session.sid || !session.sid.trim() || 'number' != typeof session.expiresIn || !Number.isFinite(session.expiresIn) || session.expiresIn <= 0) throw new TypeError('Invalid widget session response');
6559
6631
  return session;
6560
6632
  }
6633
+ function currentPagePath(locationRef = globalThis.location) {
6634
+ const path = locationRef?.pathname;
6635
+ return 'string' == typeof path && path.startsWith('/') ? path : void 0;
6636
+ }
6561
6637
  const MODEL_VIEWER_URL = "https://integrations.vizbl.com/model-viewer";
6562
6638
  const AR_DEVICE_REQUIREMENTS_DISCLAIMER = {
6563
6639
  en: '*Requires AR-supported device and browser. We recommend iOS 18 or later with Safari, or Android 7.0 or later with Chrome.',
@@ -6597,6 +6673,7 @@ function useObjectData(tinuuid, initialHid, options) {
6597
6673
  {
6598
6674
  apiBaseUrl: options.apiBaseUrl ?? null,
6599
6675
  apiKey: options.apiKey,
6676
+ appType: options.appType ?? null,
6600
6677
  handoffCode: options.handoffCode ?? null,
6601
6678
  initialHid: initialHid ?? null,
6602
6679
  initialWidgetSessionToken: options.initialWidgetSessionToken ?? null,
@@ -6609,12 +6686,13 @@ function useObjectData(tinuuid, initialHid, options) {
6609
6686
  refetchOnMount: 'always',
6610
6687
  queryFn: async ({ signal })=>{
6611
6688
  if (options.handoffCode) {
6612
- const resolved = readHandoffResolution(options.handoffCode) ?? await resolveWidgetHandoffCode(options.apiBaseUrl ?? window.location.origin, options.handoffCode);
6689
+ const resolved = readHandoffResolution(options.handoffCode) ?? await resolveWidgetHandoffCode(options.apiBaseUrl ?? window.location.origin, options.handoffCode, options.appType);
6613
6690
  if (!resolved) throw new WidgetHandoffExpiredError();
6614
6691
  rememberHandoffResolution(options.handoffCode, resolved);
6615
6692
  const widgetSession = await createModelViewerWidgetSession({
6616
6693
  apiBaseUrl: options.apiBaseUrl,
6617
6694
  apiKey: resolved.apiKey,
6695
+ appType: options.appType,
6618
6696
  initialWidgetSessionToken: resolved.token,
6619
6697
  mode: options.mode,
6620
6698
  signal
@@ -6625,30 +6703,38 @@ function useObjectData(tinuuid, initialHid, options) {
6625
6703
  widgetSession
6626
6704
  };
6627
6705
  }
6628
- if (!options.apiKey) {
6706
+ const readAnonymously = async ()=>{
6629
6707
  const object = await readObject({
6630
6708
  tinuuid
6631
6709
  }, {
6632
6710
  baseURL: options.apiBaseUrl,
6711
+ headers: getAppTypeHeaders(options.appType),
6633
6712
  signal
6634
6713
  });
6635
6714
  return {
6636
6715
  object,
6637
6716
  widgetSession: null
6638
6717
  };
6639
- }
6640
- const widgetSession = await createModelViewerWidgetSession({
6641
- apiBaseUrl: options.apiBaseUrl,
6642
- apiKey: options.apiKey,
6643
- initialWidgetSessionToken: options.initialWidgetSessionToken,
6644
- mode: options.mode,
6645
- signal
6646
- });
6647
- const object = await fetchModelViewerObject(tinuuid, initialHid, widgetSession, signal);
6648
- return {
6649
- object,
6650
- widgetSession
6651
6718
  };
6719
+ if (!options.apiKey && !options.initialWidgetSessionToken) return readAnonymously();
6720
+ try {
6721
+ const widgetSession = await createModelViewerWidgetSession({
6722
+ apiBaseUrl: options.apiBaseUrl,
6723
+ apiKey: options.apiKey,
6724
+ appType: options.appType,
6725
+ initialWidgetSessionToken: options.initialWidgetSessionToken,
6726
+ mode: options.mode,
6727
+ signal
6728
+ });
6729
+ const object = await fetchModelViewerObject(tinuuid, initialHid, widgetSession, signal);
6730
+ return {
6731
+ object,
6732
+ widgetSession
6733
+ };
6734
+ } catch (error) {
6735
+ if (options.apiKey || signal.aborted || 401 !== getErrorResponseStatus(error)) throw error;
6736
+ return readAnonymously();
6737
+ }
6652
6738
  }
6653
6739
  });
6654
6740
  const object = data?.object ?? null;
@@ -6716,6 +6802,7 @@ function ModelViewerProvider({ children, tinuuid, apiKey, appClipBundleId, arExp
6716
6802
  const { object, material, error, isLoading, setMaterialHid: setObjectMaterialHid, widgetSession } = useObjectData(tinuuid, hid, {
6717
6803
  apiBaseUrl,
6718
6804
  apiKey,
6805
+ appType: 'ru' === lang ? 'arizo' : 'vizbl',
6719
6806
  handoffCode,
6720
6807
  initialWidgetSessionToken,
6721
6808
  mode
@@ -6834,6 +6921,7 @@ function ModelViewerProvider({ children, tinuuid, apiKey, appClipBundleId, arExp
6834
6921
  widgetSession
6835
6922
  ]);
6836
6923
  const contextValue = external_react_useMemo(()=>({
6924
+ apiBaseUrl,
6837
6925
  apiKey,
6838
6926
  appClipBundleId,
6839
6927
  arExperience,
@@ -6865,6 +6953,7 @@ function ModelViewerProvider({ children, tinuuid, apiKey, appClipBundleId, arExp
6865
6953
  setModelViewerEl,
6866
6954
  trackEvent
6867
6955
  }), [
6956
+ apiBaseUrl,
6868
6957
  apiKey,
6869
6958
  appClipBundleId,
6870
6959
  arExperience,
@@ -6978,7 +7067,20 @@ async function launchArOverride(launchAr, target) {
6978
7067
  return false;
6979
7068
  }
6980
7069
  }
6981
- function buildModelViewerHandoffUrl({ apiKey, arExperience, baseUrl, hid, mode, tinuuid, widgetSessionToken }) {
7070
+ const AR_TAP_WINDOW_MS = 3000;
7071
+ function withinTapWindow(promise, windowMs = AR_TAP_WINDOW_MS) {
7072
+ return new Promise((resolve, reject)=>{
7073
+ const timer = setTimeout(()=>resolve(null), windowMs);
7074
+ promise.then((value)=>{
7075
+ clearTimeout(timer);
7076
+ resolve(value);
7077
+ }, (error)=>{
7078
+ clearTimeout(timer);
7079
+ reject(error);
7080
+ });
7081
+ });
7082
+ }
7083
+ function buildModelViewerHandoffUrl({ apiKey, arExperience, baseUrl, hid, lang, mode, tinuuid, widgetSessionToken }) {
6982
7084
  if (!baseUrl || !tinuuid) return null;
6983
7085
  let url;
6984
7086
  try {
@@ -6992,6 +7094,7 @@ function buildModelViewerHandoffUrl({ apiKey, arExperience, baseUrl, hid, mode,
6992
7094
  else url.searchParams.delete('ar-experience');
6993
7095
  if (hid) url.searchParams.set('hid', hid);
6994
7096
  else url.searchParams.delete('hid');
7097
+ setLang(url, lang);
6995
7098
  const fragment = new URLSearchParams(url.hash.slice(1));
6996
7099
  if (apiKey) fragment.set('api-key', apiKey);
6997
7100
  else fragment.delete('api-key');
@@ -7000,7 +7103,7 @@ function buildModelViewerHandoffUrl({ apiKey, arExperience, baseUrl, hid, mode,
7000
7103
  url.hash = fragment.toString();
7001
7104
  return url.toString();
7002
7105
  }
7003
- function buildModelViewerHandoffCodeUrl({ arExperience, baseUrl, code, hid, mode, tinuuid }) {
7106
+ function buildModelViewerHandoffCodeUrl({ arExperience, baseUrl, code, hid, lang, mode, tinuuid }) {
7004
7107
  if (!baseUrl || !tinuuid) return null;
7005
7108
  let url;
7006
7109
  try {
@@ -7014,6 +7117,7 @@ function buildModelViewerHandoffCodeUrl({ arExperience, baseUrl, code, hid, mode
7014
7117
  else url.searchParams.delete('ar-experience');
7015
7118
  if (hid) url.searchParams.set('hid', hid);
7016
7119
  else url.searchParams.delete('hid');
7120
+ setLang(url, lang);
7017
7121
  const fragment = new URLSearchParams(url.hash.slice(1));
7018
7122
  if (code) fragment.set('code', code);
7019
7123
  else fragment.delete('code');
@@ -7023,9 +7127,14 @@ function buildModelViewerHandoffCodeUrl({ arExperience, baseUrl, code, hid, mode
7023
7127
  function handoffCodeReissueDelayMs(expiresInSeconds) {
7024
7128
  return Math.round(1000 * expiresInSeconds * 2 / 3);
7025
7129
  }
7130
+ function setLang(url, lang) {
7131
+ if (lang) url.searchParams.set('lang', lang);
7132
+ else url.searchParams.delete('lang');
7133
+ }
7026
7134
  const EVENT_FLUSH_TIMEOUT_MS = 300;
7135
+ const LAUNCH_TOKEN_TIMEOUT_MS = 800;
7027
7136
  const HANDOFF_CODE_RETRY_MS = 15000;
7028
- async function issueHandoffQrCode(widgetSession, { arExperience, baseUrl, hid, mode, tinuuid }) {
7137
+ async function issueHandoffQrCode(widgetSession, { arExperience, baseUrl, hid, lang, mode, tinuuid }) {
7029
7138
  const { code, expiresIn } = await issueWidgetHandoffCode(widgetSession, {
7030
7139
  arExperience,
7031
7140
  hid,
@@ -7036,6 +7145,7 @@ async function issueHandoffQrCode(widgetSession, { arExperience, baseUrl, hid, m
7036
7145
  baseUrl,
7037
7146
  code,
7038
7147
  hid,
7148
+ lang,
7039
7149
  mode,
7040
7150
  tinuuid
7041
7151
  });
@@ -7054,7 +7164,7 @@ async function flushWidgetEvent(eventRequest) {
7054
7164
  });
7055
7165
  }
7056
7166
  function AR({ disabled, onClick, onBeforeActivateAr, size = 'lg', ...props }) {
7057
- const { apiKey, appClipBundleId, arExperience, containerEl, handoffUrl, isLoading, isModelLoading, lang, launchAr, material, mode, modelViewerEl, object, tinuuid, trackEvent, widgetSession } = useModelViewer();
7167
+ const { apiBaseUrl, apiKey, appClipBundleId, arExperience, containerEl, handoffUrl, isLoading, isModelLoading, lang, launchAr, material, mode, modelViewerEl, object, tinuuid, trackEvent, widgetSession } = useModelViewer();
7058
7168
  const [open, setOpen] = useState(false);
7059
7169
  const [qrState, setQrState] = useState({
7060
7170
  status: 'loading'
@@ -7063,12 +7173,33 @@ function AR({ disabled, onClick, onBeforeActivateAr, size = 'lg', ...props }) {
7063
7173
  const widgetSessionSid = widgetSession?.sid;
7064
7174
  const isDisabled = isLoading || isModelLoading || null === material;
7065
7175
  const arTarget = external_react_useMemo(()=>({
7176
+ apiBaseUrl,
7177
+ apiKey: widgetSession?.apiKey || apiKey || void 0,
7066
7178
  hid: material?.hid,
7179
+ lang,
7180
+ name: object?.name ?? void 0,
7067
7181
  tinuuid: object?.tinuuid ?? tinuuid
7068
7182
  }), [
7183
+ apiBaseUrl,
7184
+ apiKey,
7185
+ lang,
7069
7186
  material?.hid,
7187
+ object?.name,
7070
7188
  object?.tinuuid,
7071
- tinuuid
7189
+ tinuuid,
7190
+ widgetSession
7191
+ ]);
7192
+ const launchHostAr = useCallback(async ()=>{
7193
+ if (!launchAr) return false;
7194
+ const widgetSessionToken = widgetSession ? await withinTapWindow(widgetSession.getTokenForRequest(), LAUNCH_TOKEN_TIMEOUT_MS).catch(()=>null) ?? widgetSession.token : void 0;
7195
+ return launchArOverride(launchAr, {
7196
+ ...arTarget,
7197
+ widgetSessionToken
7198
+ });
7199
+ }, [
7200
+ arTarget,
7201
+ launchAr,
7202
+ widgetSession
7072
7203
  ]);
7073
7204
  const directArExperience = resolveDirectArExperience(mode, arExperience);
7074
7205
  const directArLaunchOptions = external_react_useMemo(()=>{
@@ -7116,7 +7247,7 @@ function AR({ disabled, onClick, onBeforeActivateAr, size = 'lg', ...props }) {
7116
7247
  const timer = window.setTimeout(()=>{
7117
7248
  (async ()=>{
7118
7249
  if (autoLaunchKeyRef.current !== launchKey) return;
7119
- if (await launchArOverride(launchAr, arTarget)) return;
7250
+ if (await launchHostAr()) return;
7120
7251
  await flushWidgetEvent(trackEvent('ar_launched'));
7121
7252
  if (autoLaunchKeyRef.current === launchKey) launchDirectAr(directArLaunchOptions);
7122
7253
  })();
@@ -7126,10 +7257,9 @@ function AR({ disabled, onClick, onBeforeActivateAr, size = 'lg', ...props }) {
7126
7257
  if (autoLaunchKeyRef.current === launchKey) autoLaunchKeyRef.current = null;
7127
7258
  };
7128
7259
  }, [
7129
- arTarget,
7130
7260
  directArLaunchOptions,
7131
7261
  isLoading,
7132
- launchAr,
7262
+ launchHostAr,
7133
7263
  material?.hid,
7134
7264
  mode,
7135
7265
  trackEvent,
@@ -7149,6 +7279,7 @@ function AR({ disabled, onClick, onBeforeActivateAr, size = 'lg', ...props }) {
7149
7279
  arExperience,
7150
7280
  baseUrl,
7151
7281
  hid: material?.hid,
7282
+ lang,
7152
7283
  mode,
7153
7284
  tinuuid: resolvedTinuuid
7154
7285
  });
@@ -7165,6 +7296,7 @@ function AR({ disabled, onClick, onBeforeActivateAr, size = 'lg', ...props }) {
7165
7296
  arExperience,
7166
7297
  baseUrl,
7167
7298
  hid: material?.hid,
7299
+ lang,
7168
7300
  mode,
7169
7301
  tinuuid: resolvedTinuuid
7170
7302
  });
@@ -7179,6 +7311,7 @@ function AR({ disabled, onClick, onBeforeActivateAr, size = 'lg', ...props }) {
7179
7311
  arExperience,
7180
7312
  baseUrl,
7181
7313
  hid: material?.hid,
7314
+ lang,
7182
7315
  mode,
7183
7316
  tinuuid: resolvedTinuuid,
7184
7317
  widgetSessionToken: sessionToken
@@ -7202,6 +7335,7 @@ function AR({ disabled, onClick, onBeforeActivateAr, size = 'lg', ...props }) {
7202
7335
  arExperience,
7203
7336
  baseUrl: handoffUrl || MODEL_VIEWER_URL,
7204
7337
  hid: material?.hid,
7338
+ lang,
7205
7339
  mode,
7206
7340
  tinuuid: object?.tinuuid ?? tinuuid
7207
7341
  });
@@ -7223,6 +7357,7 @@ function AR({ disabled, onClick, onBeforeActivateAr, size = 'lg', ...props }) {
7223
7357
  }, [
7224
7358
  arExperience,
7225
7359
  handoffUrl,
7360
+ lang,
7226
7361
  material?.hid,
7227
7362
  mode,
7228
7363
  object?.tinuuid,
@@ -7233,7 +7368,7 @@ function AR({ disabled, onClick, onBeforeActivateAr, size = 'lg', ...props }) {
7233
7368
  ]);
7234
7369
  const handleClick = async ()=>{
7235
7370
  if (!modelViewerEl || isDisabled) return;
7236
- if (await launchArOverride(launchAr, arTarget)) return void onBeforeActivateAr?.();
7371
+ if (await launchHostAr()) return void onBeforeActivateAr?.();
7237
7372
  if (directArLaunchOptions && isMobileArDevice()) {
7238
7373
  onBeforeActivateAr?.();
7239
7374
  await flushWidgetEvent(trackEvent('ar_launched'));
@@ -8070,7 +8205,7 @@ function ModelViewerRoot({ tinuuid, apiKey, appClipBundleId = 'com.us.vizbl.Clip
8070
8205
  })
8071
8206
  });
8072
8207
  }
8073
- function ModelViewerDialog({ tinuuid, apiKey, apiBaseUrl, appClipBundleId, initialWidgetSessionToken, handoffUrl, hid, lang = 'en', mode = 'ar-viewer', sizeType, dimensionAxes, cameraOrbit, minCameraOrbit, maxCameraOrbit, ...props }) {
8208
+ function ModelViewerDialog({ tinuuid, apiKey, apiBaseUrl, appClipBundleId, initialWidgetSessionToken, handoffUrl, hid, lang = 'en', launchAr, mode = 'ar-viewer', sizeType, dimensionAxes, cameraOrbit, minCameraOrbit, maxCameraOrbit, ...props }) {
8074
8209
  const [container, setContainer] = useState(null);
8075
8210
  return /*#__PURE__*/ jsxs(Fragment, {
8076
8211
  children: [
@@ -8111,6 +8246,7 @@ function ModelViewerDialog({ tinuuid, apiKey, apiBaseUrl, appClipBundleId, initi
8111
8246
  lang: lang,
8112
8247
  handoffUrl: handoffUrl,
8113
8248
  hid: hid,
8249
+ launchAr: launchAr,
8114
8250
  sizeType: sizeType,
8115
8251
  dimensionAxes: dimensionAxes,
8116
8252
  cameraOrbit: cameraOrbit,
@@ -8202,4 +8338,5 @@ function ModelViewer({ tinuuid, apiKey, appClipBundleId, arExperience, hid, apiB
8202
8338
  ]
8203
8339
  });
8204
8340
  }
8205
- export { ModelViewer, ModelViewerDialog };
8341
+ var __webpack_exports__AR_TAP_WINDOW_MS = 3000;
8342
+ export { ModelViewer, ModelViewerDialog, isIOSDevice, isMobileArDevice, withinTapWindow, __webpack_exports__AR_TAP_WINDOW_MS as AR_TAP_WINDOW_MS };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@neurodyn/react-model-viewer",
3
3
  "type": "module",
4
- "version": "0.1.1",
4
+ "version": "0.2.1",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },