Skip to content

Commit df4f703

Browse files
global cdn provider to be able to change cdn globally. Docs for app developers on how to provide good integration layer to 3D Bits.
1 parent 83b2416 commit df4f703

File tree

8 files changed

+553
-11
lines changed

8 files changed

+553
-11
lines changed
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
---
2+
sidebar_position: 2
3+
title: "Third Party App Integrations"
4+
sidebar_label: Third Party App Integrations
5+
description: If you are the developer of third party option application, we'd love to collaborate and make our app compatible with your solution. This guide defines some ways how this is possible.
6+
tags: [shopify, 3d-bits]
7+
---
8+
import Admonition from '@theme/Admonition';
9+
10+
## Overview
11+
12+
We'd love to collaborate with product options app developers to create seamless integrations between our apps. This guide explains how implementing a standardized event-based communication layer can benefit our mutual customers who want to build advanced 3D product configurators.
13+
14+
By working together, we can provide customers with stable, reliable experiences where 3D visualizations respond smoothly to product option changes—without either of us worrying about breaking integrations during routine updates. This strengthens the entire ecosystem of advanced product configurators on Shopify and creates better experiences for the customers we both serve.
15+
16+
## The Current Integration Challenge
17+
18+
Currently, 3D Bits integrates with third-party product options apps by monitoring HTML form inputs. While this works in many cases, it relies on stable `name` attributes and can be affected by DOM changes from framework updates. A native event-based approach provides a more reliable integration layer that's independent of your internal implementation.
19+
20+
## The Solution: Native Event-Based Integration
21+
22+
We propose establishing an official communication layer using **standard Browser Custom Events**. This approach provides a stable, versioned API contract between your app and 3D Bits, ensuring integrations remain functional regardless of your internal implementation or DOM structure changes.
23+
24+
### How It Works
25+
26+
When a mutual customer builds a 3D configurator using both your product options app and 3D Bits, they would:
27+
28+
1. **Select Your App**: In the 3D Bits settings, they choose your app's name from the ["Input Collection Mode"](../tutorials/getting-started/common-settings#input-collection-mode) dropdown
29+
2. **Automatic Subscription**: 3D Bits adds a listener to the global `window` object
30+
3. **Real-Time Synchronization**: As users interact with your product options, your app dispatches a standard event. 3D Bits captures this payload and updates the 3D model immediately
31+
32+
This creates a plug-and-play experience that requires no technical configuration from the end user.
33+
34+
## Technical Implementation
35+
36+
### Architecture: Native CustomEvent API
37+
38+
Instead of creating a proprietary global object (which is prone to conflicts and security issues), we use the standard [Web API CustomEvent interface](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent).
39+
40+
<Admonition type="info" title="Why Native CustomEvents?">
41+
Using standard browser events (`window.dispatchEvent`) is superior to a custom "Event Bus" object because:
42+
- **Decoupling**: Your app does not need to check if 3D Bits is present or loaded. You simply fire the message into the `window`.
43+
- **Security**: There is no shared global state or mutable listener array that other malicious scripts can easily hijack or overwrite.
44+
- **Shadow DOM Support**: Native events can be configured to bubble through Shadow DOM boundaries, supporting encapsulated apps.
45+
</Admonition>
46+
47+
### Implementation Guide
48+
49+
Your app simply needs to dispatch a `CustomEvent` specifically named `3dbits.productOptions.changed` whenever a user modifies an option.
50+
51+
#### The Dispatch Code
52+
53+
Add this logic to your app's change handler (e.g., inside your React `useEffect`, Vue watcher, or vanilla JS `change` listener):
54+
55+
```javascript
56+
// 1. Construct the payload
57+
const payload = {
58+
app: 'YourAppName', // Your unique app identifier
59+
version: '1.0.0', // Schema version
60+
61+
// IMPORTANT: Use a flat structure - do NOT nest related options
62+
options: {
63+
'Color': 'Red',
64+
'Size': 'XL',
65+
'Material': 'Walnut Wood',
66+
'Width': 100,
67+
'Height': 75,
68+
'Depth': 50,
69+
'LED Lighting': true, // Boolean for enabled/disabled features
70+
'Wireless Charging': false,
71+
'Part 1': true, // Flat structure - keep related options separate
72+
'Part 1 Quantity': 2, // Rather than nesting under 'Part 1'
73+
'Part 2': false,
74+
'Part 2 Quantity': 0,
75+
'Engraving Text': 'Custom Name',
76+
'Gift Wrapping': true
77+
},
78+
79+
// Optional Metadata
80+
metadata: {
81+
timestamp: Date.now(),
82+
productId: '1234567890',
83+
variantId: '9876543210',
84+
currency: 'USD',
85+
totalPrice: 599.99
86+
}
87+
};
88+
89+
// 2. Create the Native CustomEvent
90+
// Note: All data must be passed within the 'detail' property
91+
const event = new CustomEvent('3dbits.productOptions.changed', {
92+
detail: payload,
93+
bubbles: true, // Allows the event to bubble up the DOM
94+
composed: true // Crucial: Allows event to pass through Shadow DOM boundaries
95+
});
96+
97+
// 3. Dispatch to the window
98+
window.dispatchEvent(event);
99+
```
100+
101+
## Security Considerations
102+
103+
This approach is secure because:
104+
105+
1. **No Code Execution**: 3D Bits never executes code provided by your app; it only parses the JSON data in the `detail` property
106+
2. **Read-Only Integration**: 3D Bits acts purely as a subscriber and cannot modify your app's state
107+
3. **Isolation**: Because we use the browser's native event loop, an error in the 3D Bits listener will not crash your application, and vice-versa
108+
109+
## Event Schema Specification
110+
111+
<Admonition type="note" title="Flexible Integration">
112+
While we recommend the schema below for consistency across integrations, we understand that your app may already have its own event system or preferred data structure. If you prefer to use a different event name or structure your data differently, **we're happy to adapt**. Simply [contact us](#contact-information) with your proposed event structure, and we'll add support for your specific implementation in 3D Bits.
113+
</Admonition>
114+
115+
### Event Name
116+
117+
Use the exact namespace:
118+
119+
```javascript
120+
3dbits.productOptions.changed
121+
```
122+
123+
### Event Data Structure (TypeScript)
124+
125+
The `detail` property of the event should follow this interface:
126+
127+
```typescript
128+
interface ProductOptionsEventDetail {
129+
// Identifying information
130+
app: string; // Your app's unique identifier
131+
version: string; // Use '1.0.0'
132+
133+
// Option data
134+
// Key: Human Readable Name (displayed to user)
135+
// Value: The selected value
136+
options: {
137+
[optionName: string]: string | number | boolean;
138+
};
139+
140+
// Optional metadata helpful for advanced logic
141+
metadata?: {
142+
timestamp?: number;
143+
productId?: string;
144+
variantId?: string;
145+
currency?: string;
146+
totalPrice?: number;
147+
};
148+
}
149+
```
150+
151+
<Admonition type="info" title="Important: Option Naming Requirements">
152+
**Option names in the `options` object should be the user-facing names.** 3D Bits maps the 3D configuration based on the labels the merchant sees in their 3D Bits debug mode. Also please make sure that these option names remain unaffected by translations.
153+
154+
**Examples:**
155+
-**Good**: `"options": { "Material": "Oak" }`
156+
-**Bad**: `"options": { "mat_opt_id_88": "Oak" }` (Internal Database IDs)
157+
-**Bad**: `"options": { "material-selection": "Oak" }` (CSS Classes/Slugs)
158+
159+
If your app relies on IDs internally, please map them to the display label before dispatching the event.
160+
</Admonition>
161+
162+
## When to Fire Events
163+
164+
Your app should dispatch the event:
165+
166+
1. **On Initial Load**: Once your app has calculated the default selections
167+
2. **On User Interaction**: Immediately when any option value changes
168+
3. **Range Sliders**: For continuous inputs like range sliders (e.g., dimensions), either:
169+
- **Debounce** the dispatch by 100-150ms during active dragging, OR
170+
- **Fire only on drag end** (mouseup/touchend events) to avoid excessive event firing while the user is still adjusting the value
171+
172+
## Benefits for Our Mutual Users
173+
174+
### Stability and Reliability
175+
176+
- **No More Breaking Changes**: Updates to your app's DOM structure won't break 3D integrations
177+
- **Predictable Behavior**: A versioned schema ensures forward compatibility
178+
- **Reduced Support Burden**: Fewer integration issues mean fewer support tickets
179+
180+
### Enhanced Product Offerings
181+
182+
- **Competitive Advantage**: Stand out as a developer who supports advanced 3D use cases
183+
- **Ecosystem Growth**: Enable integrations not just with 3D Bits but with any other script listening for standard events
184+
185+
## Testing Your Integration
186+
187+
### Debug Mode
188+
189+
When users enable [Debug Mode](../tutorials/getting-started/common-settings#enable-debug-mode) in 3D Bits, they will see the incoming event stream in the 3D Bits UI.
190+
191+
### Test Scenarios
192+
193+
We recommend testing:
194+
195+
1. **Initial Load**: Verify the event fires with default selections immediately after page load
196+
2. **Single Changes**: Test each option type (radio, checkbox, select, text)
197+
3. **Browser Console**: You can verify your own integration by opening the DevTools console and typing:
198+
199+
```javascript
200+
window.addEventListener('3dbits.productOptions.changed', e => console.log(e.detail));
201+
```
202+
203+
## Alternative Approach: Stable HTML Form Attributes
204+
205+
We understand that implementing a JavaScript event system may not be feasible for all apps immediately. If you cannot implement the event dispatcher, we recommend following HTML form best practices to ensure stable integrations.
206+
207+
### Use Descriptive, Stable `name` Attributes
208+
209+
When 3D Bits falls back to automatic form detection, it relies on the `name` attribute.
210+
211+
<Admonition type="tip" title="Best Practices for Form Attributes">
212+
To maintain stable integrations without breaking changes, we recommend:
213+
214+
- **Use Descriptive Names**: Consider using `name="color"` or `name="material"`
215+
- **Avoid Auto-Generated IDs**: Try to avoid names like `name="field_12345_xyz"`. If you re-deploy your app and that ID changes, the customer's 3D configuration may break
216+
- **Semantic Values**: Consider using `value="red"` rather than internal IDs like `value="opt_id_99"`
217+
</Admonition>
218+
219+
### Example: Good HTML Structure
220+
221+
```html
222+
<!-- ✅ GOOD: Stable, descriptive attributes -->
223+
<input type="radio" name="material" value="wood" checked />
224+
<input type="checkbox" name="led_lighting" value="true" />
225+
```
226+
227+
### Example: Poor HTML Structure
228+
229+
```html
230+
<!-- ❌ BAD: Unstable names that break integrations -->
231+
<input type="radio" name="shopify_app_layer_12837" value="88374" />
232+
<div data-value="wood" onclick="...">Custom Div (No Input Tag)</div>
233+
```
234+
235+
[**Learn More about Integration**](./integration.md)
236+
237+
## Next Steps
238+
239+
If you're interested in exploring this integration:
240+
241+
1. **Review the Implementation**: Consider how this event pattern might fit into your app's architecture
242+
2. **Test Locally**: Try dispatching the events in a development environment
243+
3. **Reach Out**: We'd love to hear your feedback or discuss how we can adapt to your existing event structure
244+
245+
We're eager to collaborate and make this integration work smoothly for our mutual customers.
246+
247+
### What We Can Offer
248+
249+
Once the integration is live, we'd be happy to:
250+
251+
- **List Your App**: Feature your app in our [third-party apps documentation](../tutorials/getting-started/apps-for-3d-bits) with a note about the native integration support
252+
- **Provide Examples**: Include your app in our integration examples to help customers get started
253+
- **Share Knowledge**: Document any learnings that could help other developers
254+
255+
Our goal is simply to make it easier for customers using both our apps to build great 3D configurators without worrying about breaking changes.
256+
257+
## Contact Information
258+
259+
260+
261+
We appreciate you taking the time to consider this integration approach and look forward to potentially working together.
262+

docs/learn/3d-bits/tutorials/getting-started/apps-for-3d-bits.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ These apps are only necessary when:
1313
- The default Shopify variant system cannot address your configuration needs
1414
- You require advanced conditional logic or dynamic pricing
1515
- You need more sophisticated option management
16+
- You do not have access to professional developers who could program these product option forms
1617

1718
## Important Disclaimers
1819

packages/dev/babylonjs/lib/api/bitbybit/babylon/scene.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Context } from "../../context";
22
import * as BABYLON from "@babylonjs/core";
33
import * as GUI from "@babylonjs/gui";
44
import * as Inputs from "../../inputs";
5+
import { GlobalCDNProvider } from "@bitbybit-dev/base";
56

67

78
export class BabylonScene {
@@ -326,14 +327,14 @@ export class BabylonScene {
326327
let texture: BABYLON.CubeTexture | BABYLON.HDRCubeTexture;
327328

328329
if (inputs.skybox === Inputs.Base.skyboxEnum.default) {
329-
texture = new BABYLON.CubeTexture("https://cdn.jsdelivr.net/gh/bitbybit-dev/[email protected]/textures/skybox/default_skybox/skybox", this.context.scene);
330+
texture = new BABYLON.CubeTexture(GlobalCDNProvider.BITBYBIT_CDN_URL + "/textures/skybox/default_skybox/skybox", this.context.scene);
330331
} else if (inputs.skybox === Inputs.Base.skyboxEnum.greyGradient) {
331-
texture = new BABYLON.CubeTexture("https://cdn.jsdelivr.net/gh/bitbybit-dev/[email protected]/textures/skybox/grey_gradient/skybox", this.context.scene);
332+
texture = new BABYLON.CubeTexture(GlobalCDNProvider.BITBYBIT_CDN_URL + "/textures/skybox/grey_gradient/skybox", this.context.scene);
332333
} else if (inputs.skybox === Inputs.Base.skyboxEnum.clearSky) {
333-
texture = BABYLON.CubeTexture.CreateFromPrefilteredData("https://cdn.jsdelivr.net/gh/bitbybit-dev/[email protected]/textures/skybox/clear_sky/environment.env",
334+
texture = BABYLON.CubeTexture.CreateFromPrefilteredData(GlobalCDNProvider.BITBYBIT_CDN_URL + "/textures/skybox/clear_sky/environment.env",
334335
this.context.scene, false, false);
335336
} else if (inputs.skybox === Inputs.Base.skyboxEnum.city) {
336-
texture = BABYLON.CubeTexture.CreateFromPrefilteredData("https://cdn.jsdelivr.net/gh/bitbybit-dev/[email protected]/textures/skybox/city/environmentSpecular.env",
337+
texture = BABYLON.CubeTexture.CreateFromPrefilteredData(GlobalCDNProvider.BITBYBIT_CDN_URL + "/textures/skybox/city/environmentSpecular.env",
337338
this.context.scene, false, false);
338339
}
339340

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export class GlobalCDNProvider {
2+
static BITBYBIT_CDN_URL = "https://cdn.jsdelivr.net/gh/bitbybit-dev/[email protected]";
3+
}

packages/dev/base/lib/api/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from "./services";
2+
export * from "./GlobalCDNProvider";

packages/dev/occt/bitbybit-dev-occt/cdn.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import ocFullJS from "./bitbybit-dev-occt.js";
2+
import { GlobalCDNProvider } from "@bitbybit-dev/base";
23

34
const initOpenCascade = ({
45
mainJS = ocFullJS,
5-
mainWasm = "https://cdn.jsdelivr.net/gh/bitbybit-dev/[email protected]/wasm/bitbybit-dev-occt.f151efeb.wasm",
6+
mainWasm = GlobalCDNProvider.BITBYBIT_CDN_URL + "/wasm/bitbybit-dev-occt.f151efeb.wasm",
67
worker = undefined,
78
libs = [],
89
module = {},

0 commit comments

Comments
 (0)