@adobe/aio-commerce-lib-admin-ui 0.2.0-beta-20260714082406 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -23
- package/dist/cjs/mass-actions/index.cjs +1 -55
- package/dist/cjs/mass-actions/index.d.cts +1 -42
- package/dist/cjs/web/index.cjs +122 -80
- package/dist/cjs/web/index.d.cts +50 -36
- package/dist/es/mass-actions/index.d.mts +1 -42
- package/dist/es/mass-actions/index.mjs +1 -53
- package/dist/es/web/index.d.mts +50 -36
- package/dist/es/web/index.mjs +122 -80
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,50 +1,32 @@
|
|
|
1
1
|
# @adobe/aio-commerce-lib-admin-ui
|
|
2
2
|
|
|
3
|
-
## 0.2.0
|
|
4
|
-
### Minor Changes
|
|
5
|
-
|
|
3
|
+
## 0.2.0
|
|
6
4
|
|
|
5
|
+
### Minor Changes
|
|
7
6
|
|
|
8
7
|
- [#528](https://github.com/adobe/aio-commerce-sdk/pull/528) [`0bb7d15`](https://github.com/adobe/aio-commerce-sdk/commit/0bb7d150961f631116b1ac421124960b5b857896) Thanks [@oshmyheliuk](https://github.com/oshmyheliuk)! - Add `getGridColumnAclResourceId`, `getMassActionAclResourceId`, and `getOrderViewButtonAclResourceId` helpers to derive the Commerce ACL resource id for protected grid columns, mass actions, and order view buttons, mirroring `getMenuAclResourceId`.
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
9
|
- [#522](https://github.com/adobe/aio-commerce-sdk/pull/522) [`4396a64`](https://github.com/adobe/aio-commerce-sdk/commit/4396a647ad22dba063cdf03a16e04aef9387212f) Thanks [@oshmyheliuk](https://github.com/oshmyheliuk)! - Add `getAdminUiPermissionClient` for checking ACL permissions at runtime (SPA bootstrap and runtime action entry). Includes in-process TTL cache, in-flight request deduplication, and deny-by-default on errors. Also exports `getAclResourceId` for deterministic ACL resource id derivation from `metadata.id`.
|
|
13
10
|
|
|
14
|
-
|
|
15
|
-
|
|
16
11
|
- [#542](https://github.com/adobe/aio-commerce-sdk/pull/542) [`c15b50f`](https://github.com/adobe/aio-commerce-sdk/commit/c15b50f667e6d1288270dbd4192c150db0ff0914) Thanks [@iivvaannxx](https://github.com/iivvaannxx)! - Add browser helpers for Admin UI iframe extensions.
|
|
17
12
|
|
|
18
|
-
|
|
19
|
-
|
|
20
13
|
- [#508](https://github.com/adobe/aio-commerce-sdk/pull/508) [`c36f825`](https://github.com/adobe/aio-commerce-sdk/commit/c36f825aff7329fc2831d8aaca032d17bbee373a) Thanks [@iivvaannxx](https://github.com/iivvaannxx)! - Add `@adobe/aio-commerce-lib-admin-ui/menu` entrypoint with named constants for Commerce Admin menu IDs (`MENU_SALES`, `MENU_CATALOG`, etc.), a `COMMERCE_MENUS` collection, a `CommerceMenu` type, and an `isCommerceMenu` type guard.
|
|
21
14
|
|
|
22
|
-
|
|
23
|
-
|
|
24
15
|
- [#516](https://github.com/adobe/aio-commerce-sdk/pull/516) [`bdcced9`](https://github.com/adobe/aio-commerce-sdk/commit/bdcced9297ae5e1b87ce86490a2b6fad76b2e341) Thanks [@obarcelonap](https://github.com/obarcelonap)! - Add `order-view-buttons` entrypoint with request parsing and response builders for `commerce/backend-ui/2` order view button handlers.
|
|
25
16
|
|
|
26
|
-
|
|
27
|
-
|
|
28
17
|
- [#551](https://github.com/adobe/aio-commerce-sdk/pull/551) [`a66f389`](https://github.com/adobe/aio-commerce-sdk/commit/a66f3892622584d27cd6b5d2fc633e15bf9942a8) Thanks [@asalloum5](https://github.com/asalloum5)! - Add `enableAdminUiSdk` to the Admin UI API client, which enables the Admin UI SDK in Commerce via `PUT /V1/adminuisdk/config`.
|
|
29
18
|
|
|
30
|
-
|
|
31
|
-
|
|
32
19
|
- [#486](https://github.com/adobe/aio-commerce-sdk/pull/486) [`413da36`](https://github.com/adobe/aio-commerce-sdk/commit/413da36dcd83e5a41c194f5af50611097659e725) Thanks [@obarcelonap](https://github.com/obarcelonap)! - Add request and response builders for the `commerce/backend-ui/2` grid column wire contract.
|
|
33
20
|
|
|
34
|
-
|
|
35
|
-
|
|
36
21
|
- [#546](https://github.com/adobe/aio-commerce-sdk/pull/546) [`ca2c4fe`](https://github.com/adobe/aio-commerce-sdk/commit/ca2c4febcdfbe52b933775f08e9a53765fc306ab) Thanks [@obarcelonap](https://github.com/obarcelonap)! - Remove `extensionUrl` from extension registration params. The Commerce backend now derives the view URL from the App Registry, so the field has no effect for v2 installed apps.
|
|
37
22
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
- [#492](https://github.com/adobe/aio-commerce-sdk/pull/492) [`ce63b2b`](https://github.com/adobe/aio-commerce-sdk/commit/ce63b2b46f7ef1f2f68780b13e52d1c13fd5fe6e) Thanks [@jcuerdo](https://github.com/jcuerdo)! - Add `mass-actions` entrypoint with wire contract helpers for `commerce/backend-ui/2` mass actions. Parse worker action requests with `parseMassActionRequest`, build success/error responses with `okMassActionResponse`/`errorMassActionResponse`, and parse iframe selection parameters with `parseMassActionSelection`.
|
|
41
|
-
|
|
23
|
+
- [#492](https://github.com/adobe/aio-commerce-sdk/pull/492) [`ce63b2b`](https://github.com/adobe/aio-commerce-sdk/commit/ce63b2b46f7ef1f2f68780b13e52d1c13fd5fe6e) Thanks [@jcuerdo](https://github.com/jcuerdo)! - Add `mass-actions` entrypoint with wire contract helpers for `commerce/backend-ui/2` mass actions. Parse worker action requests with `parseMassActionRequest`, and build success/error responses with `okMassActionResponse`/`errorMassActionResponse`.
|
|
42
24
|
|
|
43
25
|
### Patch Changes
|
|
44
26
|
|
|
45
27
|
- Updated dependencies [[`19400fb`](https://github.com/adobe/aio-commerce-sdk/commit/19400fbdfc03c5bbfc30f995a2acdd4272c5e032), [`ce7768e`](https://github.com/adobe/aio-commerce-sdk/commit/ce7768e0ddc6ca35ad6ccec60f584c8bb5a743f0), [`4396a64`](https://github.com/adobe/aio-commerce-sdk/commit/4396a647ad22dba063cdf03a16e04aef9387212f), [`e85bec4`](https://github.com/adobe/aio-commerce-sdk/commit/e85bec40f1eaa91dd6bb7bc6954b2ecfc3ea4029), [`413da36`](https://github.com/adobe/aio-commerce-sdk/commit/413da36dcd83e5a41c194f5af50611097659e725)]:
|
|
46
|
-
- @adobe/aio-commerce-lib-core@1.2.0
|
|
47
|
-
- @adobe/aio-commerce-lib-api@1.3.0
|
|
28
|
+
- @adobe/aio-commerce-lib-core@1.2.0
|
|
29
|
+
- @adobe/aio-commerce-lib-api@1.3.0
|
|
48
30
|
|
|
49
31
|
## 0.1.0
|
|
50
32
|
|
|
@@ -51,58 +51,6 @@ function getMassActionAclResourceId(metadataId, entity, actionId) {
|
|
|
51
51
|
return `${appRoot}_${entity}_massactions_${require_acl_resource_id.sanitizeSegment(actionId)}`;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
//#endregion
|
|
55
|
-
//#region source/mass-actions/view/schema.ts
|
|
56
|
-
/**
|
|
57
|
-
* Schema for the `selection` query parameter Commerce appends to the iframe URL
|
|
58
|
-
* of a view mass action.
|
|
59
|
-
*
|
|
60
|
-
* Commerce serializes the selection as a JSON-encoded string:
|
|
61
|
-
* `?selection={"ids":["000000001"],"gridType":"customer"}`
|
|
62
|
-
*/
|
|
63
|
-
const MassActionSelectionSchema = valibot.object({
|
|
64
|
-
gridType: valibot.picklist([
|
|
65
|
-
"order",
|
|
66
|
-
"product",
|
|
67
|
-
"customer"
|
|
68
|
-
]),
|
|
69
|
-
ids: valibot.pipe(valibot.array(require_schemas.nonEmptyStringValueSchema("id")), valibot.minLength(1, "The value of \"ids\" must contain at least one entry"))
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
//#endregion
|
|
73
|
-
//#region source/mass-actions/view/presets.ts
|
|
74
|
-
/**
|
|
75
|
-
* Parses the `selection` query parameter Commerce appends to the iframe URL of
|
|
76
|
-
* a view mass action.
|
|
77
|
-
*
|
|
78
|
-
* Commerce serializes the selection as a JSON string:
|
|
79
|
-
* `?selection={"ids":["000000001","000000002"],"gridType":"customer"}`
|
|
80
|
-
*
|
|
81
|
-
* Throws a `CommerceSdkValidationError` if the input is missing, not valid
|
|
82
|
-
* JSON, or does not match the expected shape.
|
|
83
|
-
*
|
|
84
|
-
* @param rawSelection - The raw string value of the `selection` query parameter.
|
|
85
|
-
*
|
|
86
|
-
* @example
|
|
87
|
-
* ```ts
|
|
88
|
-
* import { parseMassActionSelection } from "@adobe/aio-commerce-lib-admin-ui/mass-actions";
|
|
89
|
-
*
|
|
90
|
-
* // In your SPA route handler:
|
|
91
|
-
* const raw = new URLSearchParams(window.location.search).get("selection");
|
|
92
|
-
* const { ids, gridType } = parseMassActionSelection(raw);
|
|
93
|
-
* ```
|
|
94
|
-
*/
|
|
95
|
-
function parseMassActionSelection(rawSelection) {
|
|
96
|
-
if (rawSelection === null || rawSelection === void 0) throw new Error("Invalid mass action selection: the selection query parameter is missing.");
|
|
97
|
-
let parsed;
|
|
98
|
-
try {
|
|
99
|
-
parsed = JSON.parse(String(rawSelection));
|
|
100
|
-
} catch (error) {
|
|
101
|
-
throw new Error(`Invalid mass action selection: expected a JSON string, got: ${String(rawSelection)}`, { cause: error });
|
|
102
|
-
}
|
|
103
|
-
return require_utils.parseOrThrow(MassActionSelectionSchema, parsed, "Invalid mass action selection");
|
|
104
|
-
}
|
|
105
|
-
|
|
106
54
|
//#endregion
|
|
107
55
|
//#region source/mass-actions/worker/schema.ts
|
|
108
56
|
/**
|
|
@@ -180,9 +128,7 @@ function massActionErrorResponse(statusCode, errorMessage) {
|
|
|
180
128
|
//#endregion
|
|
181
129
|
exports.MassActionGridTypeSchema = MassActionGridTypeSchema;
|
|
182
130
|
exports.MassActionRequestSchema = MassActionRequestSchema;
|
|
183
|
-
exports.MassActionSelectionSchema = MassActionSelectionSchema;
|
|
184
131
|
exports.getMassActionAclResourceId = getMassActionAclResourceId;
|
|
185
132
|
exports.massActionErrorResponse = massActionErrorResponse;
|
|
186
133
|
exports.okMassActionResponse = okMassActionResponse;
|
|
187
|
-
exports.parseMassActionRequest = parseMassActionRequest;
|
|
188
|
-
exports.parseMassActionSelection = parseMassActionSelection;
|
|
134
|
+
exports.parseMassActionRequest = parseMassActionRequest;
|
|
@@ -42,47 +42,6 @@ import { ErrorResponse, SuccessResponse } from "@adobe/aio-commerce-lib-core/res
|
|
|
42
42
|
*/
|
|
43
43
|
declare function getMassActionAclResourceId(metadataId: string, entity: AdminUiEntity, actionId: string): string;
|
|
44
44
|
//#endregion
|
|
45
|
-
//#region source/mass-actions/view/schema.d.ts
|
|
46
|
-
/**
|
|
47
|
-
* Schema for the `selection` query parameter Commerce appends to the iframe URL
|
|
48
|
-
* of a view mass action.
|
|
49
|
-
*
|
|
50
|
-
* Commerce serializes the selection as a JSON-encoded string:
|
|
51
|
-
* `?selection={"ids":["000000001"],"gridType":"customer"}`
|
|
52
|
-
*/
|
|
53
|
-
declare const MassActionSelectionSchema: v.ObjectSchema<{
|
|
54
|
-
readonly gridType: v.PicklistSchema<["order", "product", "customer"], undefined>;
|
|
55
|
-
readonly ids: v.SchemaWithPipe<readonly [v.ArraySchema<v.SchemaWithPipe<readonly [v.StringSchema<`Expected a string value for '${string}'`>, v.NonEmptyAction<string, `The value of "${string}" must not be empty`>]>, undefined>, v.MinLengthAction<string[], 1, "The value of \"ids\" must contain at least one entry">]>;
|
|
56
|
-
}, undefined>;
|
|
57
|
-
//#endregion
|
|
58
|
-
//#region source/mass-actions/view/types.d.ts
|
|
59
|
-
/** Parsed `selection` query parameter appended by Commerce to the iframe URL. */
|
|
60
|
-
type MassActionSelection = v.InferOutput<typeof MassActionSelectionSchema>;
|
|
61
|
-
//#endregion
|
|
62
|
-
//#region source/mass-actions/view/presets.d.ts
|
|
63
|
-
/**
|
|
64
|
-
* Parses the `selection` query parameter Commerce appends to the iframe URL of
|
|
65
|
-
* a view mass action.
|
|
66
|
-
*
|
|
67
|
-
* Commerce serializes the selection as a JSON string:
|
|
68
|
-
* `?selection={"ids":["000000001","000000002"],"gridType":"customer"}`
|
|
69
|
-
*
|
|
70
|
-
* Throws a `CommerceSdkValidationError` if the input is missing, not valid
|
|
71
|
-
* JSON, or does not match the expected shape.
|
|
72
|
-
*
|
|
73
|
-
* @param rawSelection - The raw string value of the `selection` query parameter.
|
|
74
|
-
*
|
|
75
|
-
* @example
|
|
76
|
-
* ```ts
|
|
77
|
-
* import { parseMassActionSelection } from "@adobe/aio-commerce-lib-admin-ui/mass-actions";
|
|
78
|
-
*
|
|
79
|
-
* // In your SPA route handler:
|
|
80
|
-
* const raw = new URLSearchParams(window.location.search).get("selection");
|
|
81
|
-
* const { ids, gridType } = parseMassActionSelection(raw);
|
|
82
|
-
* ```
|
|
83
|
-
*/
|
|
84
|
-
declare function parseMassActionSelection(rawSelection: unknown): MassActionSelection;
|
|
85
|
-
//#endregion
|
|
86
45
|
//#region source/mass-actions/worker/schema.d.ts
|
|
87
46
|
/**
|
|
88
47
|
* Grid identifier sent by Commerce on the `commerce/backend-ui/2` wire contract
|
|
@@ -157,4 +116,4 @@ declare function okMassActionResponse(body?: MassActionResponseBody): SuccessRes
|
|
|
157
116
|
*/
|
|
158
117
|
declare function massActionErrorResponse(statusCode: number, errorMessage: string): ErrorResponse<MassActionErrorBody>;
|
|
159
118
|
//#endregion
|
|
160
|
-
export { type AdminUiEntity, type MassActionErrorBody, type MassActionGridType, MassActionGridTypeSchema, type MassActionRequest, MassActionRequestSchema, type MassActionResponseBody,
|
|
119
|
+
export { type AdminUiEntity, type MassActionErrorBody, type MassActionGridType, MassActionGridTypeSchema, type MassActionRequest, MassActionRequestSchema, type MassActionResponseBody, getMassActionAclResourceId, massActionErrorResponse, okMassActionResponse, parseMassActionRequest };
|
package/dist/cjs/web/index.cjs
CHANGED
|
@@ -53,6 +53,37 @@ function isControlFrame() {
|
|
|
53
53
|
return isEmbeddedInHost() && !window.name;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region source/web/react/result.ts
|
|
58
|
+
/** Creates a successful data result. */
|
|
59
|
+
function ok(data) {
|
|
60
|
+
return {
|
|
61
|
+
data,
|
|
62
|
+
error: null
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/** Creates a failed data result. */
|
|
66
|
+
function error(message, options) {
|
|
67
|
+
return {
|
|
68
|
+
data: null,
|
|
69
|
+
error: new Error(message, options)
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/** Creates a successful actions result. */
|
|
73
|
+
function okActions(actions) {
|
|
74
|
+
return {
|
|
75
|
+
actions,
|
|
76
|
+
error: null
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/** Creates a failed actions result. */
|
|
80
|
+
function actionsError(message, options) {
|
|
81
|
+
return {
|
|
82
|
+
actions: null,
|
|
83
|
+
error: new Error(message, options)
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
56
87
|
//#endregion
|
|
57
88
|
//#region source/web/react/auth/context/ims-context.tsx
|
|
58
89
|
const ImsContextValue = (0, react.createContext)(void 0);
|
|
@@ -60,14 +91,13 @@ const ImsContextValue = (0, react.createContext)(void 0);
|
|
|
60
91
|
* Returns the IMS credentials provided by the host. Works inside the Commerce Admin and the
|
|
61
92
|
* Experience Cloud shell.
|
|
62
93
|
*
|
|
63
|
-
*
|
|
64
|
-
* Commerce Admin and the Experience Cloud shell).
|
|
94
|
+
* Returns an error when no host provides credentials.
|
|
65
95
|
*/
|
|
66
96
|
function useIms() {
|
|
67
97
|
const credentials = (0, react.use)(ImsContextValue);
|
|
68
|
-
if (credentials === void 0)
|
|
69
|
-
if (!credentials)
|
|
70
|
-
return credentials;
|
|
98
|
+
if (credentials === void 0) return error("useIms must be used inside an ImsContextProvider.");
|
|
99
|
+
if (!credentials) return error("useIms requires running inside the Commerce Admin or the Experience Cloud shell, which provide the IMS credentials.");
|
|
100
|
+
return ok(credentials);
|
|
71
101
|
}
|
|
72
102
|
/**
|
|
73
103
|
* Provides the IMS credentials for a mounted Admin UI iframe app.
|
|
@@ -89,9 +119,7 @@ const SharedContextValue = (0, react.createContext)(void 0);
|
|
|
89
119
|
* Returns the current Commerce shared context provider state.
|
|
90
120
|
*/
|
|
91
121
|
function useInternalSharedContext() {
|
|
92
|
-
|
|
93
|
-
if (context === void 0) throw new Error("useSharedContext must be used inside a SharedContextProvider, which is only available in the Commerce Admin.");
|
|
94
|
-
return context;
|
|
122
|
+
return (0, react.use)(SharedContextValue);
|
|
95
123
|
}
|
|
96
124
|
/**
|
|
97
125
|
* Returns the current Commerce shared context. The guest connection is already established by
|
|
@@ -101,26 +129,29 @@ function useInternalSharedContext() {
|
|
|
101
129
|
* Prefer a purpose-built hook ({@link useCommerce}, {@link useMassActionContext},
|
|
102
130
|
* {@link useOrderViewButtonContext}) when one covers what you need.
|
|
103
131
|
*
|
|
104
|
-
* @throws If used outside a {@link SharedContextProvider}.
|
|
105
|
-
*
|
|
106
132
|
* @example
|
|
107
133
|
* ```tsx
|
|
108
134
|
* import { useSharedContext } from "@adobe/aio-commerce-lib-admin-ui/web";
|
|
109
135
|
*
|
|
110
136
|
* function ImsTokenLabel() {
|
|
111
|
-
* const {
|
|
112
|
-
*
|
|
137
|
+
* const { data, error } = useSharedContext();
|
|
138
|
+
* if (error) return null;
|
|
139
|
+
* return <span>{data.sharedContext.get("imsToken")}</span>;
|
|
113
140
|
* }
|
|
114
141
|
* ```
|
|
115
142
|
*/
|
|
116
143
|
function useSharedContext() {
|
|
117
|
-
const
|
|
118
|
-
const sharedContext = useLiveSharedContext(guestConnection);
|
|
119
|
-
return {
|
|
120
|
-
|
|
121
|
-
host
|
|
122
|
-
|
|
123
|
-
|
|
144
|
+
const context = useInternalSharedContext();
|
|
145
|
+
const sharedContext = useLiveSharedContext(context?.guestConnection);
|
|
146
|
+
return (0, react.useMemo)(() => {
|
|
147
|
+
if (!context) return error("useSharedContext must be used inside a SharedContextProvider, which is only available in the Commerce Admin.");
|
|
148
|
+
if (!sharedContext) return error("The Commerce host did not provide a shared context for this extension.");
|
|
149
|
+
return ok({
|
|
150
|
+
extensionId: context.extensionId,
|
|
151
|
+
host: context.guestConnection.host,
|
|
152
|
+
sharedContext
|
|
153
|
+
});
|
|
154
|
+
}, [context, sharedContext]);
|
|
124
155
|
}
|
|
125
156
|
/**
|
|
126
157
|
* Tracks the live UIX `sharedContext` for a connection.
|
|
@@ -132,7 +163,10 @@ function useSharedContext() {
|
|
|
132
163
|
* @param guestConnection - The established guest connection.
|
|
133
164
|
*/
|
|
134
165
|
function useLiveSharedContext(guestConnection) {
|
|
135
|
-
return (0, react.useSyncExternalStore)((0, react.useCallback)((onContextChange) =>
|
|
166
|
+
return (0, react.useSyncExternalStore)((0, react.useCallback)((onContextChange) => {
|
|
167
|
+
if (!guestConnection) return () => void 0;
|
|
168
|
+
return guestConnection.addEventListener("contextchange", onContextChange);
|
|
169
|
+
}, [guestConnection]), () => guestConnection?.sharedContext ?? null);
|
|
136
170
|
}
|
|
137
171
|
/**
|
|
138
172
|
* Provides the Commerce shared context for a mounted Admin UI iframe app.
|
|
@@ -154,28 +188,33 @@ function SharedContextProvider(props) {
|
|
|
154
188
|
//#endregion
|
|
155
189
|
//#region source/web/react/promise-cache.ts
|
|
156
190
|
/**
|
|
157
|
-
* Creates a keyed cache that memoizes in-flight,
|
|
191
|
+
* Creates a keyed cache that memoizes in-flight, successful, and failed promises, giving `use()`
|
|
158
192
|
* the reference-stable promise it needs while suspended and single-flighting side-effecting
|
|
159
193
|
* establishment calls across re-renders, remounts, and StrictMode double-invocation.
|
|
160
194
|
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
195
|
+
* Promise rejections are failures by default. Pass `isFailure` when a fulfilled value, such as an
|
|
196
|
+
* error result, must also be retryable. Failed entries remain cached until `evictIfFailed` is
|
|
197
|
+
* called, so retries never replace a stable promise during render.
|
|
198
|
+
*
|
|
199
|
+
* @param isFailure - Determines whether a fulfilled value represents a failure.
|
|
163
200
|
*/
|
|
164
|
-
function createRetryablePromiseCache() {
|
|
201
|
+
function createRetryablePromiseCache(isFailure = () => false) {
|
|
165
202
|
const cache = /* @__PURE__ */ new Map();
|
|
166
203
|
return {
|
|
167
|
-
|
|
168
|
-
if (cache.get(key)?.
|
|
204
|
+
evictIfFailed(key) {
|
|
205
|
+
if (cache.get(key)?.failed) cache.delete(key);
|
|
169
206
|
},
|
|
170
207
|
get(key, create) {
|
|
171
208
|
const cached = cache.get(key);
|
|
172
209
|
if (cached) return cached.promise;
|
|
173
210
|
const entry = {
|
|
174
|
-
|
|
175
|
-
|
|
211
|
+
failed: false,
|
|
212
|
+
promise: create()
|
|
176
213
|
};
|
|
177
|
-
entry.promise.
|
|
178
|
-
entry.
|
|
214
|
+
entry.promise.then((value) => {
|
|
215
|
+
entry.failed = isFailure(value);
|
|
216
|
+
}).catch(() => {
|
|
217
|
+
entry.failed = true;
|
|
179
218
|
});
|
|
180
219
|
cache.set(key, entry);
|
|
181
220
|
return entry.promise;
|
|
@@ -185,7 +224,7 @@ function createRetryablePromiseCache() {
|
|
|
185
224
|
|
|
186
225
|
//#endregion
|
|
187
226
|
//#region source/web/react/commerce/hooks/use-commerce.ts
|
|
188
|
-
const commerceHosts = createRetryablePromiseCache();
|
|
227
|
+
const commerceHosts = createRetryablePromiseCache((result) => result.error !== null);
|
|
189
228
|
/**
|
|
190
229
|
* Returns the cached Commerce Admin host promise for an extension, resolving it once over the
|
|
191
230
|
* given guest connection. The value is static for the lifetime of the connection.
|
|
@@ -194,24 +233,31 @@ const commerceHosts = createRetryablePromiseCache();
|
|
|
194
233
|
* @param connection - The established guest connection.
|
|
195
234
|
*/
|
|
196
235
|
function getCommerceHostPromise(extensionId, connection) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
236
|
+
return commerceHosts.get(extensionId, async () => {
|
|
237
|
+
const { integration } = connection.host;
|
|
238
|
+
if (!integration) return error("The host does not provide the integration API needed to resolve the Commerce host.");
|
|
239
|
+
try {
|
|
240
|
+
return ok({ commerceHost: await integration.getCommerceHost() });
|
|
241
|
+
} catch (cause) {
|
|
242
|
+
return error("Failed to resolve the Commerce host.", { cause });
|
|
243
|
+
}
|
|
244
|
+
});
|
|
200
245
|
}
|
|
201
246
|
/** Drops a failed Commerce host resolution for `extensionId`, so a later render retries it. */
|
|
202
247
|
function retryCommerceHost(extensionId) {
|
|
203
|
-
commerceHosts.
|
|
248
|
+
commerceHosts.evictIfFailed(extensionId);
|
|
204
249
|
}
|
|
205
250
|
/**
|
|
206
251
|
* Returns the host (domain) of the Commerce Admin the extension is embedded in, resolving it over
|
|
207
252
|
* the guest connection.
|
|
208
253
|
*
|
|
209
|
-
*
|
|
210
|
-
* Commerce integration API.
|
|
254
|
+
* Returns an error when used outside a Commerce Admin UI frame, when the host does not expose the
|
|
255
|
+
* Commerce integration API, or when resolving the host fails.
|
|
211
256
|
*/
|
|
212
257
|
function useCommerce() {
|
|
213
|
-
const
|
|
214
|
-
|
|
258
|
+
const context = useInternalSharedContext();
|
|
259
|
+
if (!context) return error("useCommerce requires running inside the Commerce Admin.");
|
|
260
|
+
return (0, react.use)(getCommerceHostPromise(context.extensionId, context.guestConnection));
|
|
215
261
|
}
|
|
216
262
|
|
|
217
263
|
//#endregion
|
|
@@ -220,28 +266,31 @@ function useCommerce() {
|
|
|
220
266
|
* Returns the context for a mass-action extension point: the selected row IDs the action was
|
|
221
267
|
* triggered with. The value is read from the host-provided Commerce context.
|
|
222
268
|
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
269
|
+
* Returns an error outside the Commerce shared context, or when the mass-action selection is
|
|
270
|
+
* missing, empty, or contains a non-string row ID.
|
|
225
271
|
*/
|
|
226
272
|
function useMassActionContext() {
|
|
227
|
-
const {
|
|
273
|
+
const { data, error: contextError } = useSharedContext();
|
|
228
274
|
return (0, react.useMemo)(() => {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
return
|
|
232
|
-
|
|
275
|
+
if (contextError) return error(contextError.message, { cause: contextError });
|
|
276
|
+
const selectedIds = data.sharedContext.get("selectedIds");
|
|
277
|
+
if (!Array.isArray(selectedIds)) return error("Could not find `selectedIds` in the Commerce shared context. Is this frame running as a mass-action extension point?");
|
|
278
|
+
if (selectedIds.length === 0) return error("No rows selected. A mass-action extension point must be triggered with at least one selected row.");
|
|
279
|
+
if (selectedIds.some((id) => typeof id !== "string")) return error("Some of the `selectedIds` in the Commerce shared context are not strings. All selected row IDs must be strings.");
|
|
280
|
+
return ok({ selectedIds });
|
|
281
|
+
}, [data, contextError]);
|
|
233
282
|
}
|
|
234
283
|
/**
|
|
235
284
|
* Returns the context for an order view-button extension point: the order ID the button was
|
|
236
285
|
* triggered from.
|
|
237
286
|
*
|
|
238
|
-
*
|
|
287
|
+
* Returns an error when no order ID is present in the page URL.
|
|
239
288
|
*/
|
|
240
289
|
function useOrderViewButtonContext() {
|
|
241
290
|
return (0, react.useMemo)(() => {
|
|
242
291
|
const orderId = parseOrderId(globalThis.location.href);
|
|
243
|
-
if (orderId === null)
|
|
244
|
-
return { orderId };
|
|
292
|
+
if (orderId === null) return error("Could not find an order ID. Is this frame running as an order view-button extension point?");
|
|
293
|
+
return ok({ orderId });
|
|
245
294
|
}, []);
|
|
246
295
|
}
|
|
247
296
|
|
|
@@ -250,51 +299,39 @@ function useOrderViewButtonContext() {
|
|
|
250
299
|
/**
|
|
251
300
|
* Returns typed helpers for interacting with the Commerce Admin host.
|
|
252
301
|
*
|
|
253
|
-
* @throws If called before the guest connection is established, or when the host frame actions
|
|
254
|
-
* are unavailable.
|
|
255
|
-
*
|
|
256
302
|
* @example
|
|
257
303
|
* ```tsx
|
|
258
304
|
* import { useHostConnection } from "@adobe/aio-commerce-lib-admin-ui/web";
|
|
259
305
|
*
|
|
260
306
|
* function DoneButton() {
|
|
261
|
-
* const {
|
|
262
|
-
*
|
|
307
|
+
* const { actions, error } = useHostConnection();
|
|
308
|
+
* if (error) return null;
|
|
309
|
+
* return <button onClick={actions.close}>Done</button>;
|
|
263
310
|
* }
|
|
264
311
|
* ```
|
|
265
312
|
*/
|
|
266
313
|
function useHostConnection() {
|
|
267
|
-
const {
|
|
314
|
+
const { data, error: contextError } = useSharedContext();
|
|
268
315
|
return (0, react.useMemo)(() => {
|
|
269
|
-
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
};
|
|
278
|
-
}, [host]);
|
|
316
|
+
if (contextError) return actionsError(contextError.message, { cause: contextError });
|
|
317
|
+
const { field } = data.host;
|
|
318
|
+
if (!field) return actionsError("Host frame actions are unavailable. They require an established guest connection with a host that exposes frame actions.");
|
|
319
|
+
return okActions({
|
|
320
|
+
close: () => field.close(),
|
|
321
|
+
closeWithError: () => field.onError()
|
|
322
|
+
});
|
|
323
|
+
}, [data, contextError]);
|
|
279
324
|
}
|
|
280
325
|
|
|
281
326
|
//#endregion
|
|
282
327
|
//#region source/web/react/routing/lib.ts
|
|
283
328
|
const HASH_ROUTE_PREFIX_PATTERN = /^[#/]+/u;
|
|
284
329
|
/**
|
|
285
|
-
* Determines if a given route is an index route.
|
|
286
|
-
* @param route The route to check.
|
|
287
|
-
*/
|
|
288
|
-
function isIndexRoute(route) {
|
|
289
|
-
return "index" in route && route.index;
|
|
290
|
-
}
|
|
291
|
-
/**
|
|
292
330
|
* Returns the path for a given route, removing any leading hash or slash characters.
|
|
293
331
|
* @param route The route to get the path for.
|
|
294
332
|
*/
|
|
295
333
|
function getRoutePath(route) {
|
|
296
|
-
|
|
297
|
-
return route.path.replace(HASH_ROUTE_PREFIX_PATTERN, "");
|
|
334
|
+
return route.path.replace(HASH_ROUTE_PREFIX_PATTERN, "") || "/";
|
|
298
335
|
}
|
|
299
336
|
/**
|
|
300
337
|
* Returns the "to" path for a given route, removing any leading hash or slash characters.
|
|
@@ -436,7 +473,7 @@ function useGuestConnection(extensionId) {
|
|
|
436
473
|
}
|
|
437
474
|
/** Drops a failed connection for `extensionId`, so a later render re-attaches. */
|
|
438
475
|
function retryGuestConnection(extensionId) {
|
|
439
|
-
guestConnections.
|
|
476
|
+
guestConnections.evictIfFailed(extensionId);
|
|
440
477
|
}
|
|
441
478
|
|
|
442
479
|
//#endregion
|
|
@@ -627,9 +664,10 @@ function CommerceGuestConnection(props) {
|
|
|
627
664
|
}
|
|
628
665
|
/** Renders IMS-gated route content for a Commerce Admin UI frame. */
|
|
629
666
|
function CommerceExtensionContent() {
|
|
630
|
-
const {
|
|
667
|
+
const { data, error } = useSharedContext();
|
|
668
|
+
if (error) throw error;
|
|
631
669
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ImsContextProvider, {
|
|
632
|
-
credentials: resolveCommerceImsCredentials(sharedContext),
|
|
670
|
+
credentials: resolveCommerceImsCredentials(data.sharedContext),
|
|
633
671
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_tanstack_react_router.Outlet, {})
|
|
634
672
|
});
|
|
635
673
|
}
|
|
@@ -785,11 +823,12 @@ function Entrypoint(props) {
|
|
|
785
823
|
*
|
|
786
824
|
* createExtensionApp({
|
|
787
825
|
* metadata: { extensionId: "my-extension-id" },
|
|
788
|
-
*
|
|
826
|
+
* menu: <MainPage />,
|
|
789
827
|
* });
|
|
790
828
|
* ```
|
|
791
829
|
*/
|
|
792
|
-
function createExtensionApp({ metadata, routes, root: customRoot }) {
|
|
830
|
+
function createExtensionApp({ menu, metadata, routes = [], root: customRoot }) {
|
|
831
|
+
if (menu !== void 0 && routes.some((route) => getRouteTo(route.path) === "/")) throw new Error("The \"/\" route is reserved for the menu. Pass its element through the menu option instead.");
|
|
793
832
|
const rootElement = customRoot ?? document.getElementById("root");
|
|
794
833
|
if (!rootElement) throw new Error("Could not find an element with id \"root\".");
|
|
795
834
|
const root = (0, react_dom_client.createRoot)(rootElement);
|
|
@@ -798,7 +837,10 @@ function createExtensionApp({ metadata, routes, root: customRoot }) {
|
|
|
798
837
|
extensionId: metadata.extensionId,
|
|
799
838
|
initialConfigurationPromise,
|
|
800
839
|
runtime
|
|
801
|
-
}), routes
|
|
840
|
+
}), menu === void 0 ? routes : [{
|
|
841
|
+
element: menu,
|
|
842
|
+
path: "/"
|
|
843
|
+
}, ...routes]);
|
|
802
844
|
root.render(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.StrictMode, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_tanstack_react_router.RouterProvider, { router }) }));
|
|
803
845
|
};
|
|
804
846
|
try {
|