@webspatial/core-sdk 1.2.1 → 1.3.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 +28 -0
- package/dist/iife/index.d.ts +43 -2
- package/dist/iife/index.global.js +68 -3
- package/dist/iife/index.global.js.map +1 -1
- package/dist/index.d.ts +43 -2
- package/dist/index.js +451 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/JSBCommand.ts +32 -0
- package/src/SpatialSession.ts +15 -1
- package/src/SpatializedElementCreator.ts +1 -1
- package/src/SpatializedStatic3DElement.ts +12 -1
- package/src/platform-adapter/index.ts +5 -1
- package/src/platform-adapter/puppeteer/PuppeteerPlatform.ts +470 -0
- package/src/reality/Attachment.ts +45 -0
- package/src/reality/index.ts +1 -0
- package/src/types/types.ts +13 -0
package/package.json
CHANGED
package/src/JSBCommand.ts
CHANGED
|
@@ -21,6 +21,8 @@ import {
|
|
|
21
21
|
SpatialModelEntityCreationOptions,
|
|
22
22
|
SpatialEntityEventType,
|
|
23
23
|
Vec3,
|
|
24
|
+
AttachmentEntityOptions,
|
|
25
|
+
AttachmentEntityUpdateOptions,
|
|
24
26
|
} from './types/types'
|
|
25
27
|
import { SpatialSceneCreationOptionsInternal } from './types/internal'
|
|
26
28
|
import { composeSRT } from './utils'
|
|
@@ -620,6 +622,36 @@ export class createSpatialSceneCommand extends WebSpatialProtocolCommand {
|
|
|
620
622
|
}
|
|
621
623
|
}
|
|
622
624
|
|
|
625
|
+
export class CreateAttachmentEntityCommand extends WebSpatialProtocolCommand {
|
|
626
|
+
commandType = 'createAttachment'
|
|
627
|
+
constructor(private options: AttachmentEntityOptions) {
|
|
628
|
+
super()
|
|
629
|
+
}
|
|
630
|
+
protected getParams() {
|
|
631
|
+
return {
|
|
632
|
+
parentEntityId: this.options.parentEntityId,
|
|
633
|
+
position: this.options.position ?? [0, 0, 0],
|
|
634
|
+
size: this.options.size,
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
export class UpdateAttachmentEntityCommand extends JSBCommand {
|
|
640
|
+
commandType = 'UpdateAttachmentEntity'
|
|
641
|
+
constructor(
|
|
642
|
+
private attachmentId: string,
|
|
643
|
+
private options: AttachmentEntityUpdateOptions,
|
|
644
|
+
) {
|
|
645
|
+
super()
|
|
646
|
+
}
|
|
647
|
+
protected getParams() {
|
|
648
|
+
return {
|
|
649
|
+
id: this.attachmentId,
|
|
650
|
+
...this.options,
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
623
655
|
// TODO: Can crypto.randomUUID be used instead including in dev environments without https
|
|
624
656
|
function uuid(): string {
|
|
625
657
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
package/src/SpatialSession.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
createSpatializedDynamic3DElement,
|
|
7
7
|
} from './SpatializedElementCreator'
|
|
8
8
|
import { createSpatializedStatic3DElement } from './SpatializedElementCreator'
|
|
9
|
+
import { Attachment, createAttachmentEntity } from './reality/Attachment'
|
|
9
10
|
import { SpatializedStatic3DElement } from './SpatializedStatic3DElement'
|
|
10
11
|
import {
|
|
11
12
|
ModelComponentOptions,
|
|
@@ -20,6 +21,7 @@ import {
|
|
|
20
21
|
SpatialSphereGeometryOptions,
|
|
21
22
|
SpatialUnlitMaterialOptions,
|
|
22
23
|
SpatialEntityUserData,
|
|
24
|
+
AttachmentEntityOptions,
|
|
23
25
|
} from './types/types'
|
|
24
26
|
import { SpatializedDynamic3DElement } from './SpatializedDynamic3DElement'
|
|
25
27
|
import { SpatialEntity } from './reality/entity/SpatialEntity'
|
|
@@ -70,7 +72,7 @@ export class SpatialSession {
|
|
|
70
72
|
* @returns Promise resolving to a new SpatializedStatic3DElement instance
|
|
71
73
|
*/
|
|
72
74
|
createSpatializedStatic3DElement(
|
|
73
|
-
modelURL: string
|
|
75
|
+
modelURL: string,
|
|
74
76
|
): Promise<SpatializedStatic3DElement> {
|
|
75
77
|
return createSpatializedStatic3DElement(modelURL)
|
|
76
78
|
}
|
|
@@ -187,4 +189,16 @@ export class SpatialSession {
|
|
|
187
189
|
) {
|
|
188
190
|
return createSpatialModelEntity(options, userData)
|
|
189
191
|
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Creates an attachment entity that renders 2D HTML content as a child
|
|
195
|
+
* of a 3D entity in the scene graph.
|
|
196
|
+
* @param options Configuration options including parent entity ID, position, and size
|
|
197
|
+
* @returns Promise resolving to a new Attachment instance
|
|
198
|
+
*/
|
|
199
|
+
createAttachmentEntity(
|
|
200
|
+
options: AttachmentEntityOptions,
|
|
201
|
+
): Promise<Attachment> {
|
|
202
|
+
return createAttachmentEntity(options)
|
|
203
|
+
}
|
|
190
204
|
}
|
|
@@ -30,7 +30,7 @@ export async function createSpatializedStatic3DElement(
|
|
|
30
30
|
throw new Error('createSpatializedStatic3DElement failed')
|
|
31
31
|
} else {
|
|
32
32
|
const { id } = result.data
|
|
33
|
-
return new SpatializedStatic3DElement(id)
|
|
33
|
+
return new SpatializedStatic3DElement(id, modelURL)
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
@@ -9,6 +9,17 @@ import { SpatialWebMsgType } from './WebMsgCommand'
|
|
|
9
9
|
* and provides events for load success and failure.
|
|
10
10
|
*/
|
|
11
11
|
export class SpatializedStatic3DElement extends SpatializedElement {
|
|
12
|
+
/**
|
|
13
|
+
* Creates a new spatialized static 3D element with the specified ID and URL.
|
|
14
|
+
* Registers the element to receive spatial events.
|
|
15
|
+
* @param id Unique identifier for this element
|
|
16
|
+
* @param modelURL URL of the 3D model
|
|
17
|
+
*/
|
|
18
|
+
constructor(id: string, modelURL: string) {
|
|
19
|
+
super(id)
|
|
20
|
+
this.modelURL = modelURL
|
|
21
|
+
}
|
|
22
|
+
|
|
12
23
|
/**
|
|
13
24
|
* Promise resolver for the ready state.
|
|
14
25
|
* Used to resolve the ready promise when the model is loaded.
|
|
@@ -19,7 +30,7 @@ export class SpatializedStatic3DElement extends SpatializedElement {
|
|
|
19
30
|
* Caches the last model URL to detect changes.
|
|
20
31
|
* Used to reset the ready promise when the model URL changes.
|
|
21
32
|
*/
|
|
22
|
-
private modelURL: string
|
|
33
|
+
private modelURL: string
|
|
23
34
|
|
|
24
35
|
/**
|
|
25
36
|
* Creates a new promise for tracking the ready state of the model.
|
|
@@ -32,7 +32,11 @@ export function createPlatform(): PlatformAbility {
|
|
|
32
32
|
}
|
|
33
33
|
const userAgent = window.navigator.userAgent
|
|
34
34
|
const webSpatialVersion = getWebSpatialVersion(userAgent)
|
|
35
|
-
if (
|
|
35
|
+
if (window.navigator.userAgent.includes('Puppeteer')) {
|
|
36
|
+
const PuppeteerPlatform =
|
|
37
|
+
require('./puppeteer/PuppeteerPlatform').PuppeteerPlatform
|
|
38
|
+
return new PuppeteerPlatform()
|
|
39
|
+
} else if (
|
|
36
40
|
userAgent.includes('PicoWebApp') &&
|
|
37
41
|
isVersionGreater(webSpatialVersion, [0, 0, 1])
|
|
38
42
|
) {
|
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
import { PlatformAbility, CommandResult } from '../interface'
|
|
2
|
+
import {
|
|
3
|
+
CommandResultFailure,
|
|
4
|
+
CommandResultSuccess,
|
|
5
|
+
} from '../CommandResultUtils'
|
|
6
|
+
|
|
7
|
+
// 添加window扩展接口
|
|
8
|
+
declare global {
|
|
9
|
+
interface Window {
|
|
10
|
+
__handleJSBMessage: (message: string) => any
|
|
11
|
+
SpatialId?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface HTMLIFrameElement {
|
|
15
|
+
spatialId?: string
|
|
16
|
+
webSpatialId?: string
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type JSBError = {
|
|
21
|
+
message: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
console.log('PuppeteerPlatform')
|
|
25
|
+
|
|
26
|
+
export class PuppeteerPlatform implements PlatformAbility {
|
|
27
|
+
// 存储iframe实例
|
|
28
|
+
private iframeRegistry: Map<string, HTMLIFrameElement> = new Map()
|
|
29
|
+
|
|
30
|
+
constructor() {}
|
|
31
|
+
|
|
32
|
+
callJSB(cmd: string, msg: string): Promise<CommandResult> {
|
|
33
|
+
return new Promise(resolve => {
|
|
34
|
+
try {
|
|
35
|
+
// 检查__handleJSBMessage是否存在
|
|
36
|
+
if (window.__handleJSBMessage) {
|
|
37
|
+
try {
|
|
38
|
+
console.log(` core-sdk Puppeteer Platform: callJSB: ${cmd}::${msg}`)
|
|
39
|
+
const result = window.__handleJSBMessage(`${cmd}::${msg}`)
|
|
40
|
+
console.log(
|
|
41
|
+
` core-sdk Puppeteer Platform callJSB result: ${result}`,
|
|
42
|
+
)
|
|
43
|
+
resolve(CommandResultSuccess(result))
|
|
44
|
+
} catch (err) {
|
|
45
|
+
resolve(CommandResultFailure('500', 'JSB execution error'))
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
// 如果不存在,返回默认结果
|
|
49
|
+
resolve(CommandResultSuccess('ok'))
|
|
50
|
+
}
|
|
51
|
+
} catch (error: unknown) {
|
|
52
|
+
console.error(
|
|
53
|
+
`PuppeteerPlatform cmd Error: ${cmd}, msg: ${msg} error: ${error}`,
|
|
54
|
+
)
|
|
55
|
+
resolve(CommandResultFailure('500', 'Internal error'))
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 同步创建Spatialized2DElement到Puppeteer Runner
|
|
62
|
+
*/
|
|
63
|
+
private createSpatializedElementSync(
|
|
64
|
+
spatialId: string,
|
|
65
|
+
webspatialUrl: string,
|
|
66
|
+
): void {
|
|
67
|
+
try {
|
|
68
|
+
console.log(
|
|
69
|
+
`[Puppeteer Platform] Creating spatialized element sync with id: ${spatialId}, url: ${webspatialUrl}`,
|
|
70
|
+
)
|
|
71
|
+
// 直接调用Puppeteer Runner的方法来创建元素
|
|
72
|
+
const win = window as any
|
|
73
|
+
if (win.__handleJSBMessage) {
|
|
74
|
+
// 使用更简洁的格式,确保JSBManager能正确使用我们传递的spatialId
|
|
75
|
+
const createCommand = {
|
|
76
|
+
id: spatialId,
|
|
77
|
+
url: webspatialUrl,
|
|
78
|
+
}
|
|
79
|
+
win.__handleJSBMessage(
|
|
80
|
+
`CreateSpatialized2DElement::${JSON.stringify(createCommand)}`,
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error('Error creating spatialized element sync:', error)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
callWebSpatialProtocol(
|
|
89
|
+
command: string,
|
|
90
|
+
query?: string,
|
|
91
|
+
target?: string,
|
|
92
|
+
features?: string,
|
|
93
|
+
): Promise<CommandResult> {
|
|
94
|
+
console.log(
|
|
95
|
+
`PuppeteerPlatform: Calling webspatial protocol: webspatial://${command}${query ? `?${query}` : ''}`,
|
|
96
|
+
)
|
|
97
|
+
return new Promise(resolve => {
|
|
98
|
+
try {
|
|
99
|
+
// 创建完整的webspatial URL
|
|
100
|
+
const webspatialUrl = `webspatial://${command}${query ? `?${query}` : ''}`
|
|
101
|
+
// 使用iframe创建新窗口
|
|
102
|
+
const { spatialId, iframe, windowProxy } = this.createIframeWindow(
|
|
103
|
+
webspatialUrl,
|
|
104
|
+
target,
|
|
105
|
+
features,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
// 对于createSpatialized2DElement命令,同步创建元素
|
|
109
|
+
if (command === 'createSpatialized2DElement') {
|
|
110
|
+
this.createSpatializedElementSync(spatialId, webspatialUrl)
|
|
111
|
+
}
|
|
112
|
+
console.log(
|
|
113
|
+
`[Puppeteer Platform] iframe created with spatialId: ${spatialId}`,
|
|
114
|
+
)
|
|
115
|
+
// 注册iframe
|
|
116
|
+
this.iframeRegistry.set(spatialId, iframe)
|
|
117
|
+
resolve(CommandResultSuccess({ windowProxy, id: spatialId }))
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.error('Error calling webspatial protocol:', error)
|
|
120
|
+
resolve(
|
|
121
|
+
CommandResultFailure('500', 'Failed to call webspatial protocol'),
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
callWebSpatialProtocolSync(
|
|
128
|
+
command: string,
|
|
129
|
+
query?: string,
|
|
130
|
+
target?: string,
|
|
131
|
+
features?: string,
|
|
132
|
+
): CommandResult {
|
|
133
|
+
try {
|
|
134
|
+
// 创建完整的webspatial URL
|
|
135
|
+
const webspatialUrl = `webspatial://${command}${query ? `?${query}` : ''}`
|
|
136
|
+
console.log(`Calling webspatial protocol sync: ${webspatialUrl}`)
|
|
137
|
+
|
|
138
|
+
// 使用iframe创建新窗口
|
|
139
|
+
const { spatialId, iframe, windowProxy } = this.createIframeWindow(
|
|
140
|
+
webspatialUrl,
|
|
141
|
+
target,
|
|
142
|
+
features,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
// 对于createSpatialized2DElement命令,同步创建元素
|
|
146
|
+
if (command === 'createSpatialized2DElement') {
|
|
147
|
+
this.createSpatializedElementSync(spatialId, webspatialUrl)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 注册iframe
|
|
151
|
+
this.iframeRegistry.set(spatialId, iframe)
|
|
152
|
+
|
|
153
|
+
return CommandResultSuccess({ windowProxy, id: spatialId })
|
|
154
|
+
} catch (error) {
|
|
155
|
+
console.error('Error calling webspatial protocol sync:', error)
|
|
156
|
+
return CommandResultFailure(
|
|
157
|
+
'500',
|
|
158
|
+
'Failed to call webspatial protocol sync',
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* 创建基于iframe的窗口
|
|
165
|
+
*/
|
|
166
|
+
private createIframeWindow(url: string, target?: string, features?: string) {
|
|
167
|
+
// 创建iframe元素
|
|
168
|
+
const iframe = document.createElement('iframe')
|
|
169
|
+
|
|
170
|
+
// 设置iframe属性
|
|
171
|
+
iframe.style.border = 'none'
|
|
172
|
+
iframe.style.display = 'none' // 初始隐藏
|
|
173
|
+
iframe.style.width = '100%'
|
|
174
|
+
iframe.style.height = '100%'
|
|
175
|
+
|
|
176
|
+
// 生成唯一的spatialId
|
|
177
|
+
const spatialId = this.generateUUID()
|
|
178
|
+
iframe.spatialId = spatialId
|
|
179
|
+
iframe.id = `spatial-iframe-${spatialId}`
|
|
180
|
+
|
|
181
|
+
// 解析features参数
|
|
182
|
+
const featuresObj = this.parseFeatures(features || '')
|
|
183
|
+
|
|
184
|
+
// 根据features设置iframe样式
|
|
185
|
+
if (featuresObj.width) {
|
|
186
|
+
iframe.style.width = featuresObj.width
|
|
187
|
+
}
|
|
188
|
+
if (featuresObj.height) {
|
|
189
|
+
iframe.style.height = featuresObj.height
|
|
190
|
+
}
|
|
191
|
+
if (featuresObj.left) {
|
|
192
|
+
iframe.style.left = featuresObj.left
|
|
193
|
+
iframe.style.position = 'absolute'
|
|
194
|
+
}
|
|
195
|
+
if (featuresObj.top) {
|
|
196
|
+
iframe.style.top = featuresObj.top
|
|
197
|
+
iframe.style.position = 'absolute'
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 添加iframe到DOM
|
|
201
|
+
document.body.appendChild(iframe)
|
|
202
|
+
|
|
203
|
+
// 创建增强的windowProxy模拟对象
|
|
204
|
+
const windowProxy = this.createEnhancedWindowProxy(iframe, url, spatialId)
|
|
205
|
+
|
|
206
|
+
// 设置iframe的src
|
|
207
|
+
iframe.src = 'about:blank'
|
|
208
|
+
|
|
209
|
+
console.log(
|
|
210
|
+
`PuppeteerPlatform created iframe window with spatialId: ${spatialId}, URL: ${url}`,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
// 初始化iframe内容
|
|
214
|
+
this.initializeIframeContent(iframe, url, spatialId)
|
|
215
|
+
|
|
216
|
+
return { spatialId, iframe, windowProxy }
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* 创建增强的windowProxy对象
|
|
221
|
+
*/
|
|
222
|
+
private createEnhancedWindowProxy(
|
|
223
|
+
iframe: HTMLIFrameElement,
|
|
224
|
+
url: string,
|
|
225
|
+
spatialId: string,
|
|
226
|
+
) {
|
|
227
|
+
// 创建增强的windowProxy模拟对象
|
|
228
|
+
return {
|
|
229
|
+
// 基本属性
|
|
230
|
+
location: {
|
|
231
|
+
href: url,
|
|
232
|
+
toString: () => url,
|
|
233
|
+
reload: () => {
|
|
234
|
+
if (iframe.contentWindow) {
|
|
235
|
+
iframe.contentWindow.location.reload()
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
navigator: {
|
|
240
|
+
userAgent: `Mozilla/5.0 (WebKit) SpatialId/${spatialId}`,
|
|
241
|
+
},
|
|
242
|
+
|
|
243
|
+
// 方法
|
|
244
|
+
close: () => {
|
|
245
|
+
console.log(`Closing iframe with spatialId: ${spatialId}`)
|
|
246
|
+
iframe.remove()
|
|
247
|
+
this.iframeRegistry.delete(spatialId)
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
// 文档访问
|
|
251
|
+
document: iframe.contentDocument || ({} as Document),
|
|
252
|
+
contentWindow: iframe.contentWindow || ({} as Window),
|
|
253
|
+
|
|
254
|
+
// 添加消息通信方法
|
|
255
|
+
postMessage: (message: any, targetOrigin?: string) => {
|
|
256
|
+
if (iframe.contentWindow) {
|
|
257
|
+
iframe.contentWindow.postMessage(message, targetOrigin || '*')
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
// 添加事件监听方法
|
|
262
|
+
addEventListener: (
|
|
263
|
+
type: string,
|
|
264
|
+
listener: EventListenerOrEventListenerObject,
|
|
265
|
+
) => {
|
|
266
|
+
if (iframe.contentWindow) {
|
|
267
|
+
iframe.contentWindow.addEventListener(type, listener)
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
|
|
271
|
+
removeEventListener: (
|
|
272
|
+
type: string,
|
|
273
|
+
listener: EventListenerOrEventListenerObject,
|
|
274
|
+
) => {
|
|
275
|
+
if (iframe.contentWindow) {
|
|
276
|
+
iframe.contentWindow.removeEventListener(type, listener)
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
// 执行JavaScript
|
|
281
|
+
executeScript: (code: string): any => {
|
|
282
|
+
if (iframe.contentWindow) {
|
|
283
|
+
try {
|
|
284
|
+
// 使用类型断言和更安全的方式执行脚本
|
|
285
|
+
const win = iframe.contentWindow as any
|
|
286
|
+
return win.eval(code)
|
|
287
|
+
} catch (error) {
|
|
288
|
+
console.error(
|
|
289
|
+
`Error executing script in iframe ${spatialId}:`,
|
|
290
|
+
error,
|
|
291
|
+
)
|
|
292
|
+
return null
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return null
|
|
296
|
+
},
|
|
297
|
+
|
|
298
|
+
// 获取iframe引用
|
|
299
|
+
getIframe: () => iframe,
|
|
300
|
+
|
|
301
|
+
// 获取spatialId
|
|
302
|
+
getSpatialId: () => spatialId,
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* 初始化iframe内容
|
|
308
|
+
*/
|
|
309
|
+
private initializeIframeContent(
|
|
310
|
+
iframe: HTMLIFrameElement,
|
|
311
|
+
url: string,
|
|
312
|
+
spatialId: string,
|
|
313
|
+
): void {
|
|
314
|
+
try {
|
|
315
|
+
// 等待iframe加载完成
|
|
316
|
+
iframe.onload = () => {
|
|
317
|
+
try {
|
|
318
|
+
// 设置iframe内容
|
|
319
|
+
const iframeContent = `
|
|
320
|
+
// 注入通信脚本
|
|
321
|
+
window.webSpatialId = '${spatialId}';
|
|
322
|
+
window.SpatialId = '${spatialId}';
|
|
323
|
+
|
|
324
|
+
// 重写window.open以支持webspatial协议
|
|
325
|
+
const originalOpen = window.open;
|
|
326
|
+
window.open = function(url, target, features) {
|
|
327
|
+
if (url && url.startsWith('webspatial://')) {
|
|
328
|
+
// 通过windowProxy处理webspatial协议
|
|
329
|
+
const windowProxy = new Proxy({}, {
|
|
330
|
+
get: function(target, prop) {
|
|
331
|
+
if (prop === 'toString') {
|
|
332
|
+
return function() { return url; };
|
|
333
|
+
}
|
|
334
|
+
return undefined;
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
return windowProxy;
|
|
338
|
+
}
|
|
339
|
+
return originalOpen.call(window, url, target, features);
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
// 设置navigator.userAgent以识别webspatial环境
|
|
343
|
+
Object.defineProperty(navigator, 'userAgent', {
|
|
344
|
+
value: 'WebSpatial/1.0 ' + navigator.userAgent,
|
|
345
|
+
configurable: true
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// 发送加载完成消息
|
|
349
|
+
window.parent.postMessage({
|
|
350
|
+
type: 'iframe_loaded',
|
|
351
|
+
spatialId: '${spatialId}',
|
|
352
|
+
url: '${url}'
|
|
353
|
+
}, '${window.location.origin}');
|
|
354
|
+
|
|
355
|
+
// 设置消息处理器
|
|
356
|
+
window.addEventListener('message', (event) => {
|
|
357
|
+
if (event.origin !== window.parent.location.origin) return;
|
|
358
|
+
|
|
359
|
+
const data = event.data;
|
|
360
|
+
if (data && data.type === 'webspatial_command') {
|
|
361
|
+
// 处理来自父窗口的命令
|
|
362
|
+
console.log('Received command in iframe from parent:', data.command);
|
|
363
|
+
// 这里可以添加命令处理逻辑
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
`
|
|
367
|
+
|
|
368
|
+
// 使用document.write代替eval,更安全且符合类型定义
|
|
369
|
+
const doc = iframe.contentDocument
|
|
370
|
+
if (doc) {
|
|
371
|
+
doc.open()
|
|
372
|
+
doc.write(`
|
|
373
|
+
<!DOCTYPE html>
|
|
374
|
+
<html>
|
|
375
|
+
<head>
|
|
376
|
+
<title>Spatial Iframe - ${spatialId}</title>
|
|
377
|
+
<meta charset="UTF-8">
|
|
378
|
+
<style>
|
|
379
|
+
body {
|
|
380
|
+
margin: 0;
|
|
381
|
+
padding: 0;
|
|
382
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
383
|
+
}
|
|
384
|
+
</style>
|
|
385
|
+
</head>
|
|
386
|
+
<body>
|
|
387
|
+
<script>${iframeContent}</script>
|
|
388
|
+
</body>
|
|
389
|
+
</html>
|
|
390
|
+
`)
|
|
391
|
+
doc.close()
|
|
392
|
+
}
|
|
393
|
+
} catch (error) {
|
|
394
|
+
console.error('Error initializing iframe content:', error)
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
} catch (error) {
|
|
398
|
+
console.error('Error setting up iframe:', error)
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* 解析features字符串为对象
|
|
404
|
+
*/
|
|
405
|
+
private parseFeatures(features: string): Record<string, string> {
|
|
406
|
+
const result: Record<string, string> = {}
|
|
407
|
+
const pairs = features.split(',')
|
|
408
|
+
|
|
409
|
+
pairs.forEach(pair => {
|
|
410
|
+
const [key, value] = pair.split('=').map(s => s.trim())
|
|
411
|
+
if (key && value) {
|
|
412
|
+
result[key] = value
|
|
413
|
+
}
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
return result
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* 发送消息到指定spatialId的iframe
|
|
421
|
+
*/
|
|
422
|
+
public sendMessageToIframe(spatialId: string, message: any): boolean {
|
|
423
|
+
const iframe = this.iframeRegistry.get(spatialId)
|
|
424
|
+
if (iframe && iframe.contentWindow) {
|
|
425
|
+
iframe.contentWindow.postMessage(message, window.location.origin)
|
|
426
|
+
return true
|
|
427
|
+
}
|
|
428
|
+
return false
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* 获取所有活跃的iframe
|
|
433
|
+
*/
|
|
434
|
+
public getAllActiveIframes(): Array<{
|
|
435
|
+
spatialId: string
|
|
436
|
+
iframe: HTMLIFrameElement
|
|
437
|
+
}> {
|
|
438
|
+
const result: Array<{ spatialId: string; iframe: HTMLIFrameElement }> = []
|
|
439
|
+
|
|
440
|
+
this.iframeRegistry.forEach((iframe, spatialId) => {
|
|
441
|
+
result.push({ spatialId, iframe })
|
|
442
|
+
})
|
|
443
|
+
|
|
444
|
+
return result
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* 清理资源
|
|
449
|
+
*/
|
|
450
|
+
public dispose(): void {
|
|
451
|
+
// 关闭所有iframe
|
|
452
|
+
this.iframeRegistry.forEach((iframe, spatialId) => {
|
|
453
|
+
console.log(`Disposing iframe with spatialId: ${spatialId}`)
|
|
454
|
+
iframe.remove()
|
|
455
|
+
})
|
|
456
|
+
this.iframeRegistry.clear()
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// 生成UUID函数
|
|
460
|
+
private generateUUID(): string {
|
|
461
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(
|
|
462
|
+
/[xy]/g,
|
|
463
|
+
function (c) {
|
|
464
|
+
const r = (Math.random() * 16) | 0
|
|
465
|
+
const v = c === 'x' ? r : (r & 0x3) | 0x8
|
|
466
|
+
return v.toString(16).toUpperCase()
|
|
467
|
+
},
|
|
468
|
+
)
|
|
469
|
+
}
|
|
470
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { SpatialObject } from '../SpatialObject'
|
|
2
|
+
import {
|
|
3
|
+
CreateAttachmentEntityCommand,
|
|
4
|
+
UpdateAttachmentEntityCommand,
|
|
5
|
+
} from '../JSBCommand'
|
|
6
|
+
import {
|
|
7
|
+
AttachmentEntityOptions,
|
|
8
|
+
AttachmentEntityUpdateOptions,
|
|
9
|
+
} from '../types/types'
|
|
10
|
+
|
|
11
|
+
export class Attachment extends SpatialObject {
|
|
12
|
+
constructor(
|
|
13
|
+
id: string,
|
|
14
|
+
private readonly windowProxy: WindowProxy,
|
|
15
|
+
private options: AttachmentEntityOptions,
|
|
16
|
+
) {
|
|
17
|
+
super(id)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
getContainer(): HTMLElement {
|
|
21
|
+
return (this.windowProxy as Window).document.body
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
getWindowProxy(): WindowProxy {
|
|
25
|
+
return this.windowProxy
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async update(options: AttachmentEntityUpdateOptions) {
|
|
29
|
+
if (this.isDestroyed) return
|
|
30
|
+
if (options.position) this.options.position = options.position
|
|
31
|
+
if (options.size) this.options.size = options.size
|
|
32
|
+
return new UpdateAttachmentEntityCommand(this.id, options).execute()
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function createAttachmentEntity(
|
|
37
|
+
options: AttachmentEntityOptions,
|
|
38
|
+
): Promise<Attachment> {
|
|
39
|
+
const result = await new CreateAttachmentEntityCommand(options).execute()
|
|
40
|
+
if (!result.success) {
|
|
41
|
+
throw new Error('createAttachmentEntity failed: ' + result?.errorMessage)
|
|
42
|
+
}
|
|
43
|
+
const { id, windowProxy } = result.data!
|
|
44
|
+
return new Attachment(id, windowProxy, options)
|
|
45
|
+
}
|
package/src/reality/index.ts
CHANGED
package/src/types/types.ts
CHANGED
|
@@ -344,12 +344,14 @@ export class CubeInfo {
|
|
|
344
344
|
|
|
345
345
|
export interface SpatialTapEventDetail {
|
|
346
346
|
location3D: Point3D
|
|
347
|
+
globalLocation3D?: Point3D
|
|
347
348
|
}
|
|
348
349
|
|
|
349
350
|
export type SpatialTapEvent = CustomEvent<SpatialTapEventDetail>
|
|
350
351
|
|
|
351
352
|
export interface SpatialDragStartEventDetail {
|
|
352
353
|
startLocation3D: Point3D
|
|
354
|
+
globalLocation3D?: Point3D
|
|
353
355
|
}
|
|
354
356
|
|
|
355
357
|
export interface SpatialDragEventDetail {
|
|
@@ -384,3 +386,14 @@ export type SpatialMagnifyEvent = CustomEvent<SpatialMagnifyEventDetail>
|
|
|
384
386
|
export type SpatialMagnifyEndEvent = CustomEvent<SpatialMagnifyEndEventDetail>
|
|
385
387
|
|
|
386
388
|
export type SpatialEntityOrReality = SpatialEntity | SpatializedDynamic3DElement
|
|
389
|
+
|
|
390
|
+
export interface AttachmentEntityOptions {
|
|
391
|
+
parentEntityId: string
|
|
392
|
+
position?: [number, number, number]
|
|
393
|
+
size: { width: number; height: number }
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
export interface AttachmentEntityUpdateOptions {
|
|
397
|
+
position?: [number, number, number]
|
|
398
|
+
size?: { width: number; height: number }
|
|
399
|
+
}
|