@shopify/ui-extensions-server-kit 0.0.0-experimental-20260902222805
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 +157 -0
- package/README.md +75 -0
- package/dist/ExtensionServerClient/ExtensionServerClient.d.ts +27 -0
- package/dist/ExtensionServerClient/ExtensionServerClient.test.d.ts +9 -0
- package/dist/ExtensionServerClient/index.d.ts +2 -0
- package/dist/ExtensionServerClient/server-types.d.ts +66 -0
- package/dist/ExtensionServerClient/types.d.ts +124 -0
- package/dist/_virtual/jsx-runtime.cjs.js +1 -0
- package/dist/_virtual/jsx-runtime.cjs2.js +1 -0
- package/dist/_virtual/jsx-runtime.es.js +5 -0
- package/dist/_virtual/jsx-runtime.es2.js +4 -0
- package/dist/_virtual/react-jsx-runtime.development.cjs.js +1 -0
- package/dist/_virtual/react-jsx-runtime.development.es.js +4 -0
- package/dist/_virtual/react-jsx-runtime.production.min.cjs.js +1 -0
- package/dist/_virtual/react-jsx-runtime.production.min.es.js +4 -0
- package/dist/context/ExtensionServerProvider.d.ts +3 -0
- package/dist/context/ExtensionServerProvider.test.d.ts +1 -0
- package/dist/context/constants.d.ts +2 -0
- package/dist/context/index.d.ts +3 -0
- package/dist/context/types.d.ts +12 -0
- package/dist/hooks/index.d.ts +3 -0
- package/dist/hooks/useExtensionServerContext.d.ts +1 -0
- package/dist/hooks/useExtensionServerState.d.ts +1 -0
- package/dist/hooks/useIsomorphicLayoutEffect.d.ts +2 -0
- package/dist/i18n.d.ts +68 -0
- package/dist/i18n.test.d.ts +1 -0
- package/dist/index.cjs.js +1 -0
- package/dist/index.cjs2.js +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.es.js +46 -0
- package/dist/index.es2.js +8 -0
- package/dist/node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react-jsx-runtime.development.cjs.js +22 -0
- package/dist/node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react-jsx-runtime.development.es.js +604 -0
- package/dist/node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react-jsx-runtime.production.min.cjs.js +9 -0
- package/dist/node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react-jsx-runtime.production.min.es.js +28 -0
- package/dist/node_modules/.pnpm/react@18.3.1/node_modules/react/jsx-runtime.cjs.js +1 -0
- package/dist/node_modules/.pnpm/react@18.3.1/node_modules/react/jsx-runtime.es.js +10 -0
- package/dist/packages/ui-extensions-server-kit/src/ExtensionServerClient/ExtensionServerClient.cjs.js +1 -0
- package/dist/packages/ui-extensions-server-kit/src/ExtensionServerClient/ExtensionServerClient.es.js +136 -0
- package/dist/packages/ui-extensions-server-kit/src/ExtensionServerClient/types.cjs.js +1 -0
- package/dist/packages/ui-extensions-server-kit/src/ExtensionServerClient/types.es.js +4 -0
- package/dist/packages/ui-extensions-server-kit/src/context/ExtensionServerProvider.cjs.js +1 -0
- package/dist/packages/ui-extensions-server-kit/src/context/ExtensionServerProvider.es.js +30 -0
- package/dist/packages/ui-extensions-server-kit/src/context/constants.cjs.js +1 -0
- package/dist/packages/ui-extensions-server-kit/src/context/constants.es.js +13 -0
- package/dist/packages/ui-extensions-server-kit/src/hooks/useExtensionServerContext.cjs.js +1 -0
- package/dist/packages/ui-extensions-server-kit/src/hooks/useExtensionServerContext.es.js +7 -0
- package/dist/packages/ui-extensions-server-kit/src/hooks/useExtensionServerState.cjs.js +1 -0
- package/dist/packages/ui-extensions-server-kit/src/hooks/useExtensionServerState.es.js +9 -0
- package/dist/packages/ui-extensions-server-kit/src/hooks/useIsomorphicLayoutEffect.cjs.js +1 -0
- package/dist/packages/ui-extensions-server-kit/src/hooks/useIsomorphicLayoutEffect.es.js +5 -0
- package/dist/packages/ui-extensions-server-kit/src/i18n.cjs.js +1 -0
- package/dist/packages/ui-extensions-server-kit/src/i18n.es.js +60 -0
- package/dist/packages/ui-extensions-server-kit/src/state/actions/actions.cjs.js +1 -0
- package/dist/packages/ui-extensions-server-kit/src/state/actions/actions.es.js +37 -0
- package/dist/packages/ui-extensions-server-kit/src/state/reducers/constants.cjs.js +1 -0
- package/dist/packages/ui-extensions-server-kit/src/state/reducers/constants.es.js +7 -0
- package/dist/packages/ui-extensions-server-kit/src/state/reducers/extensionServerReducer.cjs.js +1 -0
- package/dist/packages/ui-extensions-server-kit/src/state/reducers/extensionServerReducer.es.js +57 -0
- package/dist/packages/ui-extensions-server-kit/src/testing/MockExtensionServerProvider.cjs.js +1 -0
- package/dist/packages/ui-extensions-server-kit/src/testing/MockExtensionServerProvider.es.js +25 -0
- package/dist/packages/ui-extensions-server-kit/src/testing/app.cjs.js +1 -0
- package/dist/packages/ui-extensions-server-kit/src/testing/app.es.js +16 -0
- package/dist/packages/ui-extensions-server-kit/src/testing/extensions.cjs.js +1 -0
- package/dist/packages/ui-extensions-server-kit/src/testing/extensions.es.js +66 -0
- package/dist/packages/ui-extensions-server-kit/src/types.cjs.js +1 -0
- package/dist/packages/ui-extensions-server-kit/src/types.es.js +4 -0
- package/dist/packages/ui-extensions-server-kit/src/utilities/assetToString.cjs.js +1 -0
- package/dist/packages/ui-extensions-server-kit/src/utilities/assetToString.es.js +7 -0
- package/dist/packages/ui-extensions-server-kit/src/utilities/isUIExtension.cjs.js +1 -0
- package/dist/packages/ui-extensions-server-kit/src/utilities/isUIExtension.es.js +7 -0
- package/dist/packages/ui-extensions-server-kit/src/utilities/isValidSurface.cjs.js +1 -0
- package/dist/packages/ui-extensions-server-kit/src/utilities/isValidSurface.es.js +7 -0
- package/dist/packages/ui-extensions-server-kit/src/utilities/noop.cjs.js +1 -0
- package/dist/packages/ui-extensions-server-kit/src/utilities/noop.es.js +5 -0
- package/dist/packages/ui-extensions-server-kit/src/utilities/replaceUpdated.cjs.js +1 -0
- package/dist/packages/ui-extensions-server-kit/src/utilities/replaceUpdated.es.js +14 -0
- package/dist/packages/ui-extensions-server-kit/src/utilities/set.cjs.js +1 -0
- package/dist/packages/ui-extensions-server-kit/src/utilities/set.es.js +18 -0
- package/dist/state/actions/actions.d.ts +6 -0
- package/dist/state/actions/index.d.ts +2 -0
- package/dist/state/actions/types.d.ts +21 -0
- package/dist/state/index.d.ts +2 -0
- package/dist/state/reducers/constants.d.ts +2 -0
- package/dist/state/reducers/extensionServerReducer.d.ts +3 -0
- package/dist/state/reducers/extensionServerReducer.test.d.ts +1 -0
- package/dist/state/reducers/index.d.ts +3 -0
- package/dist/state/reducers/types.d.ts +6 -0
- package/dist/testing/MockExtensionServerProvider.d.ts +8 -0
- package/dist/testing/app.d.ts +2 -0
- package/dist/testing/extensions.d.ts +6 -0
- package/dist/testing/index.d.ts +3 -0
- package/dist/types.d.ts +196 -0
- package/dist/utilities/assetToString.d.ts +2 -0
- package/dist/utilities/assetToString.test.d.ts +1 -0
- package/dist/utilities/index.d.ts +6 -0
- package/dist/utilities/isUIExtension.d.ts +1 -0
- package/dist/utilities/isValidSurface.d.ts +2 -0
- package/dist/utilities/noop.d.ts +1 -0
- package/dist/utilities/replaceUpdated.d.ts +1 -0
- package/dist/utilities/replaceUpdated.test.d.ts +1 -0
- package/dist/utilities/set.d.ts +4 -0
- package/dist/utilities/set.test.d.ts +1 -0
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/index.mjs +1 -0
- package/node_modules/@testing-library/react/LICENSE +20 -0
- package/node_modules/@testing-library/react/README.md +692 -0
- package/node_modules/@testing-library/react/dist/@testing-library/react.cjs.js +537 -0
- package/node_modules/@testing-library/react/dist/@testing-library/react.esm.js +497 -0
- package/node_modules/@testing-library/react/dist/@testing-library/react.pure.cjs.js +501 -0
- package/node_modules/@testing-library/react/dist/@testing-library/react.pure.esm.js +461 -0
- package/node_modules/@testing-library/react/dist/@testing-library/react.pure.umd.js +502 -0
- package/node_modules/@testing-library/react/dist/@testing-library/react.pure.umd.js.map +1 -0
- package/node_modules/@testing-library/react/dist/@testing-library/react.pure.umd.min.js +2 -0
- package/node_modules/@testing-library/react/dist/@testing-library/react.pure.umd.min.js.map +1 -0
- package/node_modules/@testing-library/react/dist/@testing-library/react.umd.js +538 -0
- package/node_modules/@testing-library/react/dist/@testing-library/react.umd.js.map +1 -0
- package/node_modules/@testing-library/react/dist/@testing-library/react.umd.min.js +2 -0
- package/node_modules/@testing-library/react/dist/@testing-library/react.umd.min.js.map +1 -0
- package/node_modules/@testing-library/react/dist/act-compat.js +80 -0
- package/node_modules/@testing-library/react/dist/config.js +33 -0
- package/node_modules/@testing-library/react/dist/fire-event.js +70 -0
- package/node_modules/@testing-library/react/dist/index.js +52 -0
- package/node_modules/@testing-library/react/dist/pure.js +356 -0
- package/node_modules/@testing-library/react/dont-cleanup-after-each.js +1 -0
- package/node_modules/@testing-library/react/package.json +116 -0
- package/node_modules/@testing-library/react/pure.d.ts +1 -0
- package/node_modules/@testing-library/react/pure.js +2 -0
- package/node_modules/@testing-library/react/types/index.d.ts +287 -0
- package/node_modules/@testing-library/react/types/pure.d.ts +1 -0
- package/node_modules/@types/react/LICENSE +21 -0
- package/node_modules/@types/react/README.md +15 -0
- package/node_modules/@types/react/canary.d.ts +166 -0
- package/node_modules/@types/react/experimental.d.ts +127 -0
- package/node_modules/@types/react/global.d.ts +160 -0
- package/node_modules/@types/react/index.d.ts +4543 -0
- package/node_modules/@types/react/jsx-dev-runtime.d.ts +45 -0
- package/node_modules/@types/react/jsx-runtime.d.ts +36 -0
- package/node_modules/@types/react/package.json +206 -0
- package/node_modules/@types/react/ts5.0/canary.d.ts +166 -0
- package/node_modules/@types/react/ts5.0/experimental.d.ts +127 -0
- package/node_modules/@types/react/ts5.0/global.d.ts +160 -0
- package/node_modules/@types/react/ts5.0/index.d.ts +4530 -0
- package/node_modules/@types/react/ts5.0/jsx-dev-runtime.d.ts +44 -0
- package/node_modules/@types/react/ts5.0/jsx-runtime.d.ts +35 -0
- package/node_modules/@vitejs/plugin-react/LICENSE +21 -0
- package/node_modules/@vitejs/plugin-react/README.md +159 -0
- package/node_modules/@vitejs/plugin-react/dist/index.d.ts +75 -0
- package/node_modules/@vitejs/plugin-react/dist/index.js +401 -0
- package/node_modules/@vitejs/plugin-react/dist/refresh-runtime.js +663 -0
- package/node_modules/@vitejs/plugin-react/package.json +77 -0
- package/node_modules/@vitejs/plugin-react/types/preamble.d.ts +1 -0
- package/package.json +64 -0
- package/project.json +40 -0
- package/scripts/create-entry-files.ts +44 -0
- package/src/ExtensionServerClient/ExtensionServerClient.test.ts +1238 -0
- package/src/ExtensionServerClient/ExtensionServerClient.ts +320 -0
- package/src/ExtensionServerClient/index.ts +2 -0
- package/src/ExtensionServerClient/server-types.ts +65 -0
- package/src/ExtensionServerClient/types.ts +159 -0
- package/src/context/ExtensionServerProvider.test.tsx +318 -0
- package/src/context/ExtensionServerProvider.tsx +48 -0
- package/src/context/constants.ts +16 -0
- package/src/context/index.ts +3 -0
- package/src/context/types.ts +14 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useExtensionServerContext.ts +4 -0
- package/src/hooks/useExtensionServerState.ts +6 -0
- package/src/hooks/useIsomorphicLayoutEffect.ts +6 -0
- package/src/i18n.test.ts +417 -0
- package/src/i18n.ts +205 -0
- package/src/index.ts +7 -0
- package/src/state/actions/actions.ts +36 -0
- package/src/state/actions/index.ts +2 -0
- package/src/state/actions/types.ts +26 -0
- package/src/state/index.ts +2 -0
- package/src/state/reducers/constants.ts +6 -0
- package/src/state/reducers/extensionServerReducer.test.ts +165 -0
- package/src/state/reducers/extensionServerReducer.ts +87 -0
- package/src/state/reducers/index.ts +3 -0
- package/src/state/reducers/types.ts +7 -0
- package/src/testing/MockExtensionServerProvider.tsx +36 -0
- package/src/testing/app.ts +15 -0
- package/src/testing/extensions.ts +71 -0
- package/src/testing/index.ts +3 -0
- package/src/types.ts +204 -0
- package/src/utilities/assetToString.test.ts +16 -0
- package/src/utilities/assetToString.ts +7 -0
- package/src/utilities/index.ts +6 -0
- package/src/utilities/isUIExtension.ts +8 -0
- package/src/utilities/isValidSurface.ts +6 -0
- package/src/utilities/noop.ts +1 -0
- package/src/utilities/replaceUpdated.test.ts +26 -0
- package/src/utilities/replaceUpdated.ts +15 -0
- package/src/utilities/set.test.ts +19 -0
- package/src/utilities/set.ts +35 -0
- package/testing.d.ts +1 -0
- package/testing.js +1 -0
- package/testing.mjs +1 -0
- package/tests/setup.ts +3 -0
- package/vite.config.mts +57 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import {ExtensionServer} from './server-types.js'
|
|
3
|
+
import {Surface} from './types.js'
|
|
4
|
+
import {
|
|
5
|
+
FlattenedLocalization,
|
|
6
|
+
Localization,
|
|
7
|
+
TRANSLATED_KEYS,
|
|
8
|
+
getFlattenedLocalization,
|
|
9
|
+
isFlattenedTranslations,
|
|
10
|
+
} from '../i18n'
|
|
11
|
+
import {isUIExtension, isValidSurface} from '../utilities'
|
|
12
|
+
import {DeepPartial, ExtensionPayload, ExtensionPoint} from '../types'
|
|
13
|
+
|
|
14
|
+
export class ExtensionServerClient implements ExtensionServer.Client {
|
|
15
|
+
public id: string
|
|
16
|
+
|
|
17
|
+
public connection!: WebSocket
|
|
18
|
+
|
|
19
|
+
public options: ExtensionServer.Options
|
|
20
|
+
|
|
21
|
+
protected EVENT_THAT_WILL_MUTATE_THE_SERVER = ['update']
|
|
22
|
+
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
24
|
+
protected listeners: Record<string, Set<(payload: any) => void>> = {}
|
|
25
|
+
protected connectionListeners: {close: Set<(event: Event) => void>; open: Set<(event: Event) => void>} = {
|
|
26
|
+
close: new Set(),
|
|
27
|
+
open: new Set(),
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
protected connected = false
|
|
31
|
+
|
|
32
|
+
private uiExtensionsByUuid: Record<string, ExtensionServer.UIExtension> = {}
|
|
33
|
+
|
|
34
|
+
constructor(options: DeepPartial<ExtensionServer.Options> = {}) {
|
|
35
|
+
this.id = (Math.random() + 1).toString(36).substring(7)
|
|
36
|
+
this.options = getValidatedOptions({
|
|
37
|
+
...options,
|
|
38
|
+
connection: {
|
|
39
|
+
automaticConnect: true,
|
|
40
|
+
protocols: [],
|
|
41
|
+
...(options.connection ?? {}),
|
|
42
|
+
},
|
|
43
|
+
}) as ExtensionServer.Options
|
|
44
|
+
|
|
45
|
+
this.setupConnection(this.options.connection.automaticConnect)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public connect(options: ExtensionServer.Options = {connection: {}}) {
|
|
49
|
+
const newOptions = mergeOptions(this.options, options)
|
|
50
|
+
const optionsChanged = JSON.stringify(newOptions) !== JSON.stringify(this.options)
|
|
51
|
+
|
|
52
|
+
if (optionsChanged) {
|
|
53
|
+
this.options = newOptions as ExtensionServer.Options
|
|
54
|
+
this.setupConnection(true)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return () => {
|
|
58
|
+
this.closeConnection()
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public on<TEvent extends keyof ExtensionServer.InboundEvents>(
|
|
63
|
+
event: TEvent,
|
|
64
|
+
listener: (payload: ExtensionServer.InboundEvents[TEvent]) => void,
|
|
65
|
+
): () => void {
|
|
66
|
+
if (!this.listeners[event as string]) {
|
|
67
|
+
this.listeners[event as string] = new Set()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
this.listeners[event as string].add(listener)
|
|
71
|
+
return () => this.listeners[event as string].delete(listener)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public persist<TEvent extends keyof ExtensionServer.OutboundPersistEvents>(
|
|
75
|
+
event: TEvent,
|
|
76
|
+
data: ExtensionServer.OutboundPersistEvents[TEvent],
|
|
77
|
+
): void {
|
|
78
|
+
if (this.EVENT_THAT_WILL_MUTATE_THE_SERVER.includes(event as string)) {
|
|
79
|
+
if (!this.options.locales) {
|
|
80
|
+
return this.connection?.send(JSON.stringify({event, data}))
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Since each websocket connection will have its own translated values
|
|
85
|
+
* we need to strip out all translated properties to prevent
|
|
86
|
+
* mutating the Dev Server's data
|
|
87
|
+
* Before:
|
|
88
|
+
* ```
|
|
89
|
+
* {
|
|
90
|
+
* name: 'en name'
|
|
91
|
+
* localization: {...},
|
|
92
|
+
* extensionPoints: [{
|
|
93
|
+
* target: 'admin.product.item.action'
|
|
94
|
+
* name: 'en name'
|
|
95
|
+
* description: 'en description'
|
|
96
|
+
* localization: {...}
|
|
97
|
+
* }],
|
|
98
|
+
* }
|
|
99
|
+
* ```
|
|
100
|
+
* After:
|
|
101
|
+
* ```
|
|
102
|
+
* extensionPoints: [{
|
|
103
|
+
* target: 'admin.product.item.action'
|
|
104
|
+
* }],
|
|
105
|
+
* }
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
109
|
+
;(data as any).extensions?.forEach((extension: ExtensionPayload) => {
|
|
110
|
+
TRANSLATED_KEYS.forEach((key) => {
|
|
111
|
+
if (isUIExtension(extension)) {
|
|
112
|
+
extension.extensionPoints?.forEach((extensionPoint) => {
|
|
113
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
114
|
+
delete (extensionPoint as any)[key]
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
118
|
+
delete (extension as any)[key]
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
return this.connection?.send(JSON.stringify({event, data}))
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
console.warn(`You tried to use "persist" with a dispatch event. Please use the "emit" method instead.`)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
public emit<TEvent extends keyof ExtensionServer.DispatchEvents>(...args: ExtensionServer.EmitArgs<TEvent>): void {
|
|
128
|
+
const event = args[0]
|
|
129
|
+
const data = args.length > 1 ? args[1] : undefined
|
|
130
|
+
|
|
131
|
+
if (this.EVENT_THAT_WILL_MUTATE_THE_SERVER.includes(event as string)) {
|
|
132
|
+
return console.warn(
|
|
133
|
+
`You tried to use "emit" with a the "${event}" event. Please use the "persist" method instead to persist changes to the server.`,
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
this.connection?.send(JSON.stringify({event: 'dispatch', data: {type: event, payload: data}}))
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
public onConnection<TEvent extends keyof typeof this.connectionListeners>(
|
|
141
|
+
event: TEvent,
|
|
142
|
+
listener: (event: Event) => void,
|
|
143
|
+
): () => void {
|
|
144
|
+
this.connectionListeners[event].add(listener)
|
|
145
|
+
return () => this.connectionListeners[event].delete(listener)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
protected initializeConnection() {
|
|
149
|
+
if (!this.connection) {
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
this.connection.addEventListener('open', (event) => {
|
|
154
|
+
this.connected = true
|
|
155
|
+
this.connectionListeners.open.forEach((listener) => listener(event))
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
this.connection.addEventListener('close', (event) => {
|
|
159
|
+
this.connected = false
|
|
160
|
+
this.connectionListeners.close.forEach((listener) => listener(event))
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
this.connection?.addEventListener('message', (message) => {
|
|
164
|
+
try {
|
|
165
|
+
const {event, data} = JSON.parse(message.data) as ExtensionServer.ServerEvents
|
|
166
|
+
const typedData = data
|
|
167
|
+
|
|
168
|
+
if (event === 'dispatch') {
|
|
169
|
+
const {type, payload} = typedData as {type: string; payload: unknown}
|
|
170
|
+
this.listeners[type]?.forEach((listener) => listener(payload))
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const filteredExtensions = typedData.extensions
|
|
175
|
+
? filterExtensionsBySurface(typedData.extensions, this.options.surface)
|
|
176
|
+
: typedData.extensions
|
|
177
|
+
|
|
178
|
+
this.listeners[event]?.forEach((listener) => {
|
|
179
|
+
listener({...typedData, extensions: this._getLocalizedExtensions(filteredExtensions)})
|
|
180
|
+
})
|
|
181
|
+
// eslint-disable-next-line no-catch-all/no-catch-all
|
|
182
|
+
} catch (err) {
|
|
183
|
+
console.error(
|
|
184
|
+
`[ExtensionServer] Something went wrong while parsing a server message:`,
|
|
185
|
+
err instanceof Error ? err.message : err,
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
protected setupConnection(connectWebsocket = true) {
|
|
192
|
+
if (!this.options.connection.url) {
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (!connectWebsocket) {
|
|
197
|
+
return
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
this.closeConnection()
|
|
201
|
+
|
|
202
|
+
this.connection = new WebSocket(this.options.connection.url, this.options.connection.protocols)
|
|
203
|
+
|
|
204
|
+
this.initializeConnection()
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
protected closeConnection() {
|
|
208
|
+
if (this.connected) {
|
|
209
|
+
this.connection?.close()
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private _getLocalizedExtensions(extensions?: ExtensionPayload[]) {
|
|
214
|
+
return extensions?.map((extension) => {
|
|
215
|
+
if (!this.options.locales || !isUIExtension(extension)) {
|
|
216
|
+
return extension
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const shouldUpdateTranslations =
|
|
220
|
+
this.uiExtensionsByUuid[extension.uuid]?.localization?.lastUpdated !== extension.localization?.lastUpdated
|
|
221
|
+
|
|
222
|
+
const localization = shouldUpdateTranslations
|
|
223
|
+
? getFlattenedLocalization(extension.localization, this.options.locales)
|
|
224
|
+
: (this.uiExtensionsByUuid[extension.uuid]?.localization ?? extension.localization)
|
|
225
|
+
|
|
226
|
+
const parsedTranslation: Record<string, string> =
|
|
227
|
+
localization && isFlattenedTranslations(localization) ? JSON.parse(localization.translations) : localization
|
|
228
|
+
|
|
229
|
+
const localizedExtension = {
|
|
230
|
+
...extension,
|
|
231
|
+
localization,
|
|
232
|
+
name:
|
|
233
|
+
parsedTranslation && extension.name.startsWith('t:')
|
|
234
|
+
? this._getLocalizedValue(parsedTranslation, extension.name)
|
|
235
|
+
: extension.name,
|
|
236
|
+
...(extension.description && {
|
|
237
|
+
description:
|
|
238
|
+
parsedTranslation && extension.description?.startsWith('t:')
|
|
239
|
+
? this._getLocalizedValue(parsedTranslation, extension.description)
|
|
240
|
+
: extension.description,
|
|
241
|
+
}),
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
this.uiExtensionsByUuid[extension.uuid] = {
|
|
245
|
+
...localizedExtension,
|
|
246
|
+
extensionPoints: this._getLocalizedExtensionPoints(localization, localizedExtension),
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return this.uiExtensionsByUuid[extension.uuid]
|
|
250
|
+
})
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
private _getLocalizedExtensionPoints(
|
|
254
|
+
localization: FlattenedLocalization | Localization | null | undefined,
|
|
255
|
+
{extensionPoints, name, description}: ExtensionServer.UIExtension,
|
|
256
|
+
): ExtensionPoint[] {
|
|
257
|
+
if (!localization || !isFlattenedTranslations(localization)) {
|
|
258
|
+
return extensionPoints
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return extensionPoints?.map((extensionPoint) => {
|
|
262
|
+
return {
|
|
263
|
+
...extensionPoint,
|
|
264
|
+
localization,
|
|
265
|
+
name,
|
|
266
|
+
...(description && {description}),
|
|
267
|
+
}
|
|
268
|
+
})
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private _getLocalizedValue(translations: Record<string, string>, value: string): string {
|
|
272
|
+
const translationKey = value.replace('t:', '')
|
|
273
|
+
return translations[translationKey] || value
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function mergeOptions(options: ExtensionServer.Options, newOptions: ExtensionServer.Options) {
|
|
278
|
+
return getValidatedOptions({
|
|
279
|
+
...options,
|
|
280
|
+
...newOptions,
|
|
281
|
+
connection: {
|
|
282
|
+
...options.connection,
|
|
283
|
+
...newOptions.connection,
|
|
284
|
+
},
|
|
285
|
+
})
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function getValidatedOptions<TOptions extends DeepPartial<ExtensionServer.Options>>(options: TOptions): TOptions {
|
|
289
|
+
if (!isValidSurface(options.surface)) {
|
|
290
|
+
delete options.surface
|
|
291
|
+
}
|
|
292
|
+
return options
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function filterExtensionsBySurface(extensions: ExtensionPayload[], surface: Surface | undefined): ExtensionPayload[] {
|
|
296
|
+
if (!surface) {
|
|
297
|
+
return extensions
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return extensions.filter((extension) => {
|
|
301
|
+
if (extension.surface === surface) {
|
|
302
|
+
return true
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (Array.isArray(extension.extensionPoints)) {
|
|
306
|
+
const extensionPoints = extension.extensionPoints as (string | {surface: Surface; [key: string]: unknown})[]
|
|
307
|
+
const extensionPointMatchingSurface = extensionPoints.filter((extensionPoint) => {
|
|
308
|
+
if (typeof extensionPoint === 'string') {
|
|
309
|
+
return false
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return extensionPoint.surface === surface
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
return extensionPointMatchingSurface.length > 0
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return false
|
|
319
|
+
})
|
|
320
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import {Surface} from './types.js'
|
|
2
|
+
import {ExtensionPayload, ExtensionPoint, App} from '../types'
|
|
3
|
+
import {FlattenedLocalization, LocalesOptions, Localization} from '../i18n'
|
|
4
|
+
|
|
5
|
+
// Re-export and augment the global ExtensionServer namespace
|
|
6
|
+
export namespace ExtensionServer {
|
|
7
|
+
export interface UIExtension extends ExtensionPayload {
|
|
8
|
+
extensionPoints: ExtensionPoint[]
|
|
9
|
+
localization?: FlattenedLocalization | Localization | null
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface Client {
|
|
13
|
+
id: string
|
|
14
|
+
connection: WebSocket
|
|
15
|
+
options: Options
|
|
16
|
+
connect(options?: Options): () => void
|
|
17
|
+
on<TEvent extends keyof InboundEvents>(
|
|
18
|
+
event: TEvent,
|
|
19
|
+
listener: (payload: InboundEvents[TEvent]) => void,
|
|
20
|
+
): () => void
|
|
21
|
+
persist<TEvent extends keyof OutboundPersistEvents>(event: TEvent, data: OutboundPersistEvents[TEvent]): void
|
|
22
|
+
emit<TEvent extends keyof DispatchEvents>(...args: EmitArgs<TEvent>): void
|
|
23
|
+
onConnection<TEvent extends 'close' | 'open'>(event: TEvent, listener: (event: Event) => void): () => void
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface Options {
|
|
27
|
+
connection: {
|
|
28
|
+
url?: string
|
|
29
|
+
automaticConnect?: boolean
|
|
30
|
+
protocols?: string | string[]
|
|
31
|
+
}
|
|
32
|
+
surface?: Surface
|
|
33
|
+
locales?: LocalesOptions
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ServerEvents {
|
|
37
|
+
event: string
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
39
|
+
data: any
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface InboundEvents extends DispatchEvents {
|
|
43
|
+
dispatch: {type: keyof DispatchEvents; payload: DispatchEvents[keyof DispatchEvents]}
|
|
44
|
+
connected: {extensions: ExtensionPayload[]; app?: App; store: string}
|
|
45
|
+
update: {extensions?: ExtensionPayload[]; app?: App}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface OutboundPersistEvents {
|
|
49
|
+
update: {
|
|
50
|
+
extensions?: ExtensionPayload[]
|
|
51
|
+
app?: App
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface DispatchEvents {
|
|
56
|
+
refresh: {uuid: string}[]
|
|
57
|
+
focus: {uuid: string}[]
|
|
58
|
+
unfocus: void
|
|
59
|
+
navigate: {url: string}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type EmitArgs<TEvent extends keyof DispatchEvents> = undefined extends DispatchEvents[TEvent]
|
|
63
|
+
? [event: TEvent]
|
|
64
|
+
: [event: TEvent, payload: DispatchEvents[TEvent]]
|
|
65
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import type {LocalesOptions} from '../i18n'
|
|
2
|
+
|
|
3
|
+
declare global {
|
|
4
|
+
namespace ExtensionServer {
|
|
5
|
+
/**
|
|
6
|
+
* Events being received by the extension server where the keys are the event names
|
|
7
|
+
* and the values are the payload of the given action. In case no payload is
|
|
8
|
+
* required, a value of void should be used.
|
|
9
|
+
*/
|
|
10
|
+
interface InboundEvents {
|
|
11
|
+
//
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Events being sent to the extension server where the keys are the event names
|
|
16
|
+
* and the values are the payload of the given action. In case no payload is
|
|
17
|
+
* required, a value of void should be used.
|
|
18
|
+
*
|
|
19
|
+
* Persist events are those that will generate changes on the server, like
|
|
20
|
+
* update and connected events. Dispatch events are those that will
|
|
21
|
+
* simply be proxied to the clients connected to the server.
|
|
22
|
+
*/
|
|
23
|
+
interface OutboundPersistEvents {
|
|
24
|
+
//
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface DispatchEvents {
|
|
28
|
+
//
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Extension server client class options. These are used to configure
|
|
33
|
+
* the client class.
|
|
34
|
+
*/
|
|
35
|
+
interface Options {
|
|
36
|
+
connection: {
|
|
37
|
+
/**
|
|
38
|
+
* The absolute URL of the WebSocket.
|
|
39
|
+
*/
|
|
40
|
+
url?: string
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* This defines if we should automatically attempt to connect when the
|
|
44
|
+
* class is instantiated.
|
|
45
|
+
*
|
|
46
|
+
* @defaultValue true
|
|
47
|
+
*/
|
|
48
|
+
automaticConnect?: boolean
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The sub-protocol selected by the server.
|
|
52
|
+
*
|
|
53
|
+
* @defaultValue []
|
|
54
|
+
*/
|
|
55
|
+
protocols?: string | string[]
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* If provided the extension server will only return extensions that matches the specified surface
|
|
59
|
+
*/
|
|
60
|
+
surface?: Surface
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* If provided the extension server will return a requested translations object with flattened
|
|
64
|
+
* translations for each extension matching the requested locales
|
|
65
|
+
*/
|
|
66
|
+
locales?: LocalesOptions
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Extension server client class. This class will be used to connect and
|
|
71
|
+
* communicate with the extension server.
|
|
72
|
+
*/
|
|
73
|
+
interface Client {
|
|
74
|
+
/**
|
|
75
|
+
* Connection options
|
|
76
|
+
*/
|
|
77
|
+
options: Options
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Reconnecting WebSocket Client
|
|
81
|
+
*/
|
|
82
|
+
connection: WebSocket
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Function to add an event listener to messages coming from
|
|
86
|
+
* the extension server connection.
|
|
87
|
+
*/
|
|
88
|
+
on<TEvent extends keyof ExtensionServer.InboundEvents>(
|
|
89
|
+
event: TEvent,
|
|
90
|
+
cb: EventListener<TEvent>,
|
|
91
|
+
): EventUnsubscriber
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Function to emit an event that will persist changes to the extension server.
|
|
95
|
+
*/
|
|
96
|
+
persist<TEvent extends keyof OutboundPersistEvents>(event: TEvent, payload: OutboundPersistEvents[TEvent]): void
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Function to emit an event to the extension server.
|
|
100
|
+
*/
|
|
101
|
+
emit<TEvent extends keyof DispatchEvents>(...args: EmitArgs<TEvent>): void
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Function that opens a connection with the extensions server.
|
|
105
|
+
*/
|
|
106
|
+
connect(options?: Options): () => void
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* This defines how the ExtensionServer client's static class is defined and the constructor
|
|
111
|
+
* arguments it requires.
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```
|
|
115
|
+
* const client = new ExtensionServer({ url: 'wss://localhost:1234' });
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
type StaticClient = Static<ExtensionServer.Client, [option?: ExtensionServer.Options]>
|
|
119
|
+
|
|
120
|
+
// Utilities
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* This helper type allows us to account for nullish payloads on the emit function.
|
|
124
|
+
* In practice, this will allow TypeScript to type-check the event being emitted
|
|
125
|
+
* and, if the payload isn't required, the second argument won't be necessary.
|
|
126
|
+
*/
|
|
127
|
+
type EmitArgs<TEvent extends keyof ExtensionServer.DispatchEvents> =
|
|
128
|
+
ExtensionServer.DispatchEvents[TEvent] extends void
|
|
129
|
+
? [event: TEvent]
|
|
130
|
+
: [event: TEvent, payload: ExtensionServer.DispatchEvents[TEvent]]
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* This is a helper interface that allows us to define the static methods of a given
|
|
134
|
+
* class. This is useful to define static methods, static properties
|
|
135
|
+
* and constructor variables.
|
|
136
|
+
*/
|
|
137
|
+
interface Static<T = unknown, TArgs extends unknown[] = unknown[]> {
|
|
138
|
+
prototype: T
|
|
139
|
+
new (...args: TArgs): T
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* This helper creates a partial interface with exception to the defined key values.
|
|
144
|
+
*/
|
|
145
|
+
type PartialExcept<TOject, TKey extends keyof TOject> = Partial<Omit<TOject, TKey>> & Pick<TOject, TKey>
|
|
146
|
+
|
|
147
|
+
type EventListener<TEvent extends keyof ExtensionServer.InboundEvents> = (
|
|
148
|
+
payload: ExtensionServer.InboundEvents[TEvent],
|
|
149
|
+
) => void
|
|
150
|
+
|
|
151
|
+
type EventUnsubscriber = () => void
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export const AVAILABLE_SURFACES = ['admin', 'checkout', 'post_purchase', 'point_of_sale', 'customer-accounts'] as const
|
|
156
|
+
|
|
157
|
+
export type Surface = (typeof AVAILABLE_SURFACES)[number]
|
|
158
|
+
|
|
159
|
+
export {}
|