@miaixz/sdk 0.5.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/LICENSE +202 -0
- package/NOTICE +7 -0
- package/README.md +412 -0
- package/dist/api/client.d.ts +223 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +1069 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/endpoint.d.ts +10 -0
- package/dist/api/endpoint.d.ts.map +1 -0
- package/dist/api/endpoint.js +51 -0
- package/dist/api/endpoint.js.map +1 -0
- package/dist/api/errors.d.ts +180 -0
- package/dist/api/errors.d.ts.map +1 -0
- package/dist/api/errors.js +173 -0
- package/dist/api/errors.js.map +1 -0
- package/dist/api/index.d.ts +6 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +5 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/request.d.ts +135 -0
- package/dist/api/request.d.ts.map +1 -0
- package/dist/api/request.js +2 -0
- package/dist/api/request.js.map +1 -0
- package/dist/api/response.d.ts +69 -0
- package/dist/api/response.d.ts.map +1 -0
- package/dist/api/response.js +43 -0
- package/dist/api/response.js.map +1 -0
- package/dist/api/telemetry.d.ts +139 -0
- package/dist/api/telemetry.d.ts.map +1 -0
- package/dist/api/telemetry.js +2 -0
- package/dist/api/telemetry.js.map +1 -0
- package/dist/appearance/appearance.d.ts +161 -0
- package/dist/appearance/appearance.d.ts.map +1 -0
- package/dist/appearance/appearance.js +614 -0
- package/dist/appearance/appearance.js.map +1 -0
- package/dist/appearance/index.d.ts +3 -0
- package/dist/appearance/index.d.ts.map +1 -0
- package/dist/appearance/index.js +3 -0
- package/dist/appearance/index.js.map +1 -0
- package/dist/auth/auth.d.ts +197 -0
- package/dist/auth/auth.d.ts.map +1 -0
- package/dist/auth/auth.js +314 -0
- package/dist/auth/auth.js.map +1 -0
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +2 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/config/config.d.ts +117 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/config.js +241 -0
- package/dist/config/config.js.map +1 -0
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +2 -0
- package/dist/config/index.js.map +1 -0
- package/dist/consts/index.d.ts +35 -0
- package/dist/consts/index.d.ts.map +1 -0
- package/dist/consts/index.js +35 -0
- package/dist/consts/index.js.map +1 -0
- package/dist/context/context.d.ts +123 -0
- package/dist/context/context.d.ts.map +1 -0
- package/dist/context/context.js +252 -0
- package/dist/context/context.js.map +1 -0
- package/dist/context/index.d.ts +2 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +2 -0
- package/dist/context/index.js.map +1 -0
- package/dist/contracts/host-context.d.ts +132 -0
- package/dist/contracts/host-context.d.ts.map +1 -0
- package/dist/contracts/host-context.js +2 -0
- package/dist/contracts/host-context.js.map +1 -0
- package/dist/contracts/index.d.ts +8 -0
- package/dist/contracts/index.d.ts.map +1 -0
- package/dist/contracts/index.js +2 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/contracts/lifecycle.d.ts +49 -0
- package/dist/contracts/lifecycle.d.ts.map +1 -0
- package/dist/contracts/lifecycle.js +2 -0
- package/dist/contracts/lifecycle.js.map +1 -0
- package/dist/contracts/module-manifest.d.ts +110 -0
- package/dist/contracts/module-manifest.d.ts.map +1 -0
- package/dist/contracts/module-manifest.js +317 -0
- package/dist/contracts/module-manifest.js.map +1 -0
- package/dist/contracts/navigation.d.ts +36 -0
- package/dist/contracts/navigation.d.ts.map +1 -0
- package/dist/contracts/navigation.js +11 -0
- package/dist/contracts/navigation.js.map +1 -0
- package/dist/contracts/permission.d.ts +8 -0
- package/dist/contracts/permission.d.ts.map +1 -0
- package/dist/contracts/permission.js +14 -0
- package/dist/contracts/permission.js.map +1 -0
- package/dist/contracts/post-message.d.ts +124 -0
- package/dist/contracts/post-message.d.ts.map +1 -0
- package/dist/contracts/post-message.js +2 -0
- package/dist/contracts/post-message.js.map +1 -0
- package/dist/contracts/route.d.ts +45 -0
- package/dist/contracts/route.d.ts.map +1 -0
- package/dist/contracts/route.js +35 -0
- package/dist/contracts/route.js.map +1 -0
- package/dist/events/events.d.ts +191 -0
- package/dist/events/events.d.ts.map +1 -0
- package/dist/events/events.js +486 -0
- package/dist/events/events.js.map +1 -0
- package/dist/events/index.d.ts +2 -0
- package/dist/events/index.d.ts.map +1 -0
- package/dist/events/index.js +2 -0
- package/dist/events/index.js.map +1 -0
- package/dist/files/files.d.ts +138 -0
- package/dist/files/files.d.ts.map +1 -0
- package/dist/files/files.js +144 -0
- package/dist/files/files.js.map +1 -0
- package/dist/files/index.d.ts +2 -0
- package/dist/files/index.d.ts.map +1 -0
- package/dist/files/index.js +2 -0
- package/dist/files/index.js.map +1 -0
- package/dist/formatters/index.d.ts +44 -0
- package/dist/formatters/index.d.ts.map +1 -0
- package/dist/formatters/index.js +50 -0
- package/dist/formatters/index.js.map +1 -0
- package/dist/i18n/default-translator.d.ts +17 -0
- package/dist/i18n/default-translator.d.ts.map +1 -0
- package/dist/i18n/default-translator.js +19 -0
- package/dist/i18n/default-translator.js.map +1 -0
- package/dist/i18n/i18n.d.ts +248 -0
- package/dist/i18n/i18n.d.ts.map +1 -0
- package/dist/i18n/i18n.js +617 -0
- package/dist/i18n/i18n.js.map +1 -0
- package/dist/i18n/index.d.ts +2 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +2 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/models/index.d.ts +2 -0
- package/dist/models/index.d.ts.map +1 -0
- package/dist/models/index.js +2 -0
- package/dist/models/index.js.map +1 -0
- package/dist/permissions/index.d.ts +2 -0
- package/dist/permissions/index.d.ts.map +1 -0
- package/dist/permissions/index.js +2 -0
- package/dist/permissions/index.js.map +1 -0
- package/dist/permissions/permissions.d.ts +67 -0
- package/dist/permissions/permissions.d.ts.map +1 -0
- package/dist/permissions/permissions.js +123 -0
- package/dist/permissions/permissions.js.map +1 -0
- package/dist/runtime/direct-host-bridge.d.ts +11 -0
- package/dist/runtime/direct-host-bridge.d.ts.map +1 -0
- package/dist/runtime/direct-host-bridge.js +299 -0
- package/dist/runtime/direct-host-bridge.js.map +1 -0
- package/dist/runtime/index.d.ts +5 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +3 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/post-message-bridge.d.ts +20 -0
- package/dist/runtime/post-message-bridge.d.ts.map +1 -0
- package/dist/runtime/post-message-bridge.js +1115 -0
- package/dist/runtime/post-message-bridge.js.map +1 -0
- package/dist/sdk.d.ts +177 -0
- package/dist/sdk.d.ts.map +1 -0
- package/dist/sdk.js +214 -0
- package/dist/sdk.js.map +1 -0
- package/dist/storage/index.d.ts +2 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +2 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/storage.d.ts +246 -0
- package/dist/storage/storage.d.ts.map +1 -0
- package/dist/storage/storage.js +358 -0
- package/dist/storage/storage.js.map +1 -0
- package/dist/types/api.d.ts +82 -0
- package/dist/types/api.d.ts.map +1 -0
- package/dist/types/api.js +2 -0
- package/dist/types/api.js.map +1 -0
- package/dist/types/appearance.d.ts +85 -0
- package/dist/types/appearance.d.ts.map +1 -0
- package/dist/types/appearance.js +48 -0
- package/dist/types/appearance.js.map +1 -0
- package/dist/types/config.d.ts +49 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +2 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/context.d.ts +41 -0
- package/dist/types/context.d.ts.map +1 -0
- package/dist/types/context.js +2 -0
- package/dist/types/context.js.map +1 -0
- package/dist/types/file.d.ts +48 -0
- package/dist/types/file.d.ts.map +1 -0
- package/dist/types/file.js +2 -0
- package/dist/types/file.js.map +1 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +12 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/organization.d.ts +56 -0
- package/dist/types/organization.d.ts.map +1 -0
- package/dist/types/organization.js +2 -0
- package/dist/types/organization.js.map +1 -0
- package/dist/types/pagination.d.ts +55 -0
- package/dist/types/pagination.d.ts.map +1 -0
- package/dist/types/pagination.js +13 -0
- package/dist/types/pagination.js.map +1 -0
- package/dist/types/permissions.d.ts +26 -0
- package/dist/types/permissions.d.ts.map +1 -0
- package/dist/types/permissions.js +2 -0
- package/dist/types/permissions.js.map +1 -0
- package/dist/types/space.d.ts +43 -0
- package/dist/types/space.d.ts.map +1 -0
- package/dist/types/space.js +2 -0
- package/dist/types/space.js.map +1 -0
- package/dist/types/tenant.d.ts +31 -0
- package/dist/types/tenant.d.ts.map +1 -0
- package/dist/types/tenant.js +2 -0
- package/dist/types/tenant.js.map +1 -0
- package/dist/types/user.d.ts +48 -0
- package/dist/types/user.d.ts.map +1 -0
- package/dist/types/user.js +2 -0
- package/dist/types/user.js.map +1 -0
- package/dist/utils/date.d.ts +9 -0
- package/dist/utils/date.d.ts.map +1 -0
- package/dist/utils/date.js +11 -0
- package/dist/utils/date.js.map +1 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +5 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/number.d.ts +11 -0
- package/dist/utils/number.d.ts.map +1 -0
- package/dist/utils/number.js +13 -0
- package/dist/utils/number.js.map +1 -0
- package/dist/utils/object.d.ts +9 -0
- package/dist/utils/object.d.ts.map +1 -0
- package/dist/utils/object.js +11 -0
- package/dist/utils/object.js.map +1 -0
- package/dist/utils/string.d.ts +9 -0
- package/dist/utils/string.d.ts.map +1 -0
- package/dist/utils/string.js +11 -0
- package/dist/utils/string.js.map +1 -0
- package/dist/validators/index.d.ts +8 -0
- package/dist/validators/index.d.ts.map +1 -0
- package/dist/validators/index.js +8 -0
- package/dist/validators/index.js.map +1 -0
- package/package.json +156 -0
|
@@ -0,0 +1,1115 @@
|
|
|
1
|
+
import { MiaixzSdkError } from "../api/errors.js";
|
|
2
|
+
import { MIAIXZ_MODULE_PROTOCOL_VERSION, parseMiaixzModuleManifest, } from "../contracts/index.js";
|
|
3
|
+
import { isMiaixzModulePermission } from "../contracts/permission.js";
|
|
4
|
+
import { translateMiaixzDefaultMessage } from "../i18n/default-translator.js";
|
|
5
|
+
const moduleIdentifierPattern = /^[a-z][a-z0-9-]{1,63}$/;
|
|
6
|
+
const semanticVersionPattern = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*))?$/;
|
|
7
|
+
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
8
|
+
const envelopeKeys = new Set([
|
|
9
|
+
"channel",
|
|
10
|
+
"protocolVersion",
|
|
11
|
+
"messageId",
|
|
12
|
+
"moduleId",
|
|
13
|
+
"kind",
|
|
14
|
+
"method",
|
|
15
|
+
"payload",
|
|
16
|
+
"error",
|
|
17
|
+
]);
|
|
18
|
+
const bridgeKinds = new Set(["request", "response", "event", "cancel"]);
|
|
19
|
+
const allowedMethods = new Set([
|
|
20
|
+
"bridge.handshake",
|
|
21
|
+
"bridge.dispose",
|
|
22
|
+
"context.get",
|
|
23
|
+
"events.emit",
|
|
24
|
+
"events.message",
|
|
25
|
+
"events.subscribe",
|
|
26
|
+
"events.unsubscribe",
|
|
27
|
+
"i18n.register",
|
|
28
|
+
"navigation.navigate",
|
|
29
|
+
"permissions.has",
|
|
30
|
+
]);
|
|
31
|
+
const contextKeys = new Set([
|
|
32
|
+
"departmentId",
|
|
33
|
+
"locale",
|
|
34
|
+
"organizationId",
|
|
35
|
+
"spaceId",
|
|
36
|
+
"tenantId",
|
|
37
|
+
"timezone",
|
|
38
|
+
"traceId",
|
|
39
|
+
"userId",
|
|
40
|
+
]);
|
|
41
|
+
const errorMessageKeys = Object.freeze({
|
|
42
|
+
ABORTED: "sdk.error.aborted",
|
|
43
|
+
API_BASE_URL_INVALID: "sdk.error.api.baseUrlInvalid",
|
|
44
|
+
API_CRYPTO_UNAVAILABLE: "sdk.error.api.cryptoUnavailable",
|
|
45
|
+
API_ENVELOPE_INVALID: "sdk.error.api.envelopeInvalid",
|
|
46
|
+
API_ENVELOPE_MODE_INVALID: "sdk.error.api.envelopeModeInvalid",
|
|
47
|
+
API_FETCH_UNAVAILABLE: "sdk.error.api.fetchUnavailable",
|
|
48
|
+
API_REQUEST_ORIGIN_INVALID: "sdk.error.api.requestOriginInvalid",
|
|
49
|
+
API_RESPONSE_INVALID: "sdk.error.api.responseInvalid",
|
|
50
|
+
API_RETRY_INVALID: "sdk.error.api.retryInvalid",
|
|
51
|
+
API_TIMEOUT_INVALID: "sdk.error.api.timeoutInvalid",
|
|
52
|
+
APPEARANCE_COLOR_INVALID: "sdk.error.appearance.colorInvalid",
|
|
53
|
+
APPEARANCE_CONTRAST_INVALID: "sdk.error.appearance.contrastInvalid",
|
|
54
|
+
AUTH_MODE_MISMATCH: "sdk.error.auth.modeMismatch",
|
|
55
|
+
AUTH_PERSISTENCE_ACKNOWLEDGEMENT_REQUIRED: "sdk.error.auth.persistenceAcknowledgementRequired",
|
|
56
|
+
AUTH_REFRESH_FAILED: "sdk.error.auth.refreshFailed",
|
|
57
|
+
AUTH_SESSION_INVALID: "sdk.error.auth.sessionInvalid",
|
|
58
|
+
BRIDGE_CAPABILITY_UNAVAILABLE: "sdk.error.bridge.capabilityUnavailable",
|
|
59
|
+
BRIDGE_CRYPTO_UNAVAILABLE: "sdk.error.bridge.cryptoUnavailable",
|
|
60
|
+
BRIDGE_DISPOSED: "sdk.error.bridge.disposed",
|
|
61
|
+
BRIDGE_MESSAGE_INVALID: "sdk.error.bridge.messageInvalid",
|
|
62
|
+
BRIDGE_NAVIGATION_STATE_INVALID: "sdk.error.bridge.navigationStateInvalid",
|
|
63
|
+
BRIDGE_NOT_READY: "sdk.error.bridge.notReady",
|
|
64
|
+
BRIDGE_ORIGIN_INVALID: "sdk.error.bridge.originInvalid",
|
|
65
|
+
BRIDGE_TIMEOUT: "sdk.error.bridge.timeout",
|
|
66
|
+
MODULE_HOST_INCOMPATIBLE: "sdk.error.module.hostIncompatible",
|
|
67
|
+
MODULE_MANIFEST_INVALID: "sdk.error.module.manifestInvalid",
|
|
68
|
+
CONFIG_FETCH_FAILED: "sdk.error.config.fetchFailed",
|
|
69
|
+
CONFIG_FETCH_UNAVAILABLE: "sdk.error.config.fetchUnavailable",
|
|
70
|
+
CONFIG_INVALID: "sdk.error.config.invalid",
|
|
71
|
+
CONTEXT_INVALID: "sdk.error.context.invalid",
|
|
72
|
+
EVENT_CHANNEL_INVALID: "sdk.error.event.channelInvalid",
|
|
73
|
+
EVENT_CRYPTO_UNAVAILABLE: "sdk.error.event.cryptoUnavailable",
|
|
74
|
+
EVENT_PAYLOAD_NOT_CLONEABLE: "sdk.error.event.payloadNotCloneable",
|
|
75
|
+
EVENT_VALIDATOR_MISSING: "sdk.error.event.validatorMissing",
|
|
76
|
+
FILE_DOWNLOAD_FAILED: "sdk.error.file.downloadFailed",
|
|
77
|
+
I18N_LOAD_FAILED: "sdk.error.i18n.loadFailed",
|
|
78
|
+
I18N_LOCALE_INVALID: "sdk.error.i18n.localeInvalid",
|
|
79
|
+
I18N_MESSAGES_INVALID: "sdk.error.i18n.messagesInvalid",
|
|
80
|
+
I18N_NAMESPACE_INVALID: "sdk.error.i18n.namespaceInvalid",
|
|
81
|
+
NETWORK_ERROR: "sdk.error.network",
|
|
82
|
+
PERMISSIONS_INVALID: "sdk.error.permissions.invalid",
|
|
83
|
+
SERVICE_ENDPOINT_MISSING: "sdk.error.config.serviceMissing",
|
|
84
|
+
STORAGE_MIGRATION_CHAIN_INVALID: "sdk.error.storage.migrationChainInvalid",
|
|
85
|
+
STORAGE_SCOPE_INVALID: "sdk.error.storage.scopeInvalid",
|
|
86
|
+
TIMEOUT: "sdk.error.timeout",
|
|
87
|
+
});
|
|
88
|
+
/**
|
|
89
|
+
* Creates a registered Bridge error without caller-owned diagnostic values.
|
|
90
|
+
*
|
|
91
|
+
* @param code - Stable registered Bridge error code.
|
|
92
|
+
* @returns Localized SDK error.
|
|
93
|
+
*/
|
|
94
|
+
function bridgeError(code) {
|
|
95
|
+
return new MiaixzSdkError(translateMiaixzDefaultMessage(errorMessageKeys[code] ?? code), {
|
|
96
|
+
code,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Determines whether a value is a plain record.
|
|
101
|
+
*
|
|
102
|
+
* @param value - Runtime value to inspect.
|
|
103
|
+
* @returns Whether the value is a record without a custom prototype.
|
|
104
|
+
*/
|
|
105
|
+
function isPlainRecord(value) {
|
|
106
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
107
|
+
return false;
|
|
108
|
+
const prototype = Object.getPrototypeOf(value);
|
|
109
|
+
return prototype === Object.prototype || prototype === null;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Determines whether a record contains exactly allowed field names.
|
|
113
|
+
*
|
|
114
|
+
* @param value - Record whose enumerable keys should be checked.
|
|
115
|
+
* @param allowed - Complete allowed key set.
|
|
116
|
+
* @returns Whether every key is allowed.
|
|
117
|
+
*/
|
|
118
|
+
function hasOnlyKeys(value, allowed) {
|
|
119
|
+
return Object.keys(value).every((key) => allowed.has(key));
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Resolves the current Realm Window required by postMessage Bridge factories.
|
|
123
|
+
*
|
|
124
|
+
* @returns Browser Window capable of registering message listeners.
|
|
125
|
+
* @throws MiaixzSdkError When the current Realm lacks required Window capabilities.
|
|
126
|
+
*/
|
|
127
|
+
function getRuntimeWindow() {
|
|
128
|
+
const candidate = globalThis.window;
|
|
129
|
+
if (candidate === undefined ||
|
|
130
|
+
typeof candidate.addEventListener !== "function" ||
|
|
131
|
+
typeof candidate.removeEventListener !== "function") {
|
|
132
|
+
throw bridgeError("BRIDGE_CAPABILITY_UNAVAILABLE");
|
|
133
|
+
}
|
|
134
|
+
return candidate;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Validates a Window used as the single postMessage target.
|
|
138
|
+
*
|
|
139
|
+
* @param targetWindow - Window candidate to validate.
|
|
140
|
+
* @returns Valid target Window.
|
|
141
|
+
* @throws MiaixzSdkError When postMessage is unavailable.
|
|
142
|
+
*/
|
|
143
|
+
function parseTargetWindow(targetWindow) {
|
|
144
|
+
if (targetWindow === null || typeof targetWindow?.postMessage !== "function") {
|
|
145
|
+
throw bridgeError("BRIDGE_CAPABILITY_UNAVAILABLE");
|
|
146
|
+
}
|
|
147
|
+
return targetWindow;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Validates one exact HTTP or HTTPS origin string.
|
|
151
|
+
*
|
|
152
|
+
* @param value - Origin candidate.
|
|
153
|
+
* @returns Canonical origin identical to the supplied value.
|
|
154
|
+
* @throws MiaixzSdkError When the value is not one exact origin.
|
|
155
|
+
*/
|
|
156
|
+
function parseTargetOrigin(value) {
|
|
157
|
+
if (typeof value !== "string" || value === "*" || value === "null") {
|
|
158
|
+
throw bridgeError("BRIDGE_ORIGIN_INVALID");
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
const parsed = new URL(value);
|
|
162
|
+
if ((parsed.protocol !== "https:" && parsed.protocol !== "http:") ||
|
|
163
|
+
parsed.origin !== value ||
|
|
164
|
+
parsed.username !== "" ||
|
|
165
|
+
parsed.password !== "" ||
|
|
166
|
+
parsed.pathname !== "/" ||
|
|
167
|
+
parsed.search !== "" ||
|
|
168
|
+
parsed.hash !== "") {
|
|
169
|
+
throw bridgeError("BRIDGE_ORIGIN_INVALID");
|
|
170
|
+
}
|
|
171
|
+
return parsed.origin;
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
if (error instanceof MiaixzSdkError)
|
|
175
|
+
throw error;
|
|
176
|
+
throw bridgeError("BRIDGE_ORIGIN_INVALID");
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Validates one Bridge request timeout.
|
|
181
|
+
*
|
|
182
|
+
* @param timeoutMs - Optional timeout override.
|
|
183
|
+
* @returns Valid timeout, defaulting to ten seconds.
|
|
184
|
+
* @throws MiaixzSdkError When the timeout is outside the frozen range.
|
|
185
|
+
*/
|
|
186
|
+
function parseTimeout(timeoutMs) {
|
|
187
|
+
const value = timeoutMs ?? 10_000;
|
|
188
|
+
if (!Number.isInteger(value) || value < 1_000 || value > 60_000) {
|
|
189
|
+
throw bridgeError("BRIDGE_MESSAGE_INVALID");
|
|
190
|
+
}
|
|
191
|
+
return value;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Produces one secure Bridge message or subscription identifier.
|
|
195
|
+
*
|
|
196
|
+
* @returns Cryptographically secure UUID.
|
|
197
|
+
* @throws MiaixzSdkError When secure UUID generation is unavailable.
|
|
198
|
+
*/
|
|
199
|
+
function createMessageId() {
|
|
200
|
+
if (typeof globalThis.crypto?.randomUUID !== "function") {
|
|
201
|
+
throw bridgeError("BRIDGE_CRYPTO_UNAVAILABLE");
|
|
202
|
+
}
|
|
203
|
+
return globalThis.crypto.randomUUID();
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Validates the fixed Bridge envelope without interpreting method payloads.
|
|
207
|
+
*
|
|
208
|
+
* @param value - Incoming message data.
|
|
209
|
+
* @returns Frozen typed envelope, or undefined for invalid data.
|
|
210
|
+
*/
|
|
211
|
+
function parseEnvelope(value) {
|
|
212
|
+
if (!isPlainRecord(value) ||
|
|
213
|
+
!hasOnlyKeys(value, envelopeKeys) ||
|
|
214
|
+
value.channel !== "miaixz.bridge" ||
|
|
215
|
+
value.protocolVersion !== MIAIXZ_MODULE_PROTOCOL_VERSION ||
|
|
216
|
+
typeof value.messageId !== "string" ||
|
|
217
|
+
!uuidPattern.test(value.messageId) ||
|
|
218
|
+
typeof value.moduleId !== "string" ||
|
|
219
|
+
!moduleIdentifierPattern.test(value.moduleId) ||
|
|
220
|
+
typeof value.kind !== "string" ||
|
|
221
|
+
!bridgeKinds.has(value.kind) ||
|
|
222
|
+
typeof value.method !== "string" ||
|
|
223
|
+
!allowedMethods.has(value.method)) {
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
if (value.error !== undefined) {
|
|
227
|
+
if (value.kind !== "response" ||
|
|
228
|
+
!isPlainRecord(value.error) ||
|
|
229
|
+
!hasOnlyKeys(value.error, new Set(["code", "messageKey", "details"])) ||
|
|
230
|
+
typeof value.error.code !== "string" ||
|
|
231
|
+
typeof value.error.messageKey !== "string") {
|
|
232
|
+
return undefined;
|
|
233
|
+
}
|
|
234
|
+
if (value.payload !== undefined || value.error.details !== undefined)
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
return Object.freeze({ ...value });
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Creates one immutable outgoing Bridge envelope.
|
|
241
|
+
*
|
|
242
|
+
* @param moduleId - Module owning the connection.
|
|
243
|
+
* @param kind - Message state-machine branch.
|
|
244
|
+
* @param method - Allow-listed Bridge method.
|
|
245
|
+
* @param messageId - Existing request identifier or a new event identifier.
|
|
246
|
+
* @param payload - Optional method payload.
|
|
247
|
+
* @param error - Optional safe remote error.
|
|
248
|
+
* @returns Frozen Bridge envelope.
|
|
249
|
+
*/
|
|
250
|
+
function createEnvelope(moduleId, kind, method, messageId, payload, error) {
|
|
251
|
+
return Object.freeze({
|
|
252
|
+
channel: "miaixz.bridge",
|
|
253
|
+
protocolVersion: MIAIXZ_MODULE_PROTOCOL_VERSION,
|
|
254
|
+
messageId,
|
|
255
|
+
moduleId,
|
|
256
|
+
kind,
|
|
257
|
+
method,
|
|
258
|
+
...(payload === undefined ? {} : { payload }),
|
|
259
|
+
...(error === undefined ? {} : { error }),
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Maps an Adapter failure to the only safe cross-origin error fields.
|
|
264
|
+
*
|
|
265
|
+
* @param error - Local Adapter failure.
|
|
266
|
+
* @returns Frozen registered error code and message key.
|
|
267
|
+
*/
|
|
268
|
+
function serializeError(error) {
|
|
269
|
+
const candidate = error instanceof MiaixzSdkError ? error.code : undefined;
|
|
270
|
+
const code = candidate !== undefined && candidate in errorMessageKeys ? candidate : "BRIDGE_MESSAGE_INVALID";
|
|
271
|
+
return Object.freeze({
|
|
272
|
+
code,
|
|
273
|
+
messageKey: errorMessageKeys[code] ?? "sdk.error.bridge.messageInvalid",
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Reconstructs a safe local SDK error from one remote response.
|
|
278
|
+
*
|
|
279
|
+
* @param error - Remote error fields.
|
|
280
|
+
* @returns Localized SDK error without remote cause or stack.
|
|
281
|
+
*/
|
|
282
|
+
function deserializeError(error) {
|
|
283
|
+
const code = error.code;
|
|
284
|
+
const messageKey = errorMessageKeys[code];
|
|
285
|
+
if (messageKey === undefined || error.messageKey !== messageKey) {
|
|
286
|
+
return bridgeError("BRIDGE_MESSAGE_INVALID");
|
|
287
|
+
}
|
|
288
|
+
return new MiaixzSdkError(translateMiaixzDefaultMessage(messageKey), { code });
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Removes expired identifiers and enforces the frozen cache capacity.
|
|
292
|
+
*
|
|
293
|
+
* @param cache - Incoming message cache to compact.
|
|
294
|
+
* @param now - Current epoch time.
|
|
295
|
+
*/
|
|
296
|
+
function compactCache(cache, now = Date.now()) {
|
|
297
|
+
for (const [messageId, entry] of cache) {
|
|
298
|
+
if (now - entry.timestamp > 300_000)
|
|
299
|
+
cache.delete(messageId);
|
|
300
|
+
}
|
|
301
|
+
while (cache.size > 1_000) {
|
|
302
|
+
const oldest = cache.keys().next().value;
|
|
303
|
+
if (oldest === undefined)
|
|
304
|
+
break;
|
|
305
|
+
cache.delete(oldest);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Validates and clones a same-site navigation request.
|
|
310
|
+
*
|
|
311
|
+
* @param value - Request payload to validate.
|
|
312
|
+
* @returns Frozen navigation request.
|
|
313
|
+
* @throws MiaixzSdkError When the request or state is invalid.
|
|
314
|
+
*/
|
|
315
|
+
function parseNavigation(value) {
|
|
316
|
+
if (!isPlainRecord(value) ||
|
|
317
|
+
!hasOnlyKeys(value, new Set(["path", "replace", "state"])) ||
|
|
318
|
+
typeof value.path !== "string" ||
|
|
319
|
+
!value.path.startsWith("/") ||
|
|
320
|
+
value.path.startsWith("//") ||
|
|
321
|
+
value.path.includes("\\") ||
|
|
322
|
+
(value.replace !== undefined && typeof value.replace !== "boolean")) {
|
|
323
|
+
throw bridgeError("BRIDGE_MESSAGE_INVALID");
|
|
324
|
+
}
|
|
325
|
+
let state;
|
|
326
|
+
if (value.state !== undefined) {
|
|
327
|
+
try {
|
|
328
|
+
state = structuredClone(value.state);
|
|
329
|
+
}
|
|
330
|
+
catch {
|
|
331
|
+
throw bridgeError("BRIDGE_NAVIGATION_STATE_INVALID");
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return Object.freeze({
|
|
335
|
+
path: value.path,
|
|
336
|
+
...(value.replace === undefined ? {} : { replace: value.replace }),
|
|
337
|
+
...(value.state === undefined ? {} : { state }),
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Validates one permission list sent through the Bridge.
|
|
342
|
+
*
|
|
343
|
+
* @param value - Permission payload field.
|
|
344
|
+
* @returns Frozen permission copy.
|
|
345
|
+
* @throws MiaixzSdkError When a permission is malformed.
|
|
346
|
+
*/
|
|
347
|
+
function parsePermissions(value) {
|
|
348
|
+
if (!Array.isArray(value) || !value.every(isMiaixzModulePermission)) {
|
|
349
|
+
throw bridgeError("BRIDGE_MESSAGE_INVALID");
|
|
350
|
+
}
|
|
351
|
+
return Object.freeze([...value]);
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Validates and freezes a non-sensitive runtime Context payload.
|
|
355
|
+
*
|
|
356
|
+
* @param value - Context payload to validate.
|
|
357
|
+
* @returns Frozen Context containing only registered string fields.
|
|
358
|
+
* @throws MiaixzSdkError When the Context contains unknown or non-string fields.
|
|
359
|
+
*/
|
|
360
|
+
function parseRuntimeContext(value) {
|
|
361
|
+
if (!isPlainRecord(value) ||
|
|
362
|
+
!hasOnlyKeys(value, contextKeys) ||
|
|
363
|
+
Object.values(value).some((entry) => entry !== undefined && typeof entry !== "string")) {
|
|
364
|
+
throw bridgeError("BRIDGE_MESSAGE_INVALID");
|
|
365
|
+
}
|
|
366
|
+
return Object.freeze(Object.fromEntries(Object.entries(value).filter((entry) => entry[1] !== undefined)));
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Validates and defensively freezes a project message catalog.
|
|
370
|
+
*
|
|
371
|
+
* @param namespace - Module namespace that must own every message key.
|
|
372
|
+
* @param value - Catalog payload to validate.
|
|
373
|
+
* @returns Frozen locale and message map.
|
|
374
|
+
* @throws MiaixzSdkError When a locale or message collection is invalid.
|
|
375
|
+
*/
|
|
376
|
+
function parseMessageCatalog(namespace, value) {
|
|
377
|
+
if (!isPlainRecord(value))
|
|
378
|
+
throw bridgeError("BRIDGE_MESSAGE_INVALID");
|
|
379
|
+
const catalog = {};
|
|
380
|
+
for (const [locale, messages] of Object.entries(value)) {
|
|
381
|
+
try {
|
|
382
|
+
if (Intl.getCanonicalLocales(locale)[0] !== locale)
|
|
383
|
+
throw new Error("non-canonical locale");
|
|
384
|
+
}
|
|
385
|
+
catch {
|
|
386
|
+
throw bridgeError("BRIDGE_MESSAGE_INVALID");
|
|
387
|
+
}
|
|
388
|
+
if (!isPlainRecord(messages) ||
|
|
389
|
+
Object.entries(messages).some(([key, message]) => !key.startsWith(`${namespace}.`) || typeof message !== "string")) {
|
|
390
|
+
throw bridgeError("BRIDGE_MESSAGE_INVALID");
|
|
391
|
+
}
|
|
392
|
+
catalog[locale] = Object.freeze({ ...messages });
|
|
393
|
+
}
|
|
394
|
+
return Object.freeze(catalog);
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Validates one module-owned Bridge event name.
|
|
398
|
+
*
|
|
399
|
+
* @param moduleId - Module owning the connection.
|
|
400
|
+
* @param value - Event type candidate.
|
|
401
|
+
* @returns Valid module event type.
|
|
402
|
+
* @throws MiaixzSdkError When the event is malformed or belongs to another module.
|
|
403
|
+
*/
|
|
404
|
+
function parseEventType(moduleId, value) {
|
|
405
|
+
if (typeof value !== "string")
|
|
406
|
+
throw bridgeError("BRIDGE_MESSAGE_INVALID");
|
|
407
|
+
const separator = value.indexOf(":");
|
|
408
|
+
if (separator < 0 ||
|
|
409
|
+
value.indexOf(":", separator + 1) >= 0 ||
|
|
410
|
+
value.slice(0, separator) !== moduleId ||
|
|
411
|
+
!moduleIdentifierPattern.test(value.slice(separator + 1))) {
|
|
412
|
+
throw bridgeError("BRIDGE_MESSAGE_INVALID");
|
|
413
|
+
}
|
|
414
|
+
return value;
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Determines the sorted capabilities actually present on one Host Adapter.
|
|
418
|
+
*
|
|
419
|
+
* @param adapter - Host Adapter to inspect.
|
|
420
|
+
* @returns Sorted capability names.
|
|
421
|
+
*/
|
|
422
|
+
function getCapabilities(adapter) {
|
|
423
|
+
const capabilities = [
|
|
424
|
+
["context", adapter.getContext],
|
|
425
|
+
["events", adapter.emit && adapter.subscribe],
|
|
426
|
+
["i18n", adapter.registerMessages],
|
|
427
|
+
["navigation", adapter.navigate],
|
|
428
|
+
["permissions", adapter.hasPermissions],
|
|
429
|
+
];
|
|
430
|
+
return Object.freeze(capabilities
|
|
431
|
+
.filter((entry) => typeof entry[1] === "function")
|
|
432
|
+
.map((entry) => entry[0])
|
|
433
|
+
.sort());
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Implements the Host half of a postMessage Bridge.
|
|
437
|
+
*/
|
|
438
|
+
class PostMessageHost {
|
|
439
|
+
ready;
|
|
440
|
+
#runtimeWindow;
|
|
441
|
+
#targetWindow;
|
|
442
|
+
#targetOrigin;
|
|
443
|
+
#manifest;
|
|
444
|
+
#adapter;
|
|
445
|
+
#capabilities;
|
|
446
|
+
#incoming = new Map();
|
|
447
|
+
#subscriptions = new Map();
|
|
448
|
+
#requests = new Map();
|
|
449
|
+
#resolveReady;
|
|
450
|
+
#rejectReady;
|
|
451
|
+
#readyTimer;
|
|
452
|
+
#readyState = "pending";
|
|
453
|
+
#disposed = false;
|
|
454
|
+
/**
|
|
455
|
+
* Creates and starts one Host-side handshake listener.
|
|
456
|
+
*
|
|
457
|
+
* @param options - Validated Host construction options.
|
|
458
|
+
*/
|
|
459
|
+
constructor(options) {
|
|
460
|
+
this.#runtimeWindow = getRuntimeWindow();
|
|
461
|
+
this.#targetWindow = parseTargetWindow(options.targetWindow);
|
|
462
|
+
this.#targetOrigin = parseTargetOrigin(options.targetOrigin);
|
|
463
|
+
this.#manifest = parseMiaixzModuleManifest(options.manifest);
|
|
464
|
+
if (this.#manifest.kind !== "iframe")
|
|
465
|
+
throw bridgeError("BRIDGE_ORIGIN_INVALID");
|
|
466
|
+
let entryOrigin;
|
|
467
|
+
try {
|
|
468
|
+
entryOrigin = new URL(this.#manifest.entry).origin;
|
|
469
|
+
}
|
|
470
|
+
catch {
|
|
471
|
+
throw bridgeError("BRIDGE_ORIGIN_INVALID");
|
|
472
|
+
}
|
|
473
|
+
if (entryOrigin !== this.#targetOrigin)
|
|
474
|
+
throw bridgeError("BRIDGE_ORIGIN_INVALID");
|
|
475
|
+
if (!isPlainRecord(options.adapter))
|
|
476
|
+
throw bridgeError("BRIDGE_MESSAGE_INVALID");
|
|
477
|
+
this.#adapter = options.adapter;
|
|
478
|
+
this.#capabilities = getCapabilities(this.#adapter);
|
|
479
|
+
const timeoutMs = parseTimeout(options.timeoutMs);
|
|
480
|
+
let resolveReady;
|
|
481
|
+
let rejectReady;
|
|
482
|
+
this.ready = new Promise((resolve, reject) => {
|
|
483
|
+
resolveReady = resolve;
|
|
484
|
+
rejectReady = reject;
|
|
485
|
+
});
|
|
486
|
+
this.#resolveReady = resolveReady;
|
|
487
|
+
this.#rejectReady = rejectReady;
|
|
488
|
+
if (this.#manifest.requiredCapabilities.some((capability) => !this.#capabilities.includes(capability))) {
|
|
489
|
+
this.#readyTimer = undefined;
|
|
490
|
+
this.#rejectStartup(bridgeError("BRIDGE_CAPABILITY_UNAVAILABLE"));
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
this.#runtimeWindow.addEventListener("message", this.#onMessage);
|
|
494
|
+
this.#readyTimer = setTimeout(() => {
|
|
495
|
+
this.#rejectStartup(bridgeError("BRIDGE_TIMEOUT"));
|
|
496
|
+
this.dispose();
|
|
497
|
+
}, timeoutMs);
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Releases the Host connection and all tracked subscriptions.
|
|
501
|
+
*/
|
|
502
|
+
dispose() {
|
|
503
|
+
if (this.#disposed)
|
|
504
|
+
return;
|
|
505
|
+
this.#disposed = true;
|
|
506
|
+
if (this.#readyTimer !== undefined)
|
|
507
|
+
clearTimeout(this.#readyTimer);
|
|
508
|
+
this.#runtimeWindow.removeEventListener("message", this.#onMessage);
|
|
509
|
+
if (this.#readyState === "pending")
|
|
510
|
+
this.#rejectStartup(bridgeError("BRIDGE_DISPOSED"));
|
|
511
|
+
for (const unsubscribe of this.#subscriptions.values()) {
|
|
512
|
+
void unsubscribe().catch(() => undefined);
|
|
513
|
+
}
|
|
514
|
+
this.#subscriptions.clear();
|
|
515
|
+
this.#requests.clear();
|
|
516
|
+
this.#incoming.clear();
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Handles one Window message after exact source and origin filtering.
|
|
520
|
+
*
|
|
521
|
+
* @param event - Browser message event.
|
|
522
|
+
*/
|
|
523
|
+
#onMessage = (event) => {
|
|
524
|
+
if (this.#disposed ||
|
|
525
|
+
event.source !== this.#targetWindow ||
|
|
526
|
+
event.origin !== this.#targetOrigin)
|
|
527
|
+
return;
|
|
528
|
+
const envelope = parseEnvelope(event.data);
|
|
529
|
+
if (envelope === undefined || envelope.moduleId !== this.#manifest.id)
|
|
530
|
+
return;
|
|
531
|
+
compactCache(this.#incoming);
|
|
532
|
+
if (envelope.kind === "cancel") {
|
|
533
|
+
const request = this.#requests.get(envelope.messageId);
|
|
534
|
+
if (request !== undefined)
|
|
535
|
+
request.cancelled = true;
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
const duplicate = this.#incoming.get(envelope.messageId);
|
|
539
|
+
if (duplicate !== undefined) {
|
|
540
|
+
if (envelope.kind === "request") {
|
|
541
|
+
if (duplicate.response !== undefined)
|
|
542
|
+
this.#post(duplicate.response);
|
|
543
|
+
else if (duplicate.request !== undefined)
|
|
544
|
+
duplicate.request.replayRequested = true;
|
|
545
|
+
}
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
this.#incoming.set(envelope.messageId, { timestamp: Date.now() });
|
|
549
|
+
if (envelope.kind !== "request")
|
|
550
|
+
return;
|
|
551
|
+
if (!this.#isReady() && envelope.method !== "bridge.handshake") {
|
|
552
|
+
this.#respondWithError(envelope, bridgeError("BRIDGE_NOT_READY"));
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
const requestState = { cancelled: false, replayRequested: false };
|
|
556
|
+
this.#requests.set(envelope.messageId, requestState);
|
|
557
|
+
const cached = this.#incoming.get(envelope.messageId);
|
|
558
|
+
if (cached !== undefined)
|
|
559
|
+
cached.request = requestState;
|
|
560
|
+
void this.#handleRequest(envelope).then((payload) => this.#completeRequest(envelope, payload), (error) => this.#completeRequest(envelope, undefined, error));
|
|
561
|
+
};
|
|
562
|
+
/**
|
|
563
|
+
* Reports whether the handshake has completed successfully.
|
|
564
|
+
*
|
|
565
|
+
* @returns Whether the Host accepts non-handshake requests.
|
|
566
|
+
*/
|
|
567
|
+
#isReady() {
|
|
568
|
+
return this.#readyState === "resolved";
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Handles one validated request method.
|
|
572
|
+
*
|
|
573
|
+
* @param envelope - Valid request envelope.
|
|
574
|
+
* @returns Method response payload.
|
|
575
|
+
*/
|
|
576
|
+
async #handleRequest(envelope) {
|
|
577
|
+
switch (envelope.method) {
|
|
578
|
+
case "bridge.handshake":
|
|
579
|
+
return this.#handleHandshake(envelope.payload);
|
|
580
|
+
case "context.get":
|
|
581
|
+
if (envelope.payload !== undefined)
|
|
582
|
+
throw bridgeError("BRIDGE_MESSAGE_INVALID");
|
|
583
|
+
return parseRuntimeContext(await this.#requireCapability("getContext")());
|
|
584
|
+
case "navigation.navigate":
|
|
585
|
+
await this.#requireCapability("navigate")(parseNavigation(envelope.payload));
|
|
586
|
+
return undefined;
|
|
587
|
+
case "permissions.has": {
|
|
588
|
+
if (!isPlainRecord(envelope.payload) ||
|
|
589
|
+
!hasOnlyKeys(envelope.payload, new Set(["permissions"])))
|
|
590
|
+
throw bridgeError("BRIDGE_MESSAGE_INVALID");
|
|
591
|
+
const allowed = await this.#requireCapability("hasPermissions")(parsePermissions(envelope.payload.permissions));
|
|
592
|
+
return Object.freeze({ allowed });
|
|
593
|
+
}
|
|
594
|
+
case "i18n.register": {
|
|
595
|
+
if (!isPlainRecord(envelope.payload) ||
|
|
596
|
+
!hasOnlyKeys(envelope.payload, new Set(["namespace", "catalog"])) ||
|
|
597
|
+
envelope.payload.namespace !== this.#manifest.id)
|
|
598
|
+
throw bridgeError("BRIDGE_MESSAGE_INVALID");
|
|
599
|
+
await this.#requireCapability("registerMessages")(envelope.payload.namespace, parseMessageCatalog(envelope.payload.namespace, envelope.payload.catalog));
|
|
600
|
+
return undefined;
|
|
601
|
+
}
|
|
602
|
+
case "events.emit": {
|
|
603
|
+
if (!isPlainRecord(envelope.payload) ||
|
|
604
|
+
!hasOnlyKeys(envelope.payload, new Set(["type", "payload"])))
|
|
605
|
+
throw bridgeError("BRIDGE_MESSAGE_INVALID");
|
|
606
|
+
await this.#requireCapability("emit")(parseEventType(this.#manifest.id, envelope.payload.type), envelope.payload.payload);
|
|
607
|
+
return undefined;
|
|
608
|
+
}
|
|
609
|
+
case "events.subscribe":
|
|
610
|
+
return this.#subscribe(envelope.payload);
|
|
611
|
+
case "events.unsubscribe":
|
|
612
|
+
await this.#unsubscribe(envelope.payload);
|
|
613
|
+
return undefined;
|
|
614
|
+
case "bridge.dispose":
|
|
615
|
+
if (envelope.payload !== undefined)
|
|
616
|
+
throw bridgeError("BRIDGE_MESSAGE_INVALID");
|
|
617
|
+
return undefined;
|
|
618
|
+
default:
|
|
619
|
+
throw bridgeError("BRIDGE_MESSAGE_INVALID");
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Validates and accepts the only allowed handshake payload.
|
|
624
|
+
*
|
|
625
|
+
* @param payload - Handshake request payload.
|
|
626
|
+
* @returns Accepted handshake response payload.
|
|
627
|
+
*/
|
|
628
|
+
#handleHandshake(payload) {
|
|
629
|
+
if (this.#readyState !== "pending" ||
|
|
630
|
+
!isPlainRecord(payload) ||
|
|
631
|
+
!hasOnlyKeys(payload, new Set(["moduleId", "moduleVersion", "protocolVersion"])) ||
|
|
632
|
+
payload.moduleId !== this.#manifest.id ||
|
|
633
|
+
payload.moduleVersion !== this.#manifest.version ||
|
|
634
|
+
payload.protocolVersion !== MIAIXZ_MODULE_PROTOCOL_VERSION) {
|
|
635
|
+
throw bridgeError("BRIDGE_MESSAGE_INVALID");
|
|
636
|
+
}
|
|
637
|
+
return Object.freeze({
|
|
638
|
+
accepted: true,
|
|
639
|
+
protocolVersion: MIAIXZ_MODULE_PROTOCOL_VERSION,
|
|
640
|
+
capabilities: this.#capabilities,
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Registers one Host event subscription and returns its secure identifier.
|
|
645
|
+
*
|
|
646
|
+
* @param payload - Subscribe request payload.
|
|
647
|
+
* @returns Subscription response payload.
|
|
648
|
+
*/
|
|
649
|
+
async #subscribe(payload) {
|
|
650
|
+
if (!isPlainRecord(payload) || !hasOnlyKeys(payload, new Set(["type"])))
|
|
651
|
+
throw bridgeError("BRIDGE_MESSAGE_INVALID");
|
|
652
|
+
const type = parseEventType(this.#manifest.id, payload.type);
|
|
653
|
+
const subscriptionId = createMessageId();
|
|
654
|
+
let cancelled = false;
|
|
655
|
+
const rawUnsubscribe = await this.#requireCapability("subscribe")(type, (eventPayload) => {
|
|
656
|
+
if (cancelled || this.#disposed)
|
|
657
|
+
return;
|
|
658
|
+
this.#post(createEnvelope(this.#manifest.id, "event", "events.message", createMessageId(), {
|
|
659
|
+
subscriptionId,
|
|
660
|
+
type,
|
|
661
|
+
payload: eventPayload,
|
|
662
|
+
}));
|
|
663
|
+
});
|
|
664
|
+
if (typeof rawUnsubscribe !== "function")
|
|
665
|
+
throw bridgeError("BRIDGE_MESSAGE_INVALID");
|
|
666
|
+
const unsubscribe = async () => {
|
|
667
|
+
if (cancelled)
|
|
668
|
+
return;
|
|
669
|
+
cancelled = true;
|
|
670
|
+
this.#subscriptions.delete(subscriptionId);
|
|
671
|
+
await Promise.resolve().then(rawUnsubscribe);
|
|
672
|
+
};
|
|
673
|
+
this.#subscriptions.set(subscriptionId, unsubscribe);
|
|
674
|
+
return Object.freeze({ subscriptionId });
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Removes one previously registered Host event subscription.
|
|
678
|
+
*
|
|
679
|
+
* @param payload - Unsubscribe request payload.
|
|
680
|
+
*/
|
|
681
|
+
async #unsubscribe(payload) {
|
|
682
|
+
if (!isPlainRecord(payload) ||
|
|
683
|
+
!hasOnlyKeys(payload, new Set(["subscriptionId"])) ||
|
|
684
|
+
typeof payload.subscriptionId !== "string" ||
|
|
685
|
+
!uuidPattern.test(payload.subscriptionId))
|
|
686
|
+
throw bridgeError("BRIDGE_MESSAGE_INVALID");
|
|
687
|
+
await this.#subscriptions.get(payload.subscriptionId)?.();
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Completes one Host request, caches its response, and performs final disposal when requested.
|
|
691
|
+
*
|
|
692
|
+
* @param request - Completed request envelope.
|
|
693
|
+
* @param payload - Successful response payload.
|
|
694
|
+
* @param error - Optional request failure.
|
|
695
|
+
*/
|
|
696
|
+
#completeRequest(request, payload, error) {
|
|
697
|
+
const state = this.#requests.get(request.messageId);
|
|
698
|
+
this.#requests.delete(request.messageId);
|
|
699
|
+
if (state?.cancelled === true || this.#disposed)
|
|
700
|
+
return;
|
|
701
|
+
const response = createEnvelope(this.#manifest.id, "response", request.method, request.messageId, payload, error === undefined ? undefined : serializeError(error));
|
|
702
|
+
const cached = this.#incoming.get(request.messageId);
|
|
703
|
+
if (cached !== undefined)
|
|
704
|
+
cached.response = response;
|
|
705
|
+
this.#post(response);
|
|
706
|
+
if (state?.replayRequested === true)
|
|
707
|
+
this.#post(response);
|
|
708
|
+
if (request.method === "bridge.handshake") {
|
|
709
|
+
if (error === undefined)
|
|
710
|
+
this.#resolveStartup();
|
|
711
|
+
else {
|
|
712
|
+
this.#rejectStartup(error instanceof MiaixzSdkError ? error : bridgeError("BRIDGE_MESSAGE_INVALID"));
|
|
713
|
+
this.dispose();
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
if (request.method === "bridge.dispose" && error === undefined)
|
|
717
|
+
this.dispose();
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Sends one immediate error response for a valid request.
|
|
721
|
+
*
|
|
722
|
+
* @param request - Request being rejected.
|
|
723
|
+
* @param error - Registered SDK error.
|
|
724
|
+
*/
|
|
725
|
+
#respondWithError(request, error) {
|
|
726
|
+
this.#post(createEnvelope(this.#manifest.id, "response", request.method, request.messageId, undefined, serializeError(error)));
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* Posts one envelope to the single validated target.
|
|
730
|
+
*
|
|
731
|
+
* @param envelope - Envelope to send.
|
|
732
|
+
*/
|
|
733
|
+
#post(envelope) {
|
|
734
|
+
this.#targetWindow.postMessage(envelope, this.#targetOrigin);
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Resolves the Host startup Promise exactly once.
|
|
738
|
+
*/
|
|
739
|
+
#resolveStartup() {
|
|
740
|
+
if (this.#readyState !== "pending")
|
|
741
|
+
return;
|
|
742
|
+
this.#readyState = "resolved";
|
|
743
|
+
if (this.#readyTimer !== undefined)
|
|
744
|
+
clearTimeout(this.#readyTimer);
|
|
745
|
+
this.#resolveReady();
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Rejects the Host startup Promise exactly once.
|
|
749
|
+
*
|
|
750
|
+
* @param error - Deterministic startup failure.
|
|
751
|
+
*/
|
|
752
|
+
#rejectStartup(error) {
|
|
753
|
+
if (this.#readyState !== "pending")
|
|
754
|
+
return;
|
|
755
|
+
this.#readyState = "rejected";
|
|
756
|
+
if (this.#readyTimer !== undefined)
|
|
757
|
+
clearTimeout(this.#readyTimer);
|
|
758
|
+
this.#rejectReady(error);
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Returns one injected Adapter capability or throws the frozen missing-capability error.
|
|
762
|
+
*
|
|
763
|
+
* @typeParam Name - Adapter capability name.
|
|
764
|
+
* @param name - Capability to resolve.
|
|
765
|
+
* @returns Injected Adapter function.
|
|
766
|
+
*/
|
|
767
|
+
#requireCapability(name) {
|
|
768
|
+
const capability = this.#adapter[name];
|
|
769
|
+
if (capability === undefined)
|
|
770
|
+
throw bridgeError("BRIDGE_CAPABILITY_UNAVAILABLE");
|
|
771
|
+
return capability;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Implements the Child half of a postMessage Host Bridge.
|
|
776
|
+
*/
|
|
777
|
+
class PostMessageChildBridge {
|
|
778
|
+
/**
|
|
779
|
+
* Micro-frontend protocol version implemented by the bridge.
|
|
780
|
+
*/
|
|
781
|
+
protocolVersion = MIAIXZ_MODULE_PROTOCOL_VERSION;
|
|
782
|
+
#runtimeWindow;
|
|
783
|
+
#targetWindow;
|
|
784
|
+
#targetOrigin;
|
|
785
|
+
#moduleId;
|
|
786
|
+
#moduleVersion;
|
|
787
|
+
#timeoutMs;
|
|
788
|
+
#pending = new Map();
|
|
789
|
+
#incoming = new Map();
|
|
790
|
+
#listeners = new Map();
|
|
791
|
+
#disposed = false;
|
|
792
|
+
/**
|
|
793
|
+
* Creates one Child Bridge before its handshake starts.
|
|
794
|
+
*
|
|
795
|
+
* @param options - Validated Child connection options.
|
|
796
|
+
*/
|
|
797
|
+
constructor(options) {
|
|
798
|
+
this.#runtimeWindow = getRuntimeWindow();
|
|
799
|
+
this.#targetWindow = parseTargetWindow(options.targetWindow);
|
|
800
|
+
this.#targetOrigin = parseTargetOrigin(options.targetOrigin);
|
|
801
|
+
if (!moduleIdentifierPattern.test(options.moduleId))
|
|
802
|
+
throw bridgeError("BRIDGE_MESSAGE_INVALID");
|
|
803
|
+
if (!semanticVersionPattern.test(options.moduleVersion))
|
|
804
|
+
throw bridgeError("BRIDGE_MESSAGE_INVALID");
|
|
805
|
+
this.#moduleId = options.moduleId;
|
|
806
|
+
this.#moduleVersion = options.moduleVersion;
|
|
807
|
+
this.#timeoutMs = parseTimeout(options.timeoutMs);
|
|
808
|
+
this.#runtimeWindow.addEventListener("message", this.#onMessage);
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* Performs the fixed Child-initiated handshake.
|
|
812
|
+
*
|
|
813
|
+
* @returns Connected Child Bridge.
|
|
814
|
+
*/
|
|
815
|
+
async connect() {
|
|
816
|
+
const payload = await this.#request("bridge.handshake", {
|
|
817
|
+
moduleId: this.#moduleId,
|
|
818
|
+
moduleVersion: this.#moduleVersion,
|
|
819
|
+
protocolVersion: MIAIXZ_MODULE_PROTOCOL_VERSION,
|
|
820
|
+
});
|
|
821
|
+
if (!isPlainRecord(payload) ||
|
|
822
|
+
!hasOnlyKeys(payload, new Set(["accepted", "protocolVersion", "capabilities"])) ||
|
|
823
|
+
payload.accepted !== true ||
|
|
824
|
+
payload.protocolVersion !== MIAIXZ_MODULE_PROTOCOL_VERSION ||
|
|
825
|
+
!Array.isArray(payload.capabilities) ||
|
|
826
|
+
!payload.capabilities.every((value) => typeof value === "string")) {
|
|
827
|
+
this.dispose();
|
|
828
|
+
throw bridgeError("BRIDGE_MESSAGE_INVALID");
|
|
829
|
+
}
|
|
830
|
+
return this;
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Reads the current non-sensitive Host context.
|
|
834
|
+
*
|
|
835
|
+
* @returns Runtime context returned by the Host.
|
|
836
|
+
*/
|
|
837
|
+
async getContext() {
|
|
838
|
+
const payload = await this.#request("context.get");
|
|
839
|
+
return parseRuntimeContext(payload);
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* Requests validated same-site Host navigation.
|
|
843
|
+
*
|
|
844
|
+
* @param request - Navigation request to validate and send.
|
|
845
|
+
*/
|
|
846
|
+
async navigate(request) {
|
|
847
|
+
const payload = await this.#request("navigation.navigate", parseNavigation(request));
|
|
848
|
+
if (payload !== undefined)
|
|
849
|
+
throw bridgeError("BRIDGE_MESSAGE_INVALID");
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Checks whether all requested permissions are available.
|
|
853
|
+
*
|
|
854
|
+
* @param permissions - Non-wildcard permission list.
|
|
855
|
+
* @returns Whether the Host allows every permission.
|
|
856
|
+
*/
|
|
857
|
+
async hasPermissions(permissions) {
|
|
858
|
+
const payload = await this.#request("permissions.has", {
|
|
859
|
+
permissions: parsePermissions(permissions),
|
|
860
|
+
});
|
|
861
|
+
if (!isPlainRecord(payload) ||
|
|
862
|
+
!hasOnlyKeys(payload, new Set(["allowed"])) ||
|
|
863
|
+
typeof payload.allowed !== "boolean")
|
|
864
|
+
throw bridgeError("BRIDGE_MESSAGE_INVALID");
|
|
865
|
+
return payload.allowed;
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Registers one module-owned internationalization catalog.
|
|
869
|
+
*
|
|
870
|
+
* @param namespace - Namespace that must equal the module identifier.
|
|
871
|
+
* @param catalog - Localized project messages.
|
|
872
|
+
*/
|
|
873
|
+
async registerMessages(namespace, catalog) {
|
|
874
|
+
this.#assertActive();
|
|
875
|
+
if (namespace !== this.#moduleId)
|
|
876
|
+
throw bridgeError("BRIDGE_MESSAGE_INVALID");
|
|
877
|
+
const payload = await this.#request("i18n.register", {
|
|
878
|
+
namespace,
|
|
879
|
+
catalog: parseMessageCatalog(namespace, catalog),
|
|
880
|
+
});
|
|
881
|
+
if (payload !== undefined)
|
|
882
|
+
throw bridgeError("BRIDGE_MESSAGE_INVALID");
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* Emits one module-scoped event through the Host.
|
|
886
|
+
*
|
|
887
|
+
* @typeParam T - Event payload type.
|
|
888
|
+
* @param type - Module-owned event type.
|
|
889
|
+
* @param payload - Event payload.
|
|
890
|
+
*/
|
|
891
|
+
async emit(type, payload) {
|
|
892
|
+
const response = await this.#request("events.emit", {
|
|
893
|
+
type: parseEventType(this.#moduleId, type),
|
|
894
|
+
payload,
|
|
895
|
+
});
|
|
896
|
+
if (response !== undefined)
|
|
897
|
+
throw bridgeError("BRIDGE_MESSAGE_INVALID");
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Subscribes to one module-scoped Host event.
|
|
901
|
+
*
|
|
902
|
+
* @typeParam T - Event payload type.
|
|
903
|
+
* @param type - Module-owned event type.
|
|
904
|
+
* @param listener - Event listener.
|
|
905
|
+
* @returns Idempotent asynchronous unsubscribe function.
|
|
906
|
+
*/
|
|
907
|
+
async subscribe(type, listener) {
|
|
908
|
+
this.#assertActive();
|
|
909
|
+
const parsedType = parseEventType(this.#moduleId, type);
|
|
910
|
+
if (typeof listener !== "function")
|
|
911
|
+
throw bridgeError("BRIDGE_MESSAGE_INVALID");
|
|
912
|
+
const payload = await this.#request("events.subscribe", { type: parsedType });
|
|
913
|
+
if (!isPlainRecord(payload) ||
|
|
914
|
+
!hasOnlyKeys(payload, new Set(["subscriptionId"])) ||
|
|
915
|
+
typeof payload.subscriptionId !== "string" ||
|
|
916
|
+
!uuidPattern.test(payload.subscriptionId))
|
|
917
|
+
throw bridgeError("BRIDGE_MESSAGE_INVALID");
|
|
918
|
+
const subscriptionId = payload.subscriptionId;
|
|
919
|
+
this.#listeners.set(subscriptionId, {
|
|
920
|
+
type: parsedType,
|
|
921
|
+
listener: (eventPayload) => listener(eventPayload),
|
|
922
|
+
});
|
|
923
|
+
let cancelled = false;
|
|
924
|
+
return async () => {
|
|
925
|
+
if (cancelled)
|
|
926
|
+
return;
|
|
927
|
+
cancelled = true;
|
|
928
|
+
this.#listeners.delete(subscriptionId);
|
|
929
|
+
if (this.#disposed)
|
|
930
|
+
return;
|
|
931
|
+
const response = await this.#request("events.unsubscribe", { subscriptionId });
|
|
932
|
+
if (response !== undefined)
|
|
933
|
+
throw bridgeError("BRIDGE_MESSAGE_INVALID");
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* Notifies the Host and immediately releases local requests and listeners.
|
|
938
|
+
*/
|
|
939
|
+
dispose() {
|
|
940
|
+
if (this.#disposed)
|
|
941
|
+
return;
|
|
942
|
+
try {
|
|
943
|
+
this.#post(createEnvelope(this.#moduleId, "request", "bridge.dispose", createMessageId()));
|
|
944
|
+
}
|
|
945
|
+
catch {
|
|
946
|
+
// Best-effort remote disposal cannot delay deterministic local cleanup.
|
|
947
|
+
}
|
|
948
|
+
this.#disposed = true;
|
|
949
|
+
for (const [pendingId, pending] of this.#pending) {
|
|
950
|
+
clearTimeout(pending.timer);
|
|
951
|
+
this.#sendCancel(pendingId, pending);
|
|
952
|
+
pending.reject(bridgeError("BRIDGE_DISPOSED"));
|
|
953
|
+
}
|
|
954
|
+
this.#pending.clear();
|
|
955
|
+
this.#listeners.clear();
|
|
956
|
+
this.#incoming.clear();
|
|
957
|
+
this.#runtimeWindow.removeEventListener("message", this.#onMessage);
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Handles one exact-source Child response or event.
|
|
961
|
+
*
|
|
962
|
+
* @param event - Browser message event.
|
|
963
|
+
*/
|
|
964
|
+
#onMessage = (event) => {
|
|
965
|
+
if (this.#disposed ||
|
|
966
|
+
event.source !== this.#targetWindow ||
|
|
967
|
+
event.origin !== this.#targetOrigin)
|
|
968
|
+
return;
|
|
969
|
+
const envelope = parseEnvelope(event.data);
|
|
970
|
+
if (envelope === undefined || envelope.moduleId !== this.#moduleId)
|
|
971
|
+
return;
|
|
972
|
+
compactCache(this.#incoming);
|
|
973
|
+
if (envelope.kind === "event") {
|
|
974
|
+
if (this.#incoming.has(envelope.messageId))
|
|
975
|
+
return;
|
|
976
|
+
this.#incoming.set(envelope.messageId, { timestamp: Date.now() });
|
|
977
|
+
this.#handleEvent(envelope);
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
if (envelope.kind !== "response")
|
|
981
|
+
return;
|
|
982
|
+
const pending = this.#pending.get(envelope.messageId);
|
|
983
|
+
if (pending === undefined || pending.method !== envelope.method)
|
|
984
|
+
return;
|
|
985
|
+
if (this.#incoming.has(envelope.messageId))
|
|
986
|
+
return;
|
|
987
|
+
this.#incoming.set(envelope.messageId, { timestamp: Date.now() });
|
|
988
|
+
this.#pending.delete(envelope.messageId);
|
|
989
|
+
clearTimeout(pending.timer);
|
|
990
|
+
if (envelope.error !== undefined)
|
|
991
|
+
pending.reject(deserializeError(envelope.error));
|
|
992
|
+
else
|
|
993
|
+
pending.resolve(envelope.payload);
|
|
994
|
+
};
|
|
995
|
+
/**
|
|
996
|
+
* Delivers one validated Host subscription event.
|
|
997
|
+
*
|
|
998
|
+
* @param envelope - Event envelope to validate.
|
|
999
|
+
*/
|
|
1000
|
+
#handleEvent(envelope) {
|
|
1001
|
+
if (envelope.method !== "events.message" ||
|
|
1002
|
+
!isPlainRecord(envelope.payload) ||
|
|
1003
|
+
!hasOnlyKeys(envelope.payload, new Set(["subscriptionId", "type", "payload"])) ||
|
|
1004
|
+
typeof envelope.payload.subscriptionId !== "string" ||
|
|
1005
|
+
typeof envelope.payload.type !== "string")
|
|
1006
|
+
return;
|
|
1007
|
+
const registered = this.#listeners.get(envelope.payload.subscriptionId);
|
|
1008
|
+
if (registered === undefined || registered.type !== envelope.payload.type)
|
|
1009
|
+
return;
|
|
1010
|
+
try {
|
|
1011
|
+
registered.listener(envelope.payload.payload);
|
|
1012
|
+
}
|
|
1013
|
+
catch {
|
|
1014
|
+
// Consumer listener failures cannot corrupt the Bridge transport state.
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
/**
|
|
1018
|
+
* Sends one correlated request and applies timeout cancellation.
|
|
1019
|
+
*
|
|
1020
|
+
* @param method - Allow-listed request method.
|
|
1021
|
+
* @param payload - Optional request payload.
|
|
1022
|
+
* @returns Promise resolving to the raw response payload.
|
|
1023
|
+
*/
|
|
1024
|
+
#request(method, payload) {
|
|
1025
|
+
this.#assertActive();
|
|
1026
|
+
const messageId = createMessageId();
|
|
1027
|
+
return new Promise((resolve, reject) => {
|
|
1028
|
+
const timer = setTimeout(() => {
|
|
1029
|
+
const pending = this.#pending.get(messageId);
|
|
1030
|
+
if (pending === undefined)
|
|
1031
|
+
return;
|
|
1032
|
+
this.#pending.delete(messageId);
|
|
1033
|
+
this.#sendCancel(messageId, pending);
|
|
1034
|
+
reject(bridgeError("BRIDGE_TIMEOUT"));
|
|
1035
|
+
}, this.#timeoutMs);
|
|
1036
|
+
const pending = {
|
|
1037
|
+
method,
|
|
1038
|
+
resolve,
|
|
1039
|
+
reject,
|
|
1040
|
+
timer,
|
|
1041
|
+
cancelSent: false,
|
|
1042
|
+
};
|
|
1043
|
+
this.#pending.set(messageId, pending);
|
|
1044
|
+
try {
|
|
1045
|
+
this.#post(createEnvelope(this.#moduleId, "request", method, messageId, payload));
|
|
1046
|
+
}
|
|
1047
|
+
catch {
|
|
1048
|
+
clearTimeout(timer);
|
|
1049
|
+
this.#pending.delete(messageId);
|
|
1050
|
+
reject(bridgeError("BRIDGE_MESSAGE_INVALID"));
|
|
1051
|
+
}
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
/**
|
|
1055
|
+
* Sends at most one cancellation envelope for a pending request.
|
|
1056
|
+
*
|
|
1057
|
+
* @param messageId - Request identifier to cancel.
|
|
1058
|
+
* @param pending - Pending request state.
|
|
1059
|
+
*/
|
|
1060
|
+
#sendCancel(messageId, pending) {
|
|
1061
|
+
if (pending.cancelSent)
|
|
1062
|
+
return;
|
|
1063
|
+
pending.cancelSent = true;
|
|
1064
|
+
try {
|
|
1065
|
+
this.#post(createEnvelope(this.#moduleId, "cancel", pending.method, messageId));
|
|
1066
|
+
}
|
|
1067
|
+
catch {
|
|
1068
|
+
// Cancellation is best-effort after the local result is already deterministic.
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Posts one envelope to the single validated Host origin.
|
|
1073
|
+
*
|
|
1074
|
+
* @param envelope - Envelope to send.
|
|
1075
|
+
*/
|
|
1076
|
+
#post(envelope) {
|
|
1077
|
+
this.#targetWindow.postMessage(envelope, this.#targetOrigin);
|
|
1078
|
+
}
|
|
1079
|
+
/**
|
|
1080
|
+
* Rejects calls made after the Child Bridge has been released.
|
|
1081
|
+
*
|
|
1082
|
+
* @throws MiaixzSdkError When the Bridge is disposed.
|
|
1083
|
+
*/
|
|
1084
|
+
#assertActive() {
|
|
1085
|
+
if (this.#disposed)
|
|
1086
|
+
throw bridgeError("BRIDGE_DISPOSED");
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
/**
|
|
1090
|
+
* Creates the Host side of a validated iframe postMessage Bridge.
|
|
1091
|
+
*
|
|
1092
|
+
* @param options - Manifest, target, origin, Adapter, and timeout configuration.
|
|
1093
|
+
* @returns Host lifecycle whose ready Promise tracks the handshake.
|
|
1094
|
+
* @throws MiaixzSdkError When construction parameters or browser capabilities are invalid.
|
|
1095
|
+
* @public
|
|
1096
|
+
*/
|
|
1097
|
+
export function createMiaixzPostMessageHost(options) {
|
|
1098
|
+
return new PostMessageHost(options);
|
|
1099
|
+
}
|
|
1100
|
+
/**
|
|
1101
|
+
* Creates and handshakes the Child side of an iframe postMessage Bridge.
|
|
1102
|
+
*
|
|
1103
|
+
* @param options - Module identity, target, origin, and timeout configuration.
|
|
1104
|
+
* @returns Promise resolving to the connected Host Bridge.
|
|
1105
|
+
* @throws MiaixzSdkError When construction, handshake, or browser capabilities are invalid.
|
|
1106
|
+
* @public
|
|
1107
|
+
*/
|
|
1108
|
+
export function createMiaixzPostMessageChildBridge(options) {
|
|
1109
|
+
const bridge = new PostMessageChildBridge(options);
|
|
1110
|
+
return bridge.connect().catch((error) => {
|
|
1111
|
+
bridge.dispose();
|
|
1112
|
+
throw error;
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
//# sourceMappingURL=post-message-bridge.js.map
|