@salesforce/sf-embedding-bridge 0.0.1 → 2.2.1-rc.7
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.txt +21 -0
- package/dist/bootstrap-envelope.d.ts +25 -0
- package/dist/bootstrap-listener.d.ts +12 -0
- package/dist/bootstrap-session.d.ts +29 -0
- package/dist/embedding-info.d.ts +4 -0
- package/dist/errors.d.ts +30 -0
- package/dist/host-meta-data.d.ts +8 -0
- package/dist/index.cjs.js +332 -0
- package/dist/index.d.ts +382 -0
- package/dist/index.esm.js +313 -0
- package/package.json +40 -8
- package/README.md +0 -45
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Terms of Use
|
|
2
|
+
|
|
3
|
+
Copyright 2026 Salesforce, Inc. All rights reserved.
|
|
4
|
+
|
|
5
|
+
These Terms of Use govern the download, installation, and/or use of this software provided by Salesforce, Inc. (“Salesforce”) (the “Software”), were last updated on April 15, 2022, ** and constitute a legally binding agreement between you and Salesforce. If you do not agree to these Terms of Use, do not install or use the Software.
|
|
6
|
+
|
|
7
|
+
Salesforce grants you a worldwide, non-exclusive, no-charge, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute the Software and derivative works subject to these Terms. These Terms shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
Subject to the limited rights expressly granted hereunder, Salesforce reserves all rights, title, and interest in and to all intellectual property subsisting in the Software. No rights are granted to you hereunder other than as expressly set forth herein. Users residing in countries on the United States Office of Foreign Assets Control sanction list, or which are otherwise subject to a US export embargo, may not use the Software.
|
|
10
|
+
|
|
11
|
+
Implementation of the Software may require development work, for which you are responsible. The Software may contain bugs, errors and incompatibilities and is made available on an AS IS basis without support, updates, or service level commitments.
|
|
12
|
+
|
|
13
|
+
Salesforce reserves the right at any time to modify, suspend, or discontinue, the Software (or any part thereof) with or without notice. You agree that Salesforce shall not be liable to you or to any third party for any modification, suspension, or discontinuance.
|
|
14
|
+
|
|
15
|
+
You agree to defend Salesforce against any claim, demand, suit or proceeding made or brought against Salesforce by a third party arising out of or accruing from (a) your use of the Software, and (b) any application you develop with the Software that infringes any copyright, trademark, trade secret, trade dress, patent, or other intellectual property right of any person or defames any person or violates their rights of publicity or privacy (each a “Claim Against Salesforce”), and will indemnify Salesforce from any damages, attorney fees, and costs finally awarded against Salesforce as a result of, or for any amounts paid by Salesforce under a settlement approved by you in writing of, a Claim Against Salesforce, provided Salesforce (x) promptly gives you written notice of the Claim Against Salesforce, (y) gives you sole control of the defense and settlement of the Claim Against Salesforce (except that you may not settle any Claim Against Salesforce unless it unconditionally releases Salesforce of all liability), and (z) gives you all reasonable assistance, at your expense.
|
|
16
|
+
|
|
17
|
+
WITHOUT LIMITING THE GENERALITY OF THE FOREGOING, THE SOFTWARE IS NOT SUPPORTED AND IS PROVIDED "AS IS," WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. IN NO EVENT SHALL SALESFORCE HAVE ANY LIABILITY FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, INDIRECT, SPECIAL, INCIDENTAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES, OR DAMAGES BASED ON LOST PROFITS, DATA, OR USE, IN CONNECTION WITH THE SOFTWARE, HOWEVER CAUSED AND WHETHER IN CONTRACT, TORT, OR UNDER ANY OTHER THEORY OF LIABILITY, WHETHER OR NOT YOU HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
|
18
|
+
|
|
19
|
+
These Terms of Use shall be governed exclusively by the internal laws of the State of California, without regard to its conflicts of laws rules. Each party hereby consents to the exclusive jurisdiction of the state and federal courts located in San Francisco County, California to adjudicate any dispute arising out of or relating to these Terms of Use and the download, installation, and/or use of the Software. Except as expressly stated herein, these Terms of Use constitute the entire agreement between the parties, and supersede all prior and contemporaneous agreements, proposals, or representations, written or oral, concerning their subject matter. No modification, amendment, or waiver of any provision of these Terms of Use shall be effective unless it is by an update to these Terms of Use that Salesforce makes available, or is in writing and signed by the party against whom the modification, amendment, or waiver is to be asserted.
|
|
20
|
+
|
|
21
|
+
Data Privacy: Salesforce may collect, process, and store device, system, and other information related to your use of the Software. This information includes, but is not limited to, IP address, user metrics, and other data (“Usage Data”). Salesforce may use Usage Data for analytics, product development, and marketing purposes. You acknowledge that files generated in conjunction with the Software may contain sensitive or confidential data, and you are solely responsible for anonymizing and protecting such data.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type BootstrapEnvelope } from "@salesforce/sf-embedding-contract";
|
|
2
|
+
export type BootstrapEnvelopeValidationFailure = "INVALID_SHAPE" | "WRONG_SOURCE" | "ORIGIN_NOT_ALLOWED" | "WRONG_PORT_COUNT" | "INSTANCE_ID_MISMATCH" | "DUPLICATE_TRANSFER";
|
|
3
|
+
export type BootstrapEnvelopeValidationResult = {
|
|
4
|
+
ok: true;
|
|
5
|
+
envelope: BootstrapEnvelope;
|
|
6
|
+
port: MessagePort;
|
|
7
|
+
} | {
|
|
8
|
+
ok: false;
|
|
9
|
+
reason: BootstrapEnvelopeValidationFailure;
|
|
10
|
+
};
|
|
11
|
+
export interface BootstrapEnvelopeValidationInput {
|
|
12
|
+
data: unknown;
|
|
13
|
+
source: MessageEventSource | null;
|
|
14
|
+
origin: string;
|
|
15
|
+
ports: ReadonlyArray<MessagePort>;
|
|
16
|
+
expectedSource: MessageEventSource | null;
|
|
17
|
+
allowedOrigins: ReadonlyArray<string>;
|
|
18
|
+
expectedInstanceId: string;
|
|
19
|
+
alreadyAccepted: boolean;
|
|
20
|
+
}
|
|
21
|
+
/** Type guard: true iff `value` is shape-compatible with `BootstrapEnvelope`. */
|
|
22
|
+
export declare function isBootstrapEnvelope(value: unknown): value is BootstrapEnvelope;
|
|
23
|
+
/** Validates an inbound bootstrap envelope; never throws — failures returned as typed reasons. */
|
|
24
|
+
export declare function validateBootstrapEnvelope(input: BootstrapEnvelopeValidationInput): BootstrapEnvelopeValidationResult;
|
|
25
|
+
//# sourceMappingURL=bootstrap-envelope.d.ts.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type HostMetaData } from "@salesforce/sf-embedding-contract";
|
|
2
|
+
export interface PortCaptureResult {
|
|
3
|
+
port: MessagePort;
|
|
4
|
+
hostMetaData: HostMetaData;
|
|
5
|
+
}
|
|
6
|
+
export type BootstrapFailureReason = "MISSING_HOST_METADATA";
|
|
7
|
+
export declare class BootstrapFailureError extends Error {
|
|
8
|
+
readonly reason: BootstrapFailureReason;
|
|
9
|
+
constructor(reason: BootstrapFailureReason);
|
|
10
|
+
}
|
|
11
|
+
export declare function installBootstrapListener(): Promise<PortCaptureResult>;
|
|
12
|
+
//# sourceMappingURL=bootstrap-listener.d.ts.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { JsonRpcClient } from "@salesforce/jsonrpc";
|
|
2
|
+
import { type HostMetaData } from "@salesforce/sf-embedding-contract";
|
|
3
|
+
import { type BootstrapFailureReason } from "./bootstrap-listener";
|
|
4
|
+
export type HostInitializedPayload = Record<string, unknown>;
|
|
5
|
+
export interface SessionHandle {
|
|
6
|
+
client: JsonRpcClient;
|
|
7
|
+
hostMetaData: HostMetaData;
|
|
8
|
+
hostInitialized: HostInitializedPayload;
|
|
9
|
+
}
|
|
10
|
+
export type SessionFailureReason = BootstrapFailureReason | "ABORTED";
|
|
11
|
+
export declare class SessionFailureError extends Error {
|
|
12
|
+
readonly reason: SessionFailureReason;
|
|
13
|
+
constructor(reason: SessionFailureReason, cause?: unknown);
|
|
14
|
+
}
|
|
15
|
+
export interface BootstrapSessionOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Cancellation signal. The browser MessagePort API has no port-close event,
|
|
18
|
+
* so the consumer owns the deadline (e.g. AbortSignal.timeout(30_000)).
|
|
19
|
+
* Honored only on the first call; subsequent calls return the cached promise.
|
|
20
|
+
*/
|
|
21
|
+
signal?: AbortSignal;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Bootstraps the sf-embedding session. Single-attempt per iframe — recovery
|
|
25
|
+
* from failure requires a host-driven remount (`port1` is one-shot; a new
|
|
26
|
+
* session needs a new `MessageChannel` + `instanceId`).
|
|
27
|
+
*/
|
|
28
|
+
export declare function bootstrapSession(options?: BootstrapSessionOptions): Promise<SessionHandle>;
|
|
29
|
+
//# sourceMappingURL=bootstrap-session.d.ts.map
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type JsonRpcErrorPayload } from "@salesforce/jsonrpc";
|
|
2
|
+
/** Combined error vocabulary: standard + transport-level + sf-embedding-specific. */
|
|
3
|
+
export declare const ErrorCode: {
|
|
4
|
+
readonly CAPABILITY_NOT_ALLOWED: -32010;
|
|
5
|
+
readonly CAPABILITY_REVOKED: -32011;
|
|
6
|
+
readonly NOT_INITIALIZED: -32012;
|
|
7
|
+
readonly PROTOCOL_VERSION_MISMATCH: -32013;
|
|
8
|
+
readonly ORIGIN_MISMATCH: -32014;
|
|
9
|
+
readonly PROTOCOL_VIOLATION: -32020;
|
|
10
|
+
readonly BOOTSTRAP_DUPLICATE_TRANSFER: -32023;
|
|
11
|
+
readonly PARSE_ERROR: -32700;
|
|
12
|
+
readonly INVALID_REQUEST: -32600;
|
|
13
|
+
readonly METHOD_NOT_FOUND: -32601;
|
|
14
|
+
readonly INVALID_PARAMS: -32602;
|
|
15
|
+
readonly INTERNAL_ERROR: -32603;
|
|
16
|
+
readonly PORT_CLOSED: -32016;
|
|
17
|
+
readonly SERIALIZATION_FAILURE: -32017;
|
|
18
|
+
readonly REQUEST_TIMEOUT: -32018;
|
|
19
|
+
readonly BACKPRESSURE: -32019;
|
|
20
|
+
readonly REQUEST_CANCELLED: -32024;
|
|
21
|
+
};
|
|
22
|
+
/** Numeric type of any value in `ErrorCode`. */
|
|
23
|
+
export type ErrorCodeValue = (typeof ErrorCode)[keyof typeof ErrorCode];
|
|
24
|
+
/** Builds a JSON-RPC error payload with sf-embedding-specific retryable defaults; falls through to @salesforce/jsonrpc for codes it owns. */
|
|
25
|
+
export declare function makeJsonRpcError(code: number, options?: {
|
|
26
|
+
message?: string;
|
|
27
|
+
details?: Record<string, unknown>;
|
|
28
|
+
retryable?: boolean;
|
|
29
|
+
}): JsonRpcErrorPayload;
|
|
30
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type HostMetaData } from "@salesforce/sf-embedding-contract";
|
|
2
|
+
/**
|
|
3
|
+
* Parses the JSON-encoded `hostMetaData` query parameter from an iframe URL's
|
|
4
|
+
* search string (per ADR 0029). Returns null on any malformed input — caller
|
|
5
|
+
* fails fast rather than partially initializing.
|
|
6
|
+
*/
|
|
7
|
+
export declare function readHostMetaData(search: string): HostMetaData | null;
|
|
8
|
+
//# sourceMappingURL=host-meta-data.d.ts.map
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var jsonrpc = require('@salesforce/jsonrpc');
|
|
4
|
+
|
|
5
|
+
// Bootstrap envelope vocabulary. Spec: EMBEDDING-PROTOCOL.md §3.1, §3.1.1,
|
|
6
|
+
// MC-7/MC-8/MC-10 in §2.2.
|
|
7
|
+
/** Wire identifier for this protocol. Carried verbatim on every bootstrap envelope. */
|
|
8
|
+
const PROTOCOL_NAME = "sf-embedding";
|
|
9
|
+
/** Discriminator for the host → embedding bootstrap envelope. */
|
|
10
|
+
const BOOTSTRAP_ENVELOPE_TYPE = "sf-embedding.bootstrap";
|
|
11
|
+
/** Discriminator for the embedding → host content-free heartbeat. */
|
|
12
|
+
const HEARTBEAT_TYPE = "sf-embedding/ready";
|
|
13
|
+
/**
|
|
14
|
+
* Discriminator for the embedding → host fail-closed shutdown signal
|
|
15
|
+
* (e.g., duplicate-transfer detection). Posted via window-level postMessage,
|
|
16
|
+
* not over the JSON-RPC port.
|
|
17
|
+
*/
|
|
18
|
+
const SHUTDOWN_TYPE = "sf-embedding/shutdown";
|
|
19
|
+
|
|
20
|
+
// sf-embedding protocol-specific JSON-RPC error codes. Spec §8.2.
|
|
21
|
+
//
|
|
22
|
+
// Standard JSON-RPC codes (-32700, -32600..-32603) and transport-level codes
|
|
23
|
+
// (PORT_CLOSED, SERIALIZATION_FAILURE, REQUEST_TIMEOUT, BACKPRESSURE,
|
|
24
|
+
// REQUEST_CANCELLED) live in `@salesforce/jsonrpc`'s
|
|
25
|
+
// `StandardErrorCode` and are NOT re-exported here. Consumers that need the
|
|
26
|
+
// merged vocabulary compose them at the call site.
|
|
27
|
+
/** sf-embedding protocol-specific error codes (§8.2 numeric assignments). */
|
|
28
|
+
const SfEmbeddingErrorCode = {
|
|
29
|
+
/** Method exists at selected version but is not granted (§6.5). */
|
|
30
|
+
CAPABILITY_NOT_ALLOWED: -32010,
|
|
31
|
+
/** In-flight request gated on a capability that was just revoked (§6.6). */
|
|
32
|
+
CAPABILITY_REVOKED: -32011,
|
|
33
|
+
/** Frame arrived before the required handshake step completed (§5.2, §4.2). */
|
|
34
|
+
NOT_INITIALIZED: -32012,
|
|
35
|
+
/** Embedding's protocol version is not supported by the host (§4.2, §3.1.2). */
|
|
36
|
+
PROTOCOL_VERSION_MISMATCH: -32013,
|
|
37
|
+
/** Frame's origin does not match the bound peer origin (§3.1). */
|
|
38
|
+
ORIGIN_MISMATCH: -32014,
|
|
39
|
+
/** Catch-all — duplicate ui/select-version, duplicate ui/discover-capabilities, etc. */
|
|
40
|
+
PROTOCOL_VIOLATION: -32020,
|
|
41
|
+
/** Embedding observed a second valid bootstrap envelope and shut down (§3.1.2). */
|
|
42
|
+
BOOTSTRAP_DUPLICATE_TRANSFER: -32023,
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Codes that are non-retryable by default (§8.4). Consumers building error
|
|
46
|
+
* payloads SHOULD set `retryable: false` automatically for these.
|
|
47
|
+
*/
|
|
48
|
+
const NON_RETRYABLE_CODES = new Set([
|
|
49
|
+
SfEmbeddingErrorCode.CAPABILITY_NOT_ALLOWED,
|
|
50
|
+
SfEmbeddingErrorCode.CAPABILITY_REVOKED,
|
|
51
|
+
SfEmbeddingErrorCode.ORIGIN_MISMATCH,
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
// Host LWC reserved DOM event names and detail shapes. Spec §12.3.
|
|
55
|
+
//
|
|
56
|
+
// These are dispatched by the host LWC on its own element so application
|
|
57
|
+
// code on the page can react. The embedding never observes them.
|
|
58
|
+
/** Event names the host LWC dispatches on its own element. Reserved by the protocol. */
|
|
59
|
+
const HostLwcEvent = {
|
|
60
|
+
/** Synchronous with `ui/discover-capabilities` response; session reaches READY. */
|
|
61
|
+
READY: "sf-embedding.component.ready",
|
|
62
|
+
/** Misconfiguration, bootstrap failure, embedding-reported fatal, or protocol violation. */
|
|
63
|
+
ERROR: "sf-embedding.component.error",
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// Host-supplied iframe URL metadata (per ADR 0029). Spec §3.1, MC-8/MC-10.
|
|
67
|
+
/** URL query-parameter name carrying the JSON-encoded `HostMetaData`. */
|
|
68
|
+
const HOST_META_DATA_PARAM = "hostMetaData";
|
|
69
|
+
|
|
70
|
+
// JSON-RPC method names on the wire today.
|
|
71
|
+
/** Host announces session identity to the embedding over the port. */
|
|
72
|
+
const HOST_INITIALIZED_METHOD = "ui/notifications/host-initialized";
|
|
73
|
+
/** Embedding acknowledges receipt of `host-initialized`. Fire-and-forget. */
|
|
74
|
+
const EMBEDDING_INITIALIZED_METHOD = "ui/notifications/embedding-initialized";
|
|
75
|
+
|
|
76
|
+
// Bootstrap envelope validator for the iframe (embedding) side of the
|
|
77
|
+
// sf-embedding handshake.
|
|
78
|
+
//
|
|
79
|
+
// Wire-format types and discriminator constants live in
|
|
80
|
+
// `@salesforce/sf-embedding-contract`; consumers MUST import them from there.
|
|
81
|
+
// This module owns only the embedding-side validator
|
|
82
|
+
// (`validateBootstrapEnvelope`) and its supporting types — intentionally NOT
|
|
83
|
+
// in the contract package because the host and embedding sides have
|
|
84
|
+
// different validation rules per spec §3.1.2.
|
|
85
|
+
/** Type guard: true iff `value` is shape-compatible with `BootstrapEnvelope`. */
|
|
86
|
+
function isBootstrapEnvelope(value) {
|
|
87
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
88
|
+
return false;
|
|
89
|
+
const v = value;
|
|
90
|
+
if (v.type !== BOOTSTRAP_ENVELOPE_TYPE)
|
|
91
|
+
return false;
|
|
92
|
+
if (v.protocol !== PROTOCOL_NAME)
|
|
93
|
+
return false;
|
|
94
|
+
if (typeof v.instanceId !== "string" || v.instanceId.length === 0)
|
|
95
|
+
return false;
|
|
96
|
+
if (!Array.isArray(v.allowedOrigins))
|
|
97
|
+
return false;
|
|
98
|
+
return v.allowedOrigins.every((o) => typeof o === "string");
|
|
99
|
+
}
|
|
100
|
+
/** Validates an inbound bootstrap envelope; never throws — failures returned as typed reasons. */
|
|
101
|
+
function validateBootstrapEnvelope(input) {
|
|
102
|
+
if (!isBootstrapEnvelope(input.data))
|
|
103
|
+
return { ok: false, reason: "INVALID_SHAPE" };
|
|
104
|
+
if (input.source !== input.expectedSource)
|
|
105
|
+
return { ok: false, reason: "WRONG_SOURCE" };
|
|
106
|
+
if (!input.allowedOrigins.includes(input.origin)) {
|
|
107
|
+
return { ok: false, reason: "ORIGIN_NOT_ALLOWED" };
|
|
108
|
+
}
|
|
109
|
+
if (input.ports.length !== 1)
|
|
110
|
+
return { ok: false, reason: "WRONG_PORT_COUNT" };
|
|
111
|
+
if (input.data.instanceId !== input.expectedInstanceId) {
|
|
112
|
+
return { ok: false, reason: "INSTANCE_ID_MISMATCH" };
|
|
113
|
+
}
|
|
114
|
+
if (input.alreadyAccepted)
|
|
115
|
+
return { ok: false, reason: "DUPLICATE_TRANSFER" };
|
|
116
|
+
return { ok: true, envelope: input.data, port: input.ports[0] };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Parses the JSON-encoded `hostMetaData` query parameter from an iframe URL's
|
|
121
|
+
* search string (per ADR 0029). Returns null on any malformed input — caller
|
|
122
|
+
* fails fast rather than partially initializing.
|
|
123
|
+
*/
|
|
124
|
+
function readHostMetaData(search) {
|
|
125
|
+
let params;
|
|
126
|
+
try {
|
|
127
|
+
params = new URLSearchParams(search);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
const raw = params.get(HOST_META_DATA_PARAM);
|
|
133
|
+
if (!raw)
|
|
134
|
+
return null;
|
|
135
|
+
let parsed;
|
|
136
|
+
try {
|
|
137
|
+
parsed = JSON.parse(raw);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
143
|
+
return null;
|
|
144
|
+
const obj = parsed;
|
|
145
|
+
const instanceId = obj.instanceId;
|
|
146
|
+
const hostAppOrigin = obj.hostAppOrigin;
|
|
147
|
+
if (typeof instanceId !== "string" || instanceId.length === 0)
|
|
148
|
+
return null;
|
|
149
|
+
if (typeof hostAppOrigin !== "string" || hostAppOrigin.length === 0)
|
|
150
|
+
return null;
|
|
151
|
+
// hostAppOrigin is the targetOrigin for postMessage; reject anything that isn't
|
|
152
|
+
// a syntactically valid origin (scheme://host[:port]) so a malformed value can't
|
|
153
|
+
// silently drop the heartbeat.
|
|
154
|
+
try {
|
|
155
|
+
if (new URL(hostAppOrigin).origin !== hostAppOrigin)
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
return { instanceId, hostAppOrigin };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
class BootstrapFailureError extends Error {
|
|
165
|
+
reason;
|
|
166
|
+
constructor(reason) {
|
|
167
|
+
super(`sf-embedding bootstrap failed: ${reason}`);
|
|
168
|
+
this.reason = reason;
|
|
169
|
+
this.name = "BootstrapFailureError";
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function installBootstrapListener() {
|
|
173
|
+
const meta = readHostMetaData(window.location.search);
|
|
174
|
+
if (!meta) {
|
|
175
|
+
return Promise.reject(new BootstrapFailureError("MISSING_HOST_METADATA"));
|
|
176
|
+
}
|
|
177
|
+
return new Promise((resolve) => {
|
|
178
|
+
let accepted = false;
|
|
179
|
+
const handler = (event) => {
|
|
180
|
+
const result = validateBootstrapEnvelope({
|
|
181
|
+
data: event.data,
|
|
182
|
+
source: event.source,
|
|
183
|
+
origin: event.origin,
|
|
184
|
+
ports: event.ports,
|
|
185
|
+
expectedSource: window.parent,
|
|
186
|
+
allowedOrigins: [meta.hostAppOrigin],
|
|
187
|
+
expectedInstanceId: meta.instanceId,
|
|
188
|
+
alreadyAccepted: accepted,
|
|
189
|
+
});
|
|
190
|
+
if (!result.ok) {
|
|
191
|
+
if (result.reason === "DUPLICATE_TRANSFER") {
|
|
192
|
+
try {
|
|
193
|
+
window.parent.postMessage({ type: SHUTDOWN_TYPE, reason: "DUPLICATE_PORT_TRANSFER" }, meta.hostAppOrigin);
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
// ignore
|
|
197
|
+
}
|
|
198
|
+
window.removeEventListener("message", handler);
|
|
199
|
+
}
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
accepted = true;
|
|
203
|
+
resolve({ port: result.port, hostMetaData: meta });
|
|
204
|
+
// Listener stays registered per spec §3.1 — a second valid
|
|
205
|
+
// envelope MUST trigger the fail-closed shutdown branch above.
|
|
206
|
+
};
|
|
207
|
+
window.addEventListener("message", handler);
|
|
208
|
+
try {
|
|
209
|
+
window.parent.postMessage({ type: HEARTBEAT_TYPE, instanceId: meta.instanceId }, meta.hostAppOrigin);
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
// ignore
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Identity emitted on `ui/notifications/embedding-initialized`. */
|
|
218
|
+
const EMBEDDING_INFO = {
|
|
219
|
+
name: "@salesforce/sf-embedding-bridge",
|
|
220
|
+
version: "2.2.1-rc.7",
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
class SessionFailureError extends Error {
|
|
224
|
+
reason;
|
|
225
|
+
constructor(reason, cause) {
|
|
226
|
+
super(`sf-embedding session bootstrap failed: ${reason}`);
|
|
227
|
+
this.reason = reason;
|
|
228
|
+
this.name = "SessionFailureError";
|
|
229
|
+
if (cause !== undefined) {
|
|
230
|
+
this.cause = cause;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
class SessionClient extends jsonrpc.JsonRpcClient {
|
|
235
|
+
waitForHostInitialized() {
|
|
236
|
+
return new Promise((resolve) => {
|
|
237
|
+
this.registerNotificationHandler(HOST_INITIALIZED_METHOD, (params) => {
|
|
238
|
+
const payload = params && typeof params === "object" && !Array.isArray(params)
|
|
239
|
+
? params
|
|
240
|
+
: {};
|
|
241
|
+
resolve(payload);
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
sendInitializedNotification() {
|
|
246
|
+
const params = { embeddingInfo: EMBEDDING_INFO };
|
|
247
|
+
this.sendNotification(EMBEDDING_INITIALIZED_METHOD, params);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function rejectOnAbort(signal) {
|
|
251
|
+
if (!signal)
|
|
252
|
+
return new Promise(() => { });
|
|
253
|
+
if (signal.aborted)
|
|
254
|
+
return Promise.reject(new SessionFailureError("ABORTED"));
|
|
255
|
+
return new Promise((_, reject) => {
|
|
256
|
+
signal.addEventListener("abort", () => reject(new SessionFailureError("ABORTED")), { once: true });
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
let sessionPromise = null;
|
|
260
|
+
/**
|
|
261
|
+
* Bootstraps the sf-embedding session. Single-attempt per iframe — recovery
|
|
262
|
+
* from failure requires a host-driven remount (`port1` is one-shot; a new
|
|
263
|
+
* session needs a new `MessageChannel` + `instanceId`).
|
|
264
|
+
*/
|
|
265
|
+
function bootstrapSession(options = {}) {
|
|
266
|
+
if (sessionPromise)
|
|
267
|
+
return sessionPromise;
|
|
268
|
+
sessionPromise = installBootstrapListener()
|
|
269
|
+
.then(async (capture) => {
|
|
270
|
+
const transport = new jsonrpc.MessageChannelTransport(capture.port);
|
|
271
|
+
const client = new SessionClient(transport);
|
|
272
|
+
const hostInitialized = await Promise.race([
|
|
273
|
+
client.waitForHostInitialized(),
|
|
274
|
+
rejectOnAbort(options.signal),
|
|
275
|
+
]);
|
|
276
|
+
client.sendInitializedNotification();
|
|
277
|
+
return {
|
|
278
|
+
client,
|
|
279
|
+
hostMetaData: capture.hostMetaData,
|
|
280
|
+
hostInitialized,
|
|
281
|
+
};
|
|
282
|
+
})
|
|
283
|
+
.catch((err) => {
|
|
284
|
+
if (err instanceof SessionFailureError)
|
|
285
|
+
throw err;
|
|
286
|
+
if (err instanceof Error && "reason" in err) {
|
|
287
|
+
throw new SessionFailureError(err.reason, err);
|
|
288
|
+
}
|
|
289
|
+
throw err;
|
|
290
|
+
});
|
|
291
|
+
sessionPromise.catch(() => {
|
|
292
|
+
// no-op
|
|
293
|
+
});
|
|
294
|
+
return sessionPromise;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Bridge-side error vocabulary: composes the standard JSON-RPC + transport
|
|
298
|
+
// codes from `@salesforce/jsonrpc` with the sf-embedding-specific
|
|
299
|
+
// codes from `@salesforce/sf-embedding-contract`, and wraps `makeJsonRpcError`
|
|
300
|
+
// to inject sf-embedding-specific retryable defaults.
|
|
301
|
+
/** Combined error vocabulary: standard + transport-level + sf-embedding-specific. */
|
|
302
|
+
const ErrorCode = {
|
|
303
|
+
...jsonrpc.StandardErrorCode,
|
|
304
|
+
...SfEmbeddingErrorCode,
|
|
305
|
+
};
|
|
306
|
+
/** Builds a JSON-RPC error payload with sf-embedding-specific retryable defaults; falls through to @salesforce/jsonrpc for codes it owns. */
|
|
307
|
+
function makeJsonRpcError(code, options = {}) {
|
|
308
|
+
if (NON_RETRYABLE_CODES.has(code) && options.retryable === undefined) {
|
|
309
|
+
return jsonrpc.makeJsonRpcError(code, { ...options, retryable: false });
|
|
310
|
+
}
|
|
311
|
+
return jsonrpc.makeJsonRpcError(code, options);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
exports.BOOTSTRAP_ENVELOPE_TYPE = BOOTSTRAP_ENVELOPE_TYPE;
|
|
315
|
+
exports.BootstrapFailureError = BootstrapFailureError;
|
|
316
|
+
exports.EMBEDDING_INITIALIZED_METHOD = EMBEDDING_INITIALIZED_METHOD;
|
|
317
|
+
exports.ErrorCode = ErrorCode;
|
|
318
|
+
exports.HEARTBEAT_TYPE = HEARTBEAT_TYPE;
|
|
319
|
+
exports.HOST_INITIALIZED_METHOD = HOST_INITIALIZED_METHOD;
|
|
320
|
+
exports.HOST_META_DATA_PARAM = HOST_META_DATA_PARAM;
|
|
321
|
+
exports.HostLwcEvent = HostLwcEvent;
|
|
322
|
+
exports.NON_RETRYABLE_CODES = NON_RETRYABLE_CODES;
|
|
323
|
+
exports.PROTOCOL_NAME = PROTOCOL_NAME;
|
|
324
|
+
exports.SHUTDOWN_TYPE = SHUTDOWN_TYPE;
|
|
325
|
+
exports.SessionFailureError = SessionFailureError;
|
|
326
|
+
exports.SfEmbeddingErrorCode = SfEmbeddingErrorCode;
|
|
327
|
+
exports.bootstrapSession = bootstrapSession;
|
|
328
|
+
exports.isBootstrapEnvelope = isBootstrapEnvelope;
|
|
329
|
+
exports.makeJsonRpcError = makeJsonRpcError;
|
|
330
|
+
exports.readHostMetaData = readHostMetaData;
|
|
331
|
+
exports.validateBootstrapEnvelope = validateBootstrapEnvelope;
|
|
332
|
+
//# sourceMappingURL=index.cjs.js.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
/** Wire identifier for this protocol. Carried verbatim on every bootstrap envelope. */
|
|
2
|
+
declare const PROTOCOL_NAME: "sf-embedding";
|
|
3
|
+
/** Discriminator for the host → embedding bootstrap envelope. */
|
|
4
|
+
declare const BOOTSTRAP_ENVELOPE_TYPE: "sf-embedding.bootstrap";
|
|
5
|
+
/** Discriminator for the embedding → host content-free heartbeat. */
|
|
6
|
+
declare const HEARTBEAT_TYPE: "sf-embedding/ready";
|
|
7
|
+
/**
|
|
8
|
+
* Discriminator for the embedding → host fail-closed shutdown signal
|
|
9
|
+
* (e.g., duplicate-transfer detection). Posted via window-level postMessage,
|
|
10
|
+
* not over the JSON-RPC port.
|
|
11
|
+
*/
|
|
12
|
+
declare const SHUTDOWN_TYPE: "sf-embedding/shutdown";
|
|
13
|
+
/** Bootstrap envelope the host transfers to the embedding alongside `port1`. */
|
|
14
|
+
interface BootstrapEnvelope {
|
|
15
|
+
type: typeof BOOTSTRAP_ENVELOPE_TYPE;
|
|
16
|
+
protocol: typeof PROTOCOL_NAME;
|
|
17
|
+
/** Host-minted; byte-for-byte identical to `hostMetaData.instanceId` (MC-10). */
|
|
18
|
+
instanceId: string;
|
|
19
|
+
/** Origins the host expects its wrapper chrome to load on; embedding reverse-checks. */
|
|
20
|
+
allowedOrigins: string[];
|
|
21
|
+
}
|
|
22
|
+
/** Heartbeat the embedding posts to `window.parent` before the port is transferred. */
|
|
23
|
+
interface HeartbeatEnvelope {
|
|
24
|
+
type: typeof HEARTBEAT_TYPE;
|
|
25
|
+
/** Mirrors the URL-supplied `hostMetaData.instanceId`; binds the heartbeat to this iframe instance. */
|
|
26
|
+
instanceId: string;
|
|
27
|
+
}
|
|
28
|
+
/** Embedding-initiated fail-closed shutdown payload (sibling-race detected, etc.). */
|
|
29
|
+
interface ShutdownEnvelope {
|
|
30
|
+
type: typeof SHUTDOWN_TYPE;
|
|
31
|
+
/** Free-form; common values: `"DUPLICATE_PORT_TRANSFER"`. */
|
|
32
|
+
reason?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** sf-embedding protocol-specific error codes (§8.2 numeric assignments). */
|
|
36
|
+
declare const SfEmbeddingErrorCode: {
|
|
37
|
+
/** Method exists at selected version but is not granted (§6.5). */
|
|
38
|
+
readonly CAPABILITY_NOT_ALLOWED: -32010;
|
|
39
|
+
/** In-flight request gated on a capability that was just revoked (§6.6). */
|
|
40
|
+
readonly CAPABILITY_REVOKED: -32011;
|
|
41
|
+
/** Frame arrived before the required handshake step completed (§5.2, §4.2). */
|
|
42
|
+
readonly NOT_INITIALIZED: -32012;
|
|
43
|
+
/** Embedding's protocol version is not supported by the host (§4.2, §3.1.2). */
|
|
44
|
+
readonly PROTOCOL_VERSION_MISMATCH: -32013;
|
|
45
|
+
/** Frame's origin does not match the bound peer origin (§3.1). */
|
|
46
|
+
readonly ORIGIN_MISMATCH: -32014;
|
|
47
|
+
/** Catch-all — duplicate ui/select-version, duplicate ui/discover-capabilities, etc. */
|
|
48
|
+
readonly PROTOCOL_VIOLATION: -32020;
|
|
49
|
+
/** Embedding observed a second valid bootstrap envelope and shut down (§3.1.2). */
|
|
50
|
+
readonly BOOTSTRAP_DUPLICATE_TRANSFER: -32023;
|
|
51
|
+
};
|
|
52
|
+
/** Numeric type of any value in `SfEmbeddingErrorCode`. */
|
|
53
|
+
type SfEmbeddingErrorCodeValue = (typeof SfEmbeddingErrorCode)[keyof typeof SfEmbeddingErrorCode];
|
|
54
|
+
/**
|
|
55
|
+
* Codes that are non-retryable by default (§8.4). Consumers building error
|
|
56
|
+
* payloads SHOULD set `retryable: false` automatically for these.
|
|
57
|
+
*/
|
|
58
|
+
declare const NON_RETRYABLE_CODES: ReadonlySet<number>;
|
|
59
|
+
|
|
60
|
+
/** Event names the host LWC dispatches on its own element. Reserved by the protocol. */
|
|
61
|
+
declare const HostLwcEvent: {
|
|
62
|
+
/** Synchronous with `ui/discover-capabilities` response; session reaches READY. */
|
|
63
|
+
readonly READY: "sf-embedding.component.ready";
|
|
64
|
+
/** Misconfiguration, bootstrap failure, embedding-reported fatal, or protocol violation. */
|
|
65
|
+
readonly ERROR: "sf-embedding.component.error";
|
|
66
|
+
};
|
|
67
|
+
/** Type-level union of the reserved event names. */
|
|
68
|
+
type HostLwcEventName = (typeof HostLwcEvent)[keyof typeof HostLwcEvent];
|
|
69
|
+
/** Detail of `sf-embedding.component.ready`. */
|
|
70
|
+
interface HostReadyEventDetail {
|
|
71
|
+
instanceId: string;
|
|
72
|
+
}
|
|
73
|
+
/** Detail of `sf-embedding.component.error` (§12.3). */
|
|
74
|
+
interface HostErrorEventDetail {
|
|
75
|
+
instanceId?: string;
|
|
76
|
+
/** Failure category — drives consumer reaction. */
|
|
77
|
+
phase: "configuration" | "bootstrap" | "session";
|
|
78
|
+
/** ErrorCode constant or a configuration-error string (e.g. `"SAME_ORIGIN_SRC"`). */
|
|
79
|
+
code: string;
|
|
80
|
+
message: string;
|
|
81
|
+
/**
|
|
82
|
+
* Whether remount is plausibly fruitful (per spec §12.3).
|
|
83
|
+
* `true` — transient failures where a fresh attempt may succeed: bootstrap
|
|
84
|
+
* handshake timeout, embedding-reported runtime error.
|
|
85
|
+
* `false` — fix the underlying problem first: configuration errors
|
|
86
|
+
* (same-origin src, allow-scripts stripped, malformed src) and protocol
|
|
87
|
+
* violations (duplicate port transfer).
|
|
88
|
+
*/
|
|
89
|
+
retryable: boolean;
|
|
90
|
+
cause?: unknown;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** URL query-parameter name carrying the JSON-encoded `HostMetaData`. */
|
|
94
|
+
declare const HOST_META_DATA_PARAM: "hostMetaData";
|
|
95
|
+
/**
|
|
96
|
+
* Shape of the JSON-encoded `hostMetaData` query parameter on the iframe URL.
|
|
97
|
+
*
|
|
98
|
+
* The host MUST write this onto `iframe.src` when assigning it; the embedding
|
|
99
|
+
* MUST read it before emitting the heartbeat. `hostAppOrigin` is the trust
|
|
100
|
+
* root for the embedding's inbound bootstrap envelope (MC-8). `instanceId`
|
|
101
|
+
* binds the inbound bootstrap envelope to this iframe instance (MC-10).
|
|
102
|
+
*/
|
|
103
|
+
interface HostMetaData {
|
|
104
|
+
hostAppOrigin: string;
|
|
105
|
+
instanceId: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Host announces session identity to the embedding over the port. */
|
|
109
|
+
declare const HOST_INITIALIZED_METHOD: "ui/notifications/host-initialized";
|
|
110
|
+
/** Embedding acknowledges receipt of `host-initialized`. Fire-and-forget. */
|
|
111
|
+
declare const EMBEDDING_INITIALIZED_METHOD: "ui/notifications/embedding-initialized";
|
|
112
|
+
/** Identity of the host or embedding product. */
|
|
113
|
+
interface PeerInfo {
|
|
114
|
+
name: string;
|
|
115
|
+
version: string;
|
|
116
|
+
}
|
|
117
|
+
/** Params for `ui/notifications/host-initialized`. */
|
|
118
|
+
interface HostInitializedParams {
|
|
119
|
+
instanceId: string;
|
|
120
|
+
hostInfo: PeerInfo;
|
|
121
|
+
}
|
|
122
|
+
/** Params for `ui/notifications/embedding-initialized`. */
|
|
123
|
+
interface EmbeddingInitializedParams {
|
|
124
|
+
embeddingInfo: PeerInfo;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
type BootstrapEnvelopeValidationFailure = "INVALID_SHAPE" | "WRONG_SOURCE" | "ORIGIN_NOT_ALLOWED" | "WRONG_PORT_COUNT" | "INSTANCE_ID_MISMATCH" | "DUPLICATE_TRANSFER";
|
|
128
|
+
type BootstrapEnvelopeValidationResult = {
|
|
129
|
+
ok: true;
|
|
130
|
+
envelope: BootstrapEnvelope;
|
|
131
|
+
port: MessagePort;
|
|
132
|
+
} | {
|
|
133
|
+
ok: false;
|
|
134
|
+
reason: BootstrapEnvelopeValidationFailure;
|
|
135
|
+
};
|
|
136
|
+
interface BootstrapEnvelopeValidationInput {
|
|
137
|
+
data: unknown;
|
|
138
|
+
source: MessageEventSource | null;
|
|
139
|
+
origin: string;
|
|
140
|
+
ports: ReadonlyArray<MessagePort>;
|
|
141
|
+
expectedSource: MessageEventSource | null;
|
|
142
|
+
allowedOrigins: ReadonlyArray<string>;
|
|
143
|
+
expectedInstanceId: string;
|
|
144
|
+
alreadyAccepted: boolean;
|
|
145
|
+
}
|
|
146
|
+
/** Type guard: true iff `value` is shape-compatible with `BootstrapEnvelope`. */
|
|
147
|
+
declare function isBootstrapEnvelope(value: unknown): value is BootstrapEnvelope;
|
|
148
|
+
/** Validates an inbound bootstrap envelope; never throws — failures returned as typed reasons. */
|
|
149
|
+
declare function validateBootstrapEnvelope(input: BootstrapEnvelopeValidationInput): BootstrapEnvelopeValidationResult;
|
|
150
|
+
|
|
151
|
+
type BootstrapFailureReason = "MISSING_HOST_METADATA";
|
|
152
|
+
declare class BootstrapFailureError extends Error {
|
|
153
|
+
readonly reason: BootstrapFailureReason;
|
|
154
|
+
constructor(reason: BootstrapFailureReason);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Copyright (c) 2026, Salesforce, Inc.,
|
|
159
|
+
* All rights reserved.
|
|
160
|
+
* For full license text, see the LICENSE.txt file
|
|
161
|
+
*/
|
|
162
|
+
/**
|
|
163
|
+
* Abstract transport for a JSON-RPC 2.0 client.
|
|
164
|
+
*
|
|
165
|
+
* A `Transport` is a bidirectional, structured-message pipe. The
|
|
166
|
+
* `JsonRpcClient` uses it to send JSON-RPC messages (`post`) and subscribe
|
|
167
|
+
* to inbound ones (`onMessage`). The transport itself is agnostic to the
|
|
168
|
+
* JSON-RPC framing — it simply moves `unknown` payloads between two
|
|
169
|
+
* endpoints.
|
|
170
|
+
*
|
|
171
|
+
* Concrete transports plug into the same shape:
|
|
172
|
+
* - `WindowPostMessageTransport` (window.parent.postMessage; used by
|
|
173
|
+
* MCP Apps)
|
|
174
|
+
* - `MessageChannelTransport` (a transferred `MessagePort`; used by
|
|
175
|
+
* sf-embedding and any future surface that bootstraps with a
|
|
176
|
+
* dedicated channel)
|
|
177
|
+
*/
|
|
178
|
+
interface Transport {
|
|
179
|
+
/**
|
|
180
|
+
* Deliver a single structured message to the peer. Implementations MUST
|
|
181
|
+
* NOT throw on transient conditions (closed port, missing parent
|
|
182
|
+
* window, etc.) — drop the message silently instead, matching the
|
|
183
|
+
* prior `window.parent?.postMessage` behaviour.
|
|
184
|
+
*/
|
|
185
|
+
post(message: unknown): void;
|
|
186
|
+
/**
|
|
187
|
+
* Subscribe to inbound structured messages from the peer. The callback
|
|
188
|
+
* receives the already-unwrapped message payload (never a
|
|
189
|
+
* `MessageEvent`).
|
|
190
|
+
*
|
|
191
|
+
* @returns an unsubscribe function that removes this listener.
|
|
192
|
+
*/
|
|
193
|
+
onMessage(callback: (message: unknown) => void): () => void;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Base class for JSON-RPC 2.0 clients.
|
|
198
|
+
*
|
|
199
|
+
* This class provides core JSON-RPC client functionality for any cross-realm
|
|
200
|
+
* surface that speaks JSON-RPC 2.0 (MCP Apps, sf-embedding, future
|
|
201
|
+
* surfaces). It handles request/response correlation, message validation,
|
|
202
|
+
* and notification dispatch.
|
|
203
|
+
*
|
|
204
|
+
* The transport layer is pluggable via the `Transport` interface and is
|
|
205
|
+
* REQUIRED at construction. There is no default transport — wildcard-targeted
|
|
206
|
+
* `window.postMessage` is structurally unsafe, so callers must construct a
|
|
207
|
+
* transport with an explicit origin (`WindowPostMessageTransport`) or a
|
|
208
|
+
* port-bound transport (`MessageChannelTransport`).
|
|
209
|
+
*
|
|
210
|
+
* Subclasses should extend this class and use the protected `request()`
|
|
211
|
+
* method to send JSON-RPC requests to the peer.
|
|
212
|
+
*
|
|
213
|
+
* @example
|
|
214
|
+
* export class MCPAppsViewSDK extends JsonRpcClient implements ViewSDK {
|
|
215
|
+
* async displayAlert(options: AlertOptions): Promise<void> {
|
|
216
|
+
* await this.request("ui/message", {
|
|
217
|
+
* role: "user",
|
|
218
|
+
* content: { type: "text", text: options.message }
|
|
219
|
+
* });
|
|
220
|
+
* }
|
|
221
|
+
* }
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* // Injecting a transport (window.parent.postMessage to a known origin).
|
|
225
|
+
* class MyClient extends JsonRpcClient {
|
|
226
|
+
* constructor(targetOrigin: string) {
|
|
227
|
+
* super(new WindowPostMessageTransport(targetOrigin));
|
|
228
|
+
* }
|
|
229
|
+
* }
|
|
230
|
+
*/
|
|
231
|
+
declare class JsonRpcClient {
|
|
232
|
+
private nextRequestId;
|
|
233
|
+
private pending;
|
|
234
|
+
private notificationHandlers;
|
|
235
|
+
private readonly transport;
|
|
236
|
+
/**
|
|
237
|
+
* Construct a JSON-RPC client bound to the given transport.
|
|
238
|
+
*
|
|
239
|
+
* @param transport - the transport to use.
|
|
240
|
+
*/
|
|
241
|
+
constructor(transport: Transport);
|
|
242
|
+
/**
|
|
243
|
+
* Register a handler for a specific JSON-RPC notification method.
|
|
244
|
+
*
|
|
245
|
+
* Subclasses can register handlers to process specific notification
|
|
246
|
+
* types. When a notification with the registered method is received,
|
|
247
|
+
* the handler will be invoked with the notification params.
|
|
248
|
+
*
|
|
249
|
+
* @param method - The notification method to handle (e.g.
|
|
250
|
+
* "ui/notifications/host-context-changed")
|
|
251
|
+
* @param handler - Callback function to process the notification params
|
|
252
|
+
*
|
|
253
|
+
* @example
|
|
254
|
+
* this.registerNotificationHandler("ui/notifications/host-context-changed", (params) => {
|
|
255
|
+
* this.handleHostContextChanged(params);
|
|
256
|
+
* });
|
|
257
|
+
*/
|
|
258
|
+
protected registerNotificationHandler(method: string, handler: (params: unknown) => void): void;
|
|
259
|
+
/**
|
|
260
|
+
* Handle inbound JSON-RPC messages from the transport.
|
|
261
|
+
*
|
|
262
|
+
* Processes both responses (for requests) and notifications.
|
|
263
|
+
* Non-JSON-RPC payloads are silently ignored so that a shared transport
|
|
264
|
+
* can be used for multiple protocols without cross-talk.
|
|
265
|
+
*/
|
|
266
|
+
private onMessage;
|
|
267
|
+
/**
|
|
268
|
+
* Send a JSON-RPC request to the peer.
|
|
269
|
+
*
|
|
270
|
+
* @param method - The JSON-RPC method name
|
|
271
|
+
* @param params - The method parameters
|
|
272
|
+
* @returns Promise that resolves with the result or rejects with error
|
|
273
|
+
*
|
|
274
|
+
* @example
|
|
275
|
+
* const result = await this.request("ui/message", {
|
|
276
|
+
* role: "user",
|
|
277
|
+
* content: { type: "text", text: "Hello" }
|
|
278
|
+
* });
|
|
279
|
+
*/
|
|
280
|
+
protected request<TParams = unknown, TResult = unknown>(method: string, params: TParams): Promise<TResult>;
|
|
281
|
+
/**
|
|
282
|
+
* Send a JSON-RPC notification to the peer.
|
|
283
|
+
*
|
|
284
|
+
* Notifications are one-way messages that do not expect a response.
|
|
285
|
+
* Use notifications for:
|
|
286
|
+
* - Informing the host of state changes
|
|
287
|
+
* - Fire-and-forget operations
|
|
288
|
+
* - Events that don't require confirmation
|
|
289
|
+
*
|
|
290
|
+
* Use request() instead when you need:
|
|
291
|
+
* - A response from the host
|
|
292
|
+
* - Confirmation of success/failure
|
|
293
|
+
* - Return values from the operation
|
|
294
|
+
*
|
|
295
|
+
* @param method - The JSON-RPC method name
|
|
296
|
+
* @param params - Optional method parameters
|
|
297
|
+
*
|
|
298
|
+
* @example
|
|
299
|
+
* this.sendNotification("ui/notifications/size-changed", {
|
|
300
|
+
* width: 800,
|
|
301
|
+
* height: 600
|
|
302
|
+
* });
|
|
303
|
+
*/
|
|
304
|
+
protected sendNotification<TParams = unknown>(method: string, params?: TParams): void;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** Structure of the `data` field on a JSON-RPC error payload. */
|
|
308
|
+
interface JsonRpcErrorData {
|
|
309
|
+
retryable?: boolean;
|
|
310
|
+
details?: Record<string, unknown>;
|
|
311
|
+
}
|
|
312
|
+
/** Shape of a JSON-RPC error payload (the value under `error` on an error response). */
|
|
313
|
+
interface JsonRpcErrorPayload {
|
|
314
|
+
code: number;
|
|
315
|
+
message?: string;
|
|
316
|
+
data?: JsonRpcErrorData;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
type HostInitializedPayload = Record<string, unknown>;
|
|
320
|
+
interface SessionHandle {
|
|
321
|
+
client: JsonRpcClient;
|
|
322
|
+
hostMetaData: HostMetaData;
|
|
323
|
+
hostInitialized: HostInitializedPayload;
|
|
324
|
+
}
|
|
325
|
+
type SessionFailureReason = BootstrapFailureReason | "ABORTED";
|
|
326
|
+
declare class SessionFailureError extends Error {
|
|
327
|
+
readonly reason: SessionFailureReason;
|
|
328
|
+
constructor(reason: SessionFailureReason, cause?: unknown);
|
|
329
|
+
}
|
|
330
|
+
interface BootstrapSessionOptions {
|
|
331
|
+
/**
|
|
332
|
+
* Cancellation signal. The browser MessagePort API has no port-close event,
|
|
333
|
+
* so the consumer owns the deadline (e.g. AbortSignal.timeout(30_000)).
|
|
334
|
+
* Honored only on the first call; subsequent calls return the cached promise.
|
|
335
|
+
*/
|
|
336
|
+
signal?: AbortSignal;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Bootstraps the sf-embedding session. Single-attempt per iframe — recovery
|
|
340
|
+
* from failure requires a host-driven remount (`port1` is one-shot; a new
|
|
341
|
+
* session needs a new `MessageChannel` + `instanceId`).
|
|
342
|
+
*/
|
|
343
|
+
declare function bootstrapSession(options?: BootstrapSessionOptions): Promise<SessionHandle>;
|
|
344
|
+
|
|
345
|
+
/** Combined error vocabulary: standard + transport-level + sf-embedding-specific. */
|
|
346
|
+
declare const ErrorCode: {
|
|
347
|
+
readonly CAPABILITY_NOT_ALLOWED: -32010;
|
|
348
|
+
readonly CAPABILITY_REVOKED: -32011;
|
|
349
|
+
readonly NOT_INITIALIZED: -32012;
|
|
350
|
+
readonly PROTOCOL_VERSION_MISMATCH: -32013;
|
|
351
|
+
readonly ORIGIN_MISMATCH: -32014;
|
|
352
|
+
readonly PROTOCOL_VIOLATION: -32020;
|
|
353
|
+
readonly BOOTSTRAP_DUPLICATE_TRANSFER: -32023;
|
|
354
|
+
readonly PARSE_ERROR: -32700;
|
|
355
|
+
readonly INVALID_REQUEST: -32600;
|
|
356
|
+
readonly METHOD_NOT_FOUND: -32601;
|
|
357
|
+
readonly INVALID_PARAMS: -32602;
|
|
358
|
+
readonly INTERNAL_ERROR: -32603;
|
|
359
|
+
readonly PORT_CLOSED: -32016;
|
|
360
|
+
readonly SERIALIZATION_FAILURE: -32017;
|
|
361
|
+
readonly REQUEST_TIMEOUT: -32018;
|
|
362
|
+
readonly BACKPRESSURE: -32019;
|
|
363
|
+
readonly REQUEST_CANCELLED: -32024;
|
|
364
|
+
};
|
|
365
|
+
/** Numeric type of any value in `ErrorCode`. */
|
|
366
|
+
type ErrorCodeValue = (typeof ErrorCode)[keyof typeof ErrorCode];
|
|
367
|
+
/** Builds a JSON-RPC error payload with sf-embedding-specific retryable defaults; falls through to @salesforce/jsonrpc for codes it owns. */
|
|
368
|
+
declare function makeJsonRpcError(code: number, options?: {
|
|
369
|
+
message?: string;
|
|
370
|
+
details?: Record<string, unknown>;
|
|
371
|
+
retryable?: boolean;
|
|
372
|
+
}): JsonRpcErrorPayload;
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Parses the JSON-encoded `hostMetaData` query parameter from an iframe URL's
|
|
376
|
+
* search string (per ADR 0029). Returns null on any malformed input — caller
|
|
377
|
+
* fails fast rather than partially initializing.
|
|
378
|
+
*/
|
|
379
|
+
declare function readHostMetaData(search: string): HostMetaData | null;
|
|
380
|
+
|
|
381
|
+
export { BOOTSTRAP_ENVELOPE_TYPE, BootstrapFailureError, EMBEDDING_INITIALIZED_METHOD, ErrorCode, HEARTBEAT_TYPE, HOST_INITIALIZED_METHOD, HOST_META_DATA_PARAM, HostLwcEvent, NON_RETRYABLE_CODES, PROTOCOL_NAME, SHUTDOWN_TYPE, SessionFailureError, SfEmbeddingErrorCode, bootstrapSession, isBootstrapEnvelope, makeJsonRpcError, readHostMetaData, validateBootstrapEnvelope };
|
|
382
|
+
export type { BootstrapEnvelope, BootstrapEnvelopeValidationFailure, BootstrapEnvelopeValidationInput, BootstrapEnvelopeValidationResult, BootstrapFailureReason, BootstrapSessionOptions, EmbeddingInitializedParams, ErrorCodeValue, HeartbeatEnvelope, HostErrorEventDetail, HostInitializedParams, HostInitializedPayload, HostLwcEventName, HostMetaData, HostReadyEventDetail, PeerInfo, SessionFailureReason, SessionHandle, SfEmbeddingErrorCodeValue, ShutdownEnvelope };
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { MessageChannelTransport, JsonRpcClient, StandardErrorCode, makeJsonRpcError as makeJsonRpcError$1 } from '@salesforce/jsonrpc';
|
|
2
|
+
|
|
3
|
+
// Bootstrap envelope vocabulary. Spec: EMBEDDING-PROTOCOL.md §3.1, §3.1.1,
|
|
4
|
+
// MC-7/MC-8/MC-10 in §2.2.
|
|
5
|
+
/** Wire identifier for this protocol. Carried verbatim on every bootstrap envelope. */
|
|
6
|
+
const PROTOCOL_NAME = "sf-embedding";
|
|
7
|
+
/** Discriminator for the host → embedding bootstrap envelope. */
|
|
8
|
+
const BOOTSTRAP_ENVELOPE_TYPE = "sf-embedding.bootstrap";
|
|
9
|
+
/** Discriminator for the embedding → host content-free heartbeat. */
|
|
10
|
+
const HEARTBEAT_TYPE = "sf-embedding/ready";
|
|
11
|
+
/**
|
|
12
|
+
* Discriminator for the embedding → host fail-closed shutdown signal
|
|
13
|
+
* (e.g., duplicate-transfer detection). Posted via window-level postMessage,
|
|
14
|
+
* not over the JSON-RPC port.
|
|
15
|
+
*/
|
|
16
|
+
const SHUTDOWN_TYPE = "sf-embedding/shutdown";
|
|
17
|
+
|
|
18
|
+
// sf-embedding protocol-specific JSON-RPC error codes. Spec §8.2.
|
|
19
|
+
//
|
|
20
|
+
// Standard JSON-RPC codes (-32700, -32600..-32603) and transport-level codes
|
|
21
|
+
// (PORT_CLOSED, SERIALIZATION_FAILURE, REQUEST_TIMEOUT, BACKPRESSURE,
|
|
22
|
+
// REQUEST_CANCELLED) live in `@salesforce/jsonrpc`'s
|
|
23
|
+
// `StandardErrorCode` and are NOT re-exported here. Consumers that need the
|
|
24
|
+
// merged vocabulary compose them at the call site.
|
|
25
|
+
/** sf-embedding protocol-specific error codes (§8.2 numeric assignments). */
|
|
26
|
+
const SfEmbeddingErrorCode = {
|
|
27
|
+
/** Method exists at selected version but is not granted (§6.5). */
|
|
28
|
+
CAPABILITY_NOT_ALLOWED: -32010,
|
|
29
|
+
/** In-flight request gated on a capability that was just revoked (§6.6). */
|
|
30
|
+
CAPABILITY_REVOKED: -32011,
|
|
31
|
+
/** Frame arrived before the required handshake step completed (§5.2, §4.2). */
|
|
32
|
+
NOT_INITIALIZED: -32012,
|
|
33
|
+
/** Embedding's protocol version is not supported by the host (§4.2, §3.1.2). */
|
|
34
|
+
PROTOCOL_VERSION_MISMATCH: -32013,
|
|
35
|
+
/** Frame's origin does not match the bound peer origin (§3.1). */
|
|
36
|
+
ORIGIN_MISMATCH: -32014,
|
|
37
|
+
/** Catch-all — duplicate ui/select-version, duplicate ui/discover-capabilities, etc. */
|
|
38
|
+
PROTOCOL_VIOLATION: -32020,
|
|
39
|
+
/** Embedding observed a second valid bootstrap envelope and shut down (§3.1.2). */
|
|
40
|
+
BOOTSTRAP_DUPLICATE_TRANSFER: -32023,
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Codes that are non-retryable by default (§8.4). Consumers building error
|
|
44
|
+
* payloads SHOULD set `retryable: false` automatically for these.
|
|
45
|
+
*/
|
|
46
|
+
const NON_RETRYABLE_CODES = new Set([
|
|
47
|
+
SfEmbeddingErrorCode.CAPABILITY_NOT_ALLOWED,
|
|
48
|
+
SfEmbeddingErrorCode.CAPABILITY_REVOKED,
|
|
49
|
+
SfEmbeddingErrorCode.ORIGIN_MISMATCH,
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
// Host LWC reserved DOM event names and detail shapes. Spec §12.3.
|
|
53
|
+
//
|
|
54
|
+
// These are dispatched by the host LWC on its own element so application
|
|
55
|
+
// code on the page can react. The embedding never observes them.
|
|
56
|
+
/** Event names the host LWC dispatches on its own element. Reserved by the protocol. */
|
|
57
|
+
const HostLwcEvent = {
|
|
58
|
+
/** Synchronous with `ui/discover-capabilities` response; session reaches READY. */
|
|
59
|
+
READY: "sf-embedding.component.ready",
|
|
60
|
+
/** Misconfiguration, bootstrap failure, embedding-reported fatal, or protocol violation. */
|
|
61
|
+
ERROR: "sf-embedding.component.error",
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Host-supplied iframe URL metadata (per ADR 0029). Spec §3.1, MC-8/MC-10.
|
|
65
|
+
/** URL query-parameter name carrying the JSON-encoded `HostMetaData`. */
|
|
66
|
+
const HOST_META_DATA_PARAM = "hostMetaData";
|
|
67
|
+
|
|
68
|
+
// JSON-RPC method names on the wire today.
|
|
69
|
+
/** Host announces session identity to the embedding over the port. */
|
|
70
|
+
const HOST_INITIALIZED_METHOD = "ui/notifications/host-initialized";
|
|
71
|
+
/** Embedding acknowledges receipt of `host-initialized`. Fire-and-forget. */
|
|
72
|
+
const EMBEDDING_INITIALIZED_METHOD = "ui/notifications/embedding-initialized";
|
|
73
|
+
|
|
74
|
+
// Bootstrap envelope validator for the iframe (embedding) side of the
|
|
75
|
+
// sf-embedding handshake.
|
|
76
|
+
//
|
|
77
|
+
// Wire-format types and discriminator constants live in
|
|
78
|
+
// `@salesforce/sf-embedding-contract`; consumers MUST import them from there.
|
|
79
|
+
// This module owns only the embedding-side validator
|
|
80
|
+
// (`validateBootstrapEnvelope`) and its supporting types — intentionally NOT
|
|
81
|
+
// in the contract package because the host and embedding sides have
|
|
82
|
+
// different validation rules per spec §3.1.2.
|
|
83
|
+
/** Type guard: true iff `value` is shape-compatible with `BootstrapEnvelope`. */
|
|
84
|
+
function isBootstrapEnvelope(value) {
|
|
85
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
86
|
+
return false;
|
|
87
|
+
const v = value;
|
|
88
|
+
if (v.type !== BOOTSTRAP_ENVELOPE_TYPE)
|
|
89
|
+
return false;
|
|
90
|
+
if (v.protocol !== PROTOCOL_NAME)
|
|
91
|
+
return false;
|
|
92
|
+
if (typeof v.instanceId !== "string" || v.instanceId.length === 0)
|
|
93
|
+
return false;
|
|
94
|
+
if (!Array.isArray(v.allowedOrigins))
|
|
95
|
+
return false;
|
|
96
|
+
return v.allowedOrigins.every((o) => typeof o === "string");
|
|
97
|
+
}
|
|
98
|
+
/** Validates an inbound bootstrap envelope; never throws — failures returned as typed reasons. */
|
|
99
|
+
function validateBootstrapEnvelope(input) {
|
|
100
|
+
if (!isBootstrapEnvelope(input.data))
|
|
101
|
+
return { ok: false, reason: "INVALID_SHAPE" };
|
|
102
|
+
if (input.source !== input.expectedSource)
|
|
103
|
+
return { ok: false, reason: "WRONG_SOURCE" };
|
|
104
|
+
if (!input.allowedOrigins.includes(input.origin)) {
|
|
105
|
+
return { ok: false, reason: "ORIGIN_NOT_ALLOWED" };
|
|
106
|
+
}
|
|
107
|
+
if (input.ports.length !== 1)
|
|
108
|
+
return { ok: false, reason: "WRONG_PORT_COUNT" };
|
|
109
|
+
if (input.data.instanceId !== input.expectedInstanceId) {
|
|
110
|
+
return { ok: false, reason: "INSTANCE_ID_MISMATCH" };
|
|
111
|
+
}
|
|
112
|
+
if (input.alreadyAccepted)
|
|
113
|
+
return { ok: false, reason: "DUPLICATE_TRANSFER" };
|
|
114
|
+
return { ok: true, envelope: input.data, port: input.ports[0] };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Parses the JSON-encoded `hostMetaData` query parameter from an iframe URL's
|
|
119
|
+
* search string (per ADR 0029). Returns null on any malformed input — caller
|
|
120
|
+
* fails fast rather than partially initializing.
|
|
121
|
+
*/
|
|
122
|
+
function readHostMetaData(search) {
|
|
123
|
+
let params;
|
|
124
|
+
try {
|
|
125
|
+
params = new URLSearchParams(search);
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
const raw = params.get(HOST_META_DATA_PARAM);
|
|
131
|
+
if (!raw)
|
|
132
|
+
return null;
|
|
133
|
+
let parsed;
|
|
134
|
+
try {
|
|
135
|
+
parsed = JSON.parse(raw);
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
141
|
+
return null;
|
|
142
|
+
const obj = parsed;
|
|
143
|
+
const instanceId = obj.instanceId;
|
|
144
|
+
const hostAppOrigin = obj.hostAppOrigin;
|
|
145
|
+
if (typeof instanceId !== "string" || instanceId.length === 0)
|
|
146
|
+
return null;
|
|
147
|
+
if (typeof hostAppOrigin !== "string" || hostAppOrigin.length === 0)
|
|
148
|
+
return null;
|
|
149
|
+
// hostAppOrigin is the targetOrigin for postMessage; reject anything that isn't
|
|
150
|
+
// a syntactically valid origin (scheme://host[:port]) so a malformed value can't
|
|
151
|
+
// silently drop the heartbeat.
|
|
152
|
+
try {
|
|
153
|
+
if (new URL(hostAppOrigin).origin !== hostAppOrigin)
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
return { instanceId, hostAppOrigin };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
class BootstrapFailureError extends Error {
|
|
163
|
+
reason;
|
|
164
|
+
constructor(reason) {
|
|
165
|
+
super(`sf-embedding bootstrap failed: ${reason}`);
|
|
166
|
+
this.reason = reason;
|
|
167
|
+
this.name = "BootstrapFailureError";
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function installBootstrapListener() {
|
|
171
|
+
const meta = readHostMetaData(window.location.search);
|
|
172
|
+
if (!meta) {
|
|
173
|
+
return Promise.reject(new BootstrapFailureError("MISSING_HOST_METADATA"));
|
|
174
|
+
}
|
|
175
|
+
return new Promise((resolve) => {
|
|
176
|
+
let accepted = false;
|
|
177
|
+
const handler = (event) => {
|
|
178
|
+
const result = validateBootstrapEnvelope({
|
|
179
|
+
data: event.data,
|
|
180
|
+
source: event.source,
|
|
181
|
+
origin: event.origin,
|
|
182
|
+
ports: event.ports,
|
|
183
|
+
expectedSource: window.parent,
|
|
184
|
+
allowedOrigins: [meta.hostAppOrigin],
|
|
185
|
+
expectedInstanceId: meta.instanceId,
|
|
186
|
+
alreadyAccepted: accepted,
|
|
187
|
+
});
|
|
188
|
+
if (!result.ok) {
|
|
189
|
+
if (result.reason === "DUPLICATE_TRANSFER") {
|
|
190
|
+
try {
|
|
191
|
+
window.parent.postMessage({ type: SHUTDOWN_TYPE, reason: "DUPLICATE_PORT_TRANSFER" }, meta.hostAppOrigin);
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
// ignore
|
|
195
|
+
}
|
|
196
|
+
window.removeEventListener("message", handler);
|
|
197
|
+
}
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
accepted = true;
|
|
201
|
+
resolve({ port: result.port, hostMetaData: meta });
|
|
202
|
+
// Listener stays registered per spec §3.1 — a second valid
|
|
203
|
+
// envelope MUST trigger the fail-closed shutdown branch above.
|
|
204
|
+
};
|
|
205
|
+
window.addEventListener("message", handler);
|
|
206
|
+
try {
|
|
207
|
+
window.parent.postMessage({ type: HEARTBEAT_TYPE, instanceId: meta.instanceId }, meta.hostAppOrigin);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
// ignore
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** Identity emitted on `ui/notifications/embedding-initialized`. */
|
|
216
|
+
const EMBEDDING_INFO = {
|
|
217
|
+
name: "@salesforce/sf-embedding-bridge",
|
|
218
|
+
version: "2.2.1-rc.7",
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
class SessionFailureError extends Error {
|
|
222
|
+
reason;
|
|
223
|
+
constructor(reason, cause) {
|
|
224
|
+
super(`sf-embedding session bootstrap failed: ${reason}`);
|
|
225
|
+
this.reason = reason;
|
|
226
|
+
this.name = "SessionFailureError";
|
|
227
|
+
if (cause !== undefined) {
|
|
228
|
+
this.cause = cause;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
class SessionClient extends JsonRpcClient {
|
|
233
|
+
waitForHostInitialized() {
|
|
234
|
+
return new Promise((resolve) => {
|
|
235
|
+
this.registerNotificationHandler(HOST_INITIALIZED_METHOD, (params) => {
|
|
236
|
+
const payload = params && typeof params === "object" && !Array.isArray(params)
|
|
237
|
+
? params
|
|
238
|
+
: {};
|
|
239
|
+
resolve(payload);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
sendInitializedNotification() {
|
|
244
|
+
const params = { embeddingInfo: EMBEDDING_INFO };
|
|
245
|
+
this.sendNotification(EMBEDDING_INITIALIZED_METHOD, params);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
function rejectOnAbort(signal) {
|
|
249
|
+
if (!signal)
|
|
250
|
+
return new Promise(() => { });
|
|
251
|
+
if (signal.aborted)
|
|
252
|
+
return Promise.reject(new SessionFailureError("ABORTED"));
|
|
253
|
+
return new Promise((_, reject) => {
|
|
254
|
+
signal.addEventListener("abort", () => reject(new SessionFailureError("ABORTED")), { once: true });
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
let sessionPromise = null;
|
|
258
|
+
/**
|
|
259
|
+
* Bootstraps the sf-embedding session. Single-attempt per iframe — recovery
|
|
260
|
+
* from failure requires a host-driven remount (`port1` is one-shot; a new
|
|
261
|
+
* session needs a new `MessageChannel` + `instanceId`).
|
|
262
|
+
*/
|
|
263
|
+
function bootstrapSession(options = {}) {
|
|
264
|
+
if (sessionPromise)
|
|
265
|
+
return sessionPromise;
|
|
266
|
+
sessionPromise = installBootstrapListener()
|
|
267
|
+
.then(async (capture) => {
|
|
268
|
+
const transport = new MessageChannelTransport(capture.port);
|
|
269
|
+
const client = new SessionClient(transport);
|
|
270
|
+
const hostInitialized = await Promise.race([
|
|
271
|
+
client.waitForHostInitialized(),
|
|
272
|
+
rejectOnAbort(options.signal),
|
|
273
|
+
]);
|
|
274
|
+
client.sendInitializedNotification();
|
|
275
|
+
return {
|
|
276
|
+
client,
|
|
277
|
+
hostMetaData: capture.hostMetaData,
|
|
278
|
+
hostInitialized,
|
|
279
|
+
};
|
|
280
|
+
})
|
|
281
|
+
.catch((err) => {
|
|
282
|
+
if (err instanceof SessionFailureError)
|
|
283
|
+
throw err;
|
|
284
|
+
if (err instanceof Error && "reason" in err) {
|
|
285
|
+
throw new SessionFailureError(err.reason, err);
|
|
286
|
+
}
|
|
287
|
+
throw err;
|
|
288
|
+
});
|
|
289
|
+
sessionPromise.catch(() => {
|
|
290
|
+
// no-op
|
|
291
|
+
});
|
|
292
|
+
return sessionPromise;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Bridge-side error vocabulary: composes the standard JSON-RPC + transport
|
|
296
|
+
// codes from `@salesforce/jsonrpc` with the sf-embedding-specific
|
|
297
|
+
// codes from `@salesforce/sf-embedding-contract`, and wraps `makeJsonRpcError`
|
|
298
|
+
// to inject sf-embedding-specific retryable defaults.
|
|
299
|
+
/** Combined error vocabulary: standard + transport-level + sf-embedding-specific. */
|
|
300
|
+
const ErrorCode = {
|
|
301
|
+
...StandardErrorCode,
|
|
302
|
+
...SfEmbeddingErrorCode,
|
|
303
|
+
};
|
|
304
|
+
/** Builds a JSON-RPC error payload with sf-embedding-specific retryable defaults; falls through to @salesforce/jsonrpc for codes it owns. */
|
|
305
|
+
function makeJsonRpcError(code, options = {}) {
|
|
306
|
+
if (NON_RETRYABLE_CODES.has(code) && options.retryable === undefined) {
|
|
307
|
+
return makeJsonRpcError$1(code, { ...options, retryable: false });
|
|
308
|
+
}
|
|
309
|
+
return makeJsonRpcError$1(code, options);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export { BOOTSTRAP_ENVELOPE_TYPE, BootstrapFailureError, EMBEDDING_INITIALIZED_METHOD, ErrorCode, HEARTBEAT_TYPE, HOST_INITIALIZED_METHOD, HOST_META_DATA_PARAM, HostLwcEvent, NON_RETRYABLE_CODES, PROTOCOL_NAME, SHUTDOWN_TYPE, SessionFailureError, SfEmbeddingErrorCode, bootstrapSession, isBootstrapEnvelope, makeJsonRpcError, readHostMetaData, validateBootstrapEnvelope };
|
|
313
|
+
//# sourceMappingURL=index.esm.js.map
|
package/package.json
CHANGED
|
@@ -1,10 +1,42 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
|
|
2
|
+
"name": "@salesforce/sf-embedding-bridge",
|
|
3
|
+
"version": "2.2.1-rc.7",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "JSON-RPC 2.0 bridge for the sf-embedding host ↔ embedding protocol",
|
|
6
|
+
"license": "SEE LICENSE IN LICENSE.txt",
|
|
7
|
+
"author": "Salesforce ECS Team",
|
|
8
|
+
"main": "dist/index.cjs.js",
|
|
9
|
+
"module": "dist/index.esm.js",
|
|
10
|
+
"types": "dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.esm.js",
|
|
15
|
+
"require": "./dist/index.cjs.js",
|
|
16
|
+
"default": "./dist/index.esm.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "rollup -c",
|
|
21
|
+
"test": "jest --passWithNoTests"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@salesforce/jsonrpc": "^9.13.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@salesforce/sf-embedding-contract": "2.2.1-rc.7"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist/",
|
|
31
|
+
"!dist/__tests__/",
|
|
32
|
+
"!dist/__mocks__/",
|
|
33
|
+
"!dist/*.test.js",
|
|
34
|
+
"!dist/*.map",
|
|
35
|
+
"README.md",
|
|
36
|
+
"LICENSE.txt"
|
|
37
|
+
],
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"sideEffects": false
|
|
10
42
|
}
|
package/README.md
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# @salesforce/sf-embedding-bridge
|
|
2
|
-
|
|
3
|
-
## ⚠️ IMPORTANT NOTICE ⚠️
|
|
4
|
-
|
|
5
|
-
**This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
|
|
6
|
-
|
|
7
|
-
This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
|
|
8
|
-
|
|
9
|
-
## Purpose
|
|
10
|
-
|
|
11
|
-
This package exists to:
|
|
12
|
-
1. Configure OIDC trusted publishing for the package name `@salesforce/sf-embedding-bridge`
|
|
13
|
-
2. Enable secure, token-less publishing from CI/CD workflows
|
|
14
|
-
3. Establish provenance for packages published under this name
|
|
15
|
-
|
|
16
|
-
## What is OIDC Trusted Publishing?
|
|
17
|
-
|
|
18
|
-
OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
|
|
19
|
-
|
|
20
|
-
## Setup Instructions
|
|
21
|
-
|
|
22
|
-
To properly configure OIDC trusted publishing for this package:
|
|
23
|
-
|
|
24
|
-
1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
|
|
25
|
-
2. Configure the trusted publisher (e.g., GitHub Actions)
|
|
26
|
-
3. Specify the repository and workflow that should be allowed to publish
|
|
27
|
-
4. Use the configured workflow to publish your actual package
|
|
28
|
-
|
|
29
|
-
## DO NOT USE THIS PACKAGE
|
|
30
|
-
|
|
31
|
-
This package is a placeholder for OIDC configuration only. It:
|
|
32
|
-
- Contains no executable code
|
|
33
|
-
- Provides no functionality
|
|
34
|
-
- Should not be installed as a dependency
|
|
35
|
-
- Exists only for administrative purposes
|
|
36
|
-
|
|
37
|
-
## More Information
|
|
38
|
-
|
|
39
|
-
For more details about npm's trusted publishing feature, see:
|
|
40
|
-
- [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
|
|
41
|
-
- [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
|
|
42
|
-
|
|
43
|
-
---
|
|
44
|
-
|
|
45
|
-
**Maintained for OIDC setup purposes only**
|