@virtualdisplay.io/client 3.1.0

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/CHANGELOG.md ADDED
@@ -0,0 +1,69 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
+
5
+ ## [3.1.0](https://github.com/virtualdisplay-io/client/compare/v3.0.0...v3.1.0) (2025-06-25)
6
+
7
+ ### ⚠ BREAKING CHANGES
8
+
9
+ * Complete API redesign with the following changes:
10
+ - Removed isReady getter - use onReady callback instead
11
+ - Removed getQueueStatus method - internal implementation detail
12
+ - Removed toJSON from ModelNode - not needed for public API
13
+ - Complete restructure from core/ to src/ with domain-driven design
14
+ - New attribute-based API is now the only way to interact with 3D models
15
+ - Fire-and-forget messaging pattern for all operations
16
+ - JSON Schema validation for mapping configuration
17
+
18
+ Features:
19
+ - Simplified color configurator demo (300 lines → 160 lines)
20
+ - Bundled @virtualdisplay-io/logger package
21
+ - Domain-driven directory structure
22
+ - Proper error handling with error codes
23
+ - Event-driven architecture with EventBus
24
+ - onChange callbacks for real-time UI synchronization
25
+ - Comprehensive test coverage
26
+
27
+ Documentation:
28
+ - Complete README rewrite focusing on actual functionality
29
+ - Added terminology standards for consistent communication
30
+ - Removed unimplemented accessibility claims
31
+ - Clear separation between simple and configurable products
32
+
33
+ * fix: update lockfile after removing translator dependency
34
+
35
+ * docs: update README for v4.0 release
36
+
37
+ * fix: add GitHub registry configuration for @virtualdisplay-io scope
38
+
39
+ * fix: add npm authentication to bundle-size workflow and package permissions
40
+
41
+ * fix: use GITHUB_TOKEN for npm auth and add trailing slash to registry URL
42
+
43
+ * chore: update lockfile with fresh install
44
+
45
+ * fix: use GH_PAT instead of GITHUB_TOKEN for package authentication
46
+
47
+ - Replace GITHUB_TOKEN with GH_PAT in all workflows to avoid conflicts with GitHub's reserved token
48
+ - Update CI, release, and bundle-size workflows to use new token variable
49
+ - Add .env.act for local testing with act
50
+ - Required because GitHub's automatic GITHUB_TOKEN cannot access private organization packages
51
+
52
+ * fix: remove push trigger from bundle-size workflow
53
+
54
+ - Bundle size analysis should only run on pull requests
55
+ - Prevents duplicate runs and confusion about which branch is being tested
56
+ - Aligns with CI/CD best practices for PR-based workflows
57
+
58
+ ### ✨ Features
59
+
60
+ * v4.0.0 - simplified architecture and attribute-only API ([#41](https://github.com/virtualdisplay-io/client/issues/41)) ([dc649c1](https://github.com/virtualdisplay-io/client/commit/dc649c1b863b2427bf6e0350960c4d2e2a2dacb8))
61
+
62
+ ### 🐛 Bug Fixes
63
+
64
+ * add missing conventional-changelog-conventionalcommits dependency ([8a15743](https://github.com/virtualdisplay-io/client/commit/8a15743de3984c742ccb5de0537333f321108cd4))
65
+ * use NODE_AUTH_TOKEN for npm authentication ([#43](https://github.com/virtualdisplay-io/client/issues/43)) ([c148c6c](https://github.com/virtualdisplay-io/client/commit/c148c6cba6b6b4d1fc431ca5618ad2c3c26638c2))
66
+
67
+ ### 🔧 Maintenance
68
+
69
+ * trigger release workflow to test GH_PAT secret ([9b07140](https://github.com/virtualdisplay-io/client/commit/9b07140e01d72ccc3774f20733afd197f52c4cde))
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Virtualdisplay
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,540 @@
1
+ # Virtualdisplay client
2
+
3
+ TypeScript library for embedding interactive 3D product models in web
4
+ applications.
5
+
6
+ **Key features:**
7
+
8
+ - Simple attribute-based API for product configurators
9
+ - Fire-and-forget messaging with automatic state synchronization
10
+ - Zero external dependencies (all bundled)
11
+ - Works with any frontend framework or vanilla JavaScript
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install @virtualdisplay-io/client
17
+ # or
18
+ pnpm add @virtualdisplay-io/client
19
+ # or
20
+ yarn add @virtualdisplay-io/client
21
+ ```
22
+
23
+ ## What's new in v3.1
24
+
25
+ - Simplified message handling with unified event system
26
+ - JSON Schema validation using AJV
27
+ - Cleaner, more maintainable codebase
28
+ - Improved TypeScript types
29
+ - Better error messages with specific error codes
30
+
31
+ ## Quick start
32
+
33
+ ### Simple product (no options)
34
+
35
+ ```typescript
36
+ import { VirtualdisplayClient } from '@virtualdisplay-io/client';
37
+
38
+ // That's it! Your 3D model is now displayed
39
+ const client = new VirtualdisplayClient({
40
+ parent: '#product-container',
41
+ license: 'your-license-key',
42
+ model: 'statue-model',
43
+ });
44
+ ```
45
+
46
+ ### Configurable product (with options)
47
+
48
+ ```typescript
49
+ import { VirtualdisplayClient } from '@virtualdisplay-io/client';
50
+
51
+ // Create client instance
52
+ const client = new VirtualdisplayClient({
53
+ parent: '#product-container',
54
+ license: 'your-license-key',
55
+ model: 'sneaker',
56
+ });
57
+
58
+ // Map product options to 3D parts
59
+ client.setMapping({
60
+ attributes: [
61
+ {
62
+ name: 'Color',
63
+ values: [
64
+ { value: 'Red', nodeIds: ['laces_red', 'sole_red'], isSelected: true },
65
+ { value: 'Blue', nodeIds: ['laces_blue', 'sole_blue'] },
66
+ ],
67
+ },
68
+ ],
69
+ });
70
+
71
+ // Control via product options
72
+ client.getAttribute('Color')?.select('Red');
73
+
74
+ // Or store the attribute for multiple operations
75
+ const sizeAttribute = client.getAttribute('Size');
76
+ if (sizeAttribute) {
77
+ sizeAttribute.select('Large');
78
+ }
79
+ ```
80
+
81
+ ## How it works
82
+
83
+ The 3D server hosts the viewer in an iframe, keeping WebGL complexity isolated
84
+ from your application. The client library handles all communication via
85
+ postMessage and manages state locally using an attribute mapping system.
86
+
87
+ ```mermaid
88
+ graph LR
89
+ A[Your application] -->|API calls| B[VirtualdisplayClient]
90
+ B -->|postMessage| C[3D server iframe]
91
+ C -->|Events| B
92
+ B -->|State updates| A
93
+ ```
94
+
95
+ **Key principles:**
96
+
97
+ - **Iframe architecture**: 3D server loads the viewer independently
98
+ - **Fire-and-forget**: Send messages without waiting for confirmation
99
+ - **Attribute mapping**: Connect your product options to 3D model parts
100
+ - **State synchronization**: Client and 3D server stay in sync automatically
101
+ - **Domain-driven design**: Clean separation of concerns with simplified
102
+ architecture
103
+ - **Event-driven**: Loosely coupled components communicate via events
104
+
105
+ ## Product options
106
+
107
+ ### When do you need mapping
108
+
109
+ Before diving in, it's important to understand when you need attribute mapping:
110
+
111
+ **No mapping needed:**
112
+
113
+ - Static 3D models that always look the same
114
+ - Product showcases without options
115
+ - Architectural visualizations
116
+ - Art pieces or sculptures
117
+ - Single-configuration products
118
+
119
+ **Mapping required:**
120
+
121
+ - Products where users can change colors (different textures/materials per option)
122
+ - Products with size options (different 3D meshes per size)
123
+ - Modular products (parts that can be added/removed)
124
+ - Any product where different options show different 3D elements
125
+
126
+ ### Attribute mapping
127
+
128
+ The mapping system bridges your business logic with the 3D visualization. It
129
+ serves multiple purposes:
130
+
131
+ **Why mapping is essential:**
132
+
133
+ - The 3D server is generic - it doesn't know your specific product options
134
+ - Your business rules (stock, pricing, combinations) change independently from
135
+ the 3D model
136
+ - You control exactly which parts of the model are shown for each option
137
+ - You can group multiple 3D nodes (meshes, materials) into logical product
138
+ choices
139
+
140
+ **How it works:** The mapping connects your product options to specific parts of
141
+ the 3D model:
142
+
143
+ ```typescript
144
+ client.setMapping({
145
+ attributes: [
146
+ {
147
+ name: 'Color',
148
+ values: [
149
+ {
150
+ value: 'Red',
151
+ nodeIds: ['mat_red_sole', 'mat_red_laces', 'mat_red_logo'],
152
+ isSelected: true,
153
+ },
154
+ {
155
+ value: 'Blue',
156
+ nodeIds: ['mat_blue_sole', 'mat_blue_laces', 'mat_blue_logo'],
157
+ },
158
+ ],
159
+ },
160
+ {
161
+ name: 'Material',
162
+ values: [
163
+ { value: 'Leather', nodeIds: ['material_leather_upper'] },
164
+ {
165
+ value: 'Canvas',
166
+ nodeIds: ['material_canvas_upper'],
167
+ isSelected: true,
168
+ },
169
+ ],
170
+ },
171
+ ],
172
+ });
173
+ ```
174
+
175
+ **Structure explained:**
176
+
177
+ - `attribute` - A product feature like Color, Size, or Material
178
+ - `value` - A specific option like Red, Blue, Small, or Large
179
+ - `nodeIds` - The 3D model parts that represent this option
180
+ - `isSelected` - Whether this option is selected by default
181
+
182
+ **Example benefits:**
183
+
184
+ ```typescript
185
+ // Group related 3D parts into one logical choice
186
+ {
187
+ value: 'Red',
188
+ nodeIds: [
189
+ 'mesh_sole_red', // Red sole mesh
190
+ 'material_laces_red', // Red laces material
191
+ 'texture_logo_red', // Red logo texture
192
+ 'mesh_stitching_red' // Red stitching details
193
+ ]
194
+ }
195
+
196
+ // The server automatically handles showing Red parts
197
+ // and hiding Blue/Green parts when Red is selected
198
+ ```
199
+
200
+ **Key advantages:**
201
+
202
+ - Full control over what's visible without modifying the 3D model
203
+ - Business logic stays in your application, not in the 3D server
204
+ - Easy to update when products or availability changes
205
+ - Group complex 3D structures into simple user choices
206
+
207
+ **Need help with mapping?**
208
+
209
+ - We help you connect your product catalog to your 3D models
210
+ - Visual mapping tool coming soon to simplify this process
211
+ - Contact <support@virtualdisplay.io> for mapping assistance
212
+
213
+ ### Complete flow
214
+
215
+ Here's how the client, mapping system, and 3D server work together:
216
+
217
+ ```mermaid
218
+ sequenceDiagram
219
+ participant UI as Your UI
220
+ participant Client as VirtualdisplayClient
221
+ participant AttrService as AttributeService
222
+ participant EventBus as EventBus
223
+ participant MsgHandler as MessageHandler
224
+ participant Server as 3D server
225
+
226
+ Note over Client,Server: 0. Client initialization
227
+ UI->>Client: new VirtualdisplayClient(...)
228
+ Client->>Client: Create iframe
229
+ Client->>MsgHandler: setIframe()
230
+ Server->>MsgHandler: STATE message (all nodes)
231
+ MsgHandler->>EventBus: emit(STATE_MESSAGE)
232
+ EventBus->>AttrService: Update local state
233
+
234
+ Note over UI,Server: 1. Configure mapping (setMapping)
235
+ UI->>Client: setMapping({attributes: [...]})
236
+ Client->>AttrService: loadMapping(config)
237
+ AttrService->>AttrService: Validate with AJV
238
+ AttrService->>AttrService: Create Attribute objects
239
+ AttrService->>AttrService: Generate default mutations<br/>(from isSelected values)
240
+ AttrService->>EventBus: emit(MUTATION_MESSAGE)
241
+ EventBus->>MsgHandler: Send mutations
242
+ MsgHandler->>Server: postMessage(mutations)
243
+ Server->>Server: Apply changes
244
+ Server->>MsgHandler: STATE message (updated)
245
+ MsgHandler->>EventBus: emit(STATE_MESSAGE)
246
+ EventBus->>AttrService: Update local state
247
+ AttrService->>UI: Trigger onChange callbacks
248
+
249
+ Note over UI,Server: 2. User interaction (getAttribute.select)
250
+ UI->>Client: getAttribute('Color')
251
+ Client->>Client: new AttributeSelector('Color')
252
+ UI->>Client: .select('Blue')
253
+ Client->>AttrService: selectAttributeValue('Color', 'Blue')
254
+ AttrService->>AttrService: Generate mutations<br/>(hide current, show new)
255
+ AttrService->>EventBus: emit(MUTATION_MESSAGE)
256
+ EventBus->>MsgHandler: Send mutations
257
+ MsgHandler->>Server: postMessage(mutations)
258
+ Note right of Client: select() returns<br/>immediately
259
+ Server->>Server: Apply changes
260
+ Server->>MsgHandler: STATE message (updated)
261
+ MsgHandler->>EventBus: emit(STATE_MESSAGE)
262
+ EventBus->>AttrService: Update local state
263
+ AttrService->>UI: Trigger onChange callbacks
264
+ ```
265
+
266
+ **Phase by phase breakdown:**
267
+
268
+ #### Phase 0: Initialization (always happens)
269
+
270
+ - Client creates iframe and loads 3D server
271
+ - Server initializes the 3D model with all nodes visible
272
+ - Server sends complete node state to client
273
+ - Client now knows about all available nodes in the model
274
+
275
+ **Note:** For simple models without options, this is all you need!
276
+
277
+ #### Phase 1: Configuration (only for configurable products)
278
+
279
+ - You call `setMapping()` with your product structure
280
+ - Client stores the attribute mapping locally
281
+ - Client sends mutations for all `isSelected: true` values
282
+ - Server applies these changes and hides non-selected options
283
+ - Server sends back confirmed state
284
+ - Local state updates based on server response
285
+ - onChange callbacks fire for initial UI synchronization
286
+
287
+ #### Phase 2: User interaction (only for configurable products)
288
+
289
+ - User selects a different option in your UI
290
+ - You call `getAttribute('Color').select('Blue')`
291
+ - Client sends mutation request to server
292
+ - Your code continues immediately (doesn't wait for server response)
293
+ - Server updates the 3D model
294
+ - Server sends back the confirmed state
295
+ - Local state updates based on server response
296
+ - onChange callbacks fire with the actual state
297
+ - UI stays perfectly synchronized with 3D model
298
+
299
+ **Important:** The pattern is identical for both initial mapping and user
300
+ interactions. In both cases:
301
+
302
+ 1. Client sends mutations to server
303
+ 2. Server applies changes to 3D model
304
+ 3. Server sends back confirmed state
305
+ 4. Client updates local state based on server response
306
+ 5. onChange callbacks fire with the actual state
307
+
308
+ This ensures the server remains the single source of truth for all state
309
+ changes.
310
+
311
+ ## Advanced usage
312
+
313
+ ### Live state synchronization
314
+
315
+ Keep your UI in sync with the 3D server state using onChange callbacks:
316
+
317
+ ```typescript
318
+ const colorAttribute = client.getAttribute('Color');
319
+
320
+ colorAttribute?.getValues().forEach((value) => {
321
+ value.onChange = () => {
322
+ // Update UI when state changes
323
+ updateButton(value.value, value.isSelected);
324
+ };
325
+ });
326
+ ```
327
+
328
+ ### Mapping validation
329
+
330
+ Validate your mapping configuration during development:
331
+
332
+ ```typescript
333
+ import { mappingSchema } from '@virtualdisplay-io/client';
334
+ import Ajv from 'ajv';
335
+
336
+ const validate = new Ajv().compile(mappingSchema);
337
+ if (!validate(myMapping)) {
338
+ console.error('Invalid mapping:', validate.errors);
339
+ }
340
+ ```
341
+
342
+ ## Real-world integration examples
343
+
344
+ ### CMS integration
345
+
346
+ The Virtualdisplay client is CMS-agnostic. Store the mapping configuration in
347
+ your CMS alongside your product data:
348
+
349
+ ```typescript
350
+ // Fetch product with 3D mapping from your CMS
351
+ const product = await fetch('/api/products/sneaker-pro').then((r) => r.json());
352
+
353
+ // Use the stored mapping directly
354
+ client.setMapping(product.server3dMapping);
355
+ ```
356
+
357
+ **Best practices:**
358
+
359
+ - Store the complete mapping configuration with each product
360
+ - Update mappings when product options or availability changes
361
+ - Let your CMS handle filtering of unavailable combinations
362
+ - Version your mappings when 3D models are updated
363
+
364
+ ## Example
365
+
366
+ See the [color configurator example](./examples/color-configurator/) for a
367
+ complete working implementation that demonstrates:
368
+
369
+ - Setting up the client
370
+ - Defining attribute mappings
371
+ - Binding UI controls to attributes
372
+ - Real-time state synchronization
373
+
374
+ ## API reference
375
+
376
+ ### ClientOptions
377
+
378
+ ```typescript
379
+ interface ClientOptions {
380
+ parent: string | HTMLElement; // Container element or selector
381
+ license: string; // Your license key
382
+ model: string; // Model ID to load
383
+ debug?: boolean; // Enable debug logging (default: false)
384
+ language?: string; // Language for UI inside the iframe (default: 'nl', supported: 'nl', 'en', 'de')
385
+ }
386
+ ```
387
+
388
+ ### Methods
389
+
390
+ #### `setMapping(configuration: MappingConfiguration): void`
391
+
392
+ Configure attribute-to-node mapping for product variants. This is the primary
393
+ method for setting up your product configurator. Call this once with your
394
+ complete configuration - the client efficiently handles all updates.
395
+
396
+ ```typescript
397
+ client.setMapping({
398
+ attributes: [
399
+ {
400
+ name: 'Color',
401
+ values: [
402
+ { value: 'Red', nodeIds: ['node1', 'node2'], isSelected: true },
403
+ { value: 'Blue', nodeIds: ['node3', 'node4'] },
404
+ ],
405
+ },
406
+ ],
407
+ });
408
+ ```
409
+
410
+ #### `getAttribute(name: string): AttributeSelector | undefined`
411
+
412
+ Get an attribute selector for changing values. Returns undefined if the
413
+ attribute doesn't exist.
414
+
415
+ ```typescript
416
+ const colorAttr = client.getAttribute('Color');
417
+ if (colorAttr) {
418
+ colorAttr.select('Blue');
419
+ }
420
+ ```
421
+
422
+ #### `destroy(): void`
423
+
424
+ Clean up and remove the client connection. Always call this when unmounting your
425
+ component.
426
+
427
+ ```typescript
428
+ // React example
429
+ useEffect(() => {
430
+ const client = new VirtualdisplayClient({ ... });
431
+ return () => client.destroy();
432
+ }, []);
433
+ ```
434
+
435
+ ### Types
436
+
437
+ ```typescript
438
+ interface MappingConfiguration {
439
+ attributes: AttributeConfig[];
440
+ }
441
+
442
+ interface AttributeConfig {
443
+ name: string; // Attribute name (e.g., 'Color')
444
+ values: AttributeValueConfig[]; // Possible values
445
+ }
446
+
447
+ interface AttributeValueConfig {
448
+ value: string; // Value name (e.g., 'Red')
449
+ nodeIds: string[]; // 3D node IDs for this value
450
+ isSelected?: boolean; // Default selection state
451
+ }
452
+
453
+ class AttributeSelector {
454
+ // Properties
455
+ name: string; // Attribute name (getter)
456
+ currentValue: string | undefined; // Current selected value (getter)
457
+ availableValues: string[]; // All possible values (getter)
458
+
459
+ // Methods
460
+ select(value: string): AttributeSelector; // Select a value (chainable)
461
+ onChange(callback: () => void): AttributeSelector; // Register change callback (chainable)
462
+ getValues(): AttributeValue[]; // Get all AttributeValue objects
463
+ getValue(value: string): AttributeValue | undefined; // Get specific AttributeValue
464
+ }
465
+
466
+ class AttributeValue {
467
+ // Properties
468
+ value: string; // The value name (e.g., 'Red') - getter
469
+ nodeList: string[]; // Associated 3D node IDs - getter
470
+ isSelected: boolean; // Current selection state - getter
471
+ onChange?: () => void; // Callback when selection changes - property
472
+ }
473
+ ```
474
+
475
+ #### NodeSelector
476
+
477
+ The client exports `NodeSelector` for direct node manipulation. This is intended
478
+ for specialized tools like mapping editors and inspectors, not for typical
479
+ product configurators. For product configurators, use the attribute-based API.
480
+
481
+ ### Error handling
482
+
483
+ The client throws `VirtualdisplayError` for known error conditions:
484
+
485
+ ```typescript
486
+ import {
487
+ VirtualdisplayClient,
488
+ VirtualdisplayError,
489
+ ERROR_CODES,
490
+ } from '@virtualdisplay-io/client';
491
+
492
+ try {
493
+ client.getAttribute('NonExistent').select('Value');
494
+ } catch (error) {
495
+ if (error instanceof VirtualdisplayError) {
496
+ switch (error.code) {
497
+ case ERROR_CODES.ATTRIBUTE_NOT_FOUND:
498
+ console.error('Attribute does not exist');
499
+ break;
500
+ case ERROR_CODES.NO_MAPPING:
501
+ console.error('Call setMapping() first');
502
+ break;
503
+ // Handle other error codes
504
+ }
505
+ }
506
+ }
507
+ ```
508
+
509
+ **Error codes:**
510
+
511
+ - `NO_MAPPING` - No mapping configuration set
512
+ - `ATTRIBUTE_NOT_FOUND` - Requested attribute doesn't exist
513
+ - `VALUE_NOT_FOUND` - Requested value doesn't exist for attribute
514
+ - `INVALID_MAPPING` - Mapping configuration is invalid
515
+ - `PARENT_NOT_FOUND` - Parent element for iframe not found
516
+
517
+ ## Contributing
518
+
519
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for development setup and guidelines.
520
+
521
+ ## Troubleshooting
522
+
523
+ ### Common issues
524
+
525
+ **Iframe not loading:**
526
+
527
+ - Check that the parent element exists in the DOM
528
+ - Verify your license key is valid
529
+ - Ensure the model ID matches your license
530
+
531
+ **Attributes not working:**
532
+
533
+ - Make sure you call `setMapping()` before using `getAttribute()`
534
+ - Verify attribute names match exactly (case-sensitive)
535
+ - Check that node IDs in your mapping exist in the 3D model
536
+
537
+ ## Changelog
538
+
539
+ See the [GitHub releases](https://github.com/virtualdisplay-io/client/releases)
540
+ for release notes and version history.