@omni-co/embed 0.6.0 → 0.8.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.
@@ -1,4 +1,4 @@
1
- import { EmbedSsoContentDiscoveryProps, EmbedSsoDashboardProps, EmbedSsoWorkbookProps } from "./types";
1
+ import { EmbedSsoContentDiscoveryProps, EmbedSsoDashboardProps, EmbedSsoWorkbookProps, GenerateEmbedSsoSessionProps, RedeemEmbedSsoSessionProps } from "./types";
2
2
  export declare const embedSsoDashboard: (props: EmbedSsoDashboardProps) => Promise<string>;
3
3
  export declare const embedSsoWorkbook: (props: EmbedSsoWorkbookProps) => Promise<string>;
4
4
  /**
@@ -7,3 +7,26 @@ export declare const embedSsoWorkbook: (props: EmbedSsoWorkbookProps) => Promise
7
7
  * @returns Promise wrapped around a generated embed login url.
8
8
  */
9
9
  export declare const embedSsoContentDiscovery: (props: EmbedSsoContentDiscoveryProps) => Promise<string>;
10
+ /**
11
+ * First step of the 2-step SSO embed login flow. If successful, outputs an object that
12
+ * includes an SSO embed session token. If unsuccessful, outputs an object that includes
13
+ * an error message.
14
+ *
15
+ * To elaborate, this function upserts an SSO embed user and session to the specified Omni
16
+ * organization / host. The returned session token corresponds to the generated session.
17
+ */
18
+ export declare const createSessionToken: ({ apiKey, domain, host, organizationName, port, ...restProps }: GenerateEmbedSsoSessionProps) => Promise<{
19
+ success: true;
20
+ sessionToken: string;
21
+ error?: never;
22
+ } | {
23
+ success: false;
24
+ sessionToken?: never;
25
+ error: string;
26
+ }>;
27
+ /**
28
+ * Second step of the 2-step SSO embed login flow. Uses a generated session token
29
+ * to output an session redemption URL that can be used to display embedded content.
30
+ * The returned URL should be passed into an `iframe`'s src attribute.
31
+ */
32
+ export declare const redeemSessionToken: ({ domain, host, organizationName, port, nonce: propsNonce, secret, prefersDark, sessionToken: sessionId, theme, }: RedeemEmbedSsoSessionProps) => Promise<string>;
package/lib/cjs/embed.js CHANGED
@@ -1,13 +1,29 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.embedSsoContentDiscovery = exports.embedSsoWorkbook = exports.embedSsoDashboard = void 0;
3
+ exports.redeemSessionToken = exports.createSessionToken = exports.embedSsoContentDiscovery = exports.embedSsoWorkbook = exports.embedSsoDashboard = void 0;
4
4
  const random_str_1 = require("./random-str");
5
5
  const signature_1 = require("./signature");
6
6
  const types_1 = require("./types");
7
7
  const defaultEmbedDomain = "embed-omniapp.co";
8
8
  const embedLoginPath = "/embed/login";
9
+ const generateSsoEmbedSessionPath = "/api/unstable/embed/sso/generate-session";
10
+ const redeemSsoEmbedSessionPath = "/embed/sso/redeem-session";
11
+ const createEmbedRequestUrl = (props) => {
12
+ let host, domain, organizationName;
13
+ if ("host" in props)
14
+ host = props.host;
15
+ if ("organizationName" in props) {
16
+ domain = props.domain;
17
+ organizationName = props.organizationName;
18
+ }
19
+ // Generate the url for the embed login route
20
+ if (!host)
21
+ host = `${organizationName}.${domain !== null && domain !== void 0 ? domain : defaultEmbedDomain}`;
22
+ const url = new URL(props.requestPath, `https://${host}${props.port ? `:${props.port}` : ""}`);
23
+ return url;
24
+ };
9
25
  const validateProps = (props) => {
10
- const { externalId, name, nonce, secret, userAttributes, customTheme, customThemeId, } = props;
26
+ const { externalId, name, nonce, secret, uiSettings, userAttributes, customTheme, customThemeId, } = props;
11
27
  let host, domain, organizationName;
12
28
  if ("host" in props)
13
29
  host = props.host;
@@ -31,29 +47,28 @@ const validateProps = (props) => {
31
47
  console.warn("`domain` is deprecated and will be removed in a future v1.0.0 release. Use `host` to specify your vanity domain instead (eg `host: 'omni.my-company.com'`).");
32
48
  if (nonce && nonce.length !== 32)
33
49
  throw new Error("nonce must be 32 characters");
50
+ if (typeof uiSettings !== "object" && uiSettings)
51
+ throw new Error("uiSettings must be an object");
34
52
  if (typeof userAttributes !== "object" && userAttributes)
35
53
  throw new Error("userAttributes must be an object");
36
54
  };
37
55
  const embedSsoContent = async (props) => {
38
56
  validateProps(props);
39
- let { accessBoost, connectionRoles: rawConnectionRoles, contentPath, customTheme: rawCustomTheme, customThemeId, email, externalId, filterSearchParam, linkAccess, mode, name, nonce, port, secret, userAttributes: rawUserAttributes, entity, prefersDark, theme, entityFolderContentRole, groups: rawGroups, } = props;
40
- let host, domain, organizationName;
41
- if ("host" in props)
42
- host = props.host;
43
- if ("organizationName" in props) {
44
- domain = props.domain;
45
- organizationName = props.organizationName;
46
- }
57
+ let { accessBoost, connectionRoles: rawConnectionRoles, contentPath, customTheme: rawCustomTheme, customThemeId, domain, email, externalId, filterSearchParam, host, linkAccess, mode, name, nonce, organizationName, port, secret, uiSettings: rawUiSettings, userAttributes: rawUserAttributes, entity, prefersDark, theme, entityFolderContentRole, groups: rawGroups, } = props;
47
58
  // Handle defaults
48
59
  nonce = nonce !== null && nonce !== void 0 ? nonce : (await (0, random_str_1.random32ByteString)());
60
+ const uiSettings = rawUiSettings && JSON.stringify(rawUiSettings);
49
61
  const userAttributes = rawUserAttributes && JSON.stringify(rawUserAttributes);
50
62
  const customTheme = rawCustomTheme && JSON.stringify(rawCustomTheme);
51
63
  const connectionRoles = rawConnectionRoles && JSON.stringify(rawConnectionRoles);
52
64
  const groups = rawGroups && JSON.stringify(rawGroups);
53
- // Generate the url for the embed login route
54
- if (!host)
55
- host = `${organizationName}.${domain !== null && domain !== void 0 ? domain : defaultEmbedDomain}`;
56
- const url = new URL(embedLoginPath, `https://${host}${port ? `:${port}` : ""}`);
65
+ const url = createEmbedRequestUrl({
66
+ domain,
67
+ host,
68
+ organizationName,
69
+ port,
70
+ requestPath: embedLoginPath,
71
+ });
57
72
  // Get a signature for the request
58
73
  const signature = (0, signature_1.getSignature)({
59
74
  // Required parameters
@@ -77,6 +92,7 @@ const embedSsoContent = async (props) => {
77
92
  mode,
78
93
  prefersDark,
79
94
  theme,
95
+ uiSettings,
80
96
  userAttributes,
81
97
  });
82
98
  // Build and return the signed url
@@ -103,6 +119,7 @@ const embedSsoContent = async (props) => {
103
119
  url.searchParams.append("entityFolderContentRole", entityFolderContentRole);
104
120
  groups && url.searchParams.append("groups", groups);
105
121
  email && url.searchParams.append("email", email);
122
+ uiSettings && url.searchParams.append("uiSettings", uiSettings);
106
123
  return url.toString();
107
124
  };
108
125
  const buildContentPath = (contentType, contentId) => `/${contentType}/${contentId}`;
@@ -124,10 +141,82 @@ exports.embedSsoWorkbook = embedSsoWorkbook;
124
141
  const embedSsoContentDiscovery = async (props) => {
125
142
  // Entity paths for the Content Discovery embed no longer require stripping the leading `/`.
126
143
  // This check maintains backwards compatibility with the old behavior.
127
- const contentPath = props.path.startsWith('/') ? props.path : `/${props.path}`;
144
+ const contentPath = props.path.startsWith("/")
145
+ ? props.path
146
+ : `/${props.path}`;
128
147
  return embedSsoContent({
129
148
  ...props,
130
149
  contentPath,
131
150
  });
132
151
  };
133
152
  exports.embedSsoContentDiscovery = embedSsoContentDiscovery;
153
+ /**
154
+ * First step of the 2-step SSO embed login flow. If successful, outputs an object that
155
+ * includes an SSO embed session token. If unsuccessful, outputs an object that includes
156
+ * an error message.
157
+ *
158
+ * To elaborate, this function upserts an SSO embed user and session to the specified Omni
159
+ * organization / host. The returned session token corresponds to the generated session.
160
+ */
161
+ const createSessionToken = async ({ apiKey, domain, host, organizationName, port, ...restProps }) => {
162
+ const generateRequestUrl = createEmbedRequestUrl({
163
+ domain,
164
+ host,
165
+ organizationName,
166
+ port,
167
+ requestPath: generateSsoEmbedSessionPath,
168
+ });
169
+ // Hit the endpoint with the endpoint params + signature
170
+ const fetchResponse = await fetch(generateRequestUrl, {
171
+ method: "POST",
172
+ body: JSON.stringify({
173
+ ...restProps,
174
+ }),
175
+ headers: {
176
+ Authorization: `Bearer ${apiKey}`,
177
+ "Content-type": "application/json",
178
+ },
179
+ });
180
+ if (!fetchResponse.ok)
181
+ return {
182
+ error: `Failed to generate embed session: ${fetchResponse.statusText}`,
183
+ success: false,
184
+ };
185
+ const fetchData = await fetchResponse.json();
186
+ if (!fetchData.sessionId)
187
+ return { error: "No session ID returned from endpoint.", success: false };
188
+ return { sessionToken: fetchData.sessionId, success: true };
189
+ };
190
+ exports.createSessionToken = createSessionToken;
191
+ /**
192
+ * Second step of the 2-step SSO embed login flow. Uses a generated session token
193
+ * to output an session redemption URL that can be used to display embedded content.
194
+ * The returned URL should be passed into an `iframe`'s src attribute.
195
+ */
196
+ const redeemSessionToken = async ({ domain, host, organizationName, port, nonce: propsNonce, secret, prefersDark, sessionToken: sessionId, // sessionToken is just a facade for the sessionId
197
+ theme, }) => {
198
+ const redeemRequestUrl = createEmbedRequestUrl({
199
+ domain,
200
+ host,
201
+ organizationName,
202
+ port,
203
+ requestPath: redeemSsoEmbedSessionPath,
204
+ });
205
+ const nonce = propsNonce !== null && propsNonce !== void 0 ? propsNonce : (await (0, random_str_1.random32ByteString)());
206
+ const signature = (0, signature_1.signSessionRedemption)({
207
+ requestUrl: redeemRequestUrl.toString(),
208
+ nonce,
209
+ prefersDark,
210
+ sessionId,
211
+ secret,
212
+ theme,
213
+ });
214
+ prefersDark !== undefined &&
215
+ redeemRequestUrl.searchParams.append("prefersDark", prefersDark);
216
+ theme !== undefined && redeemRequestUrl.searchParams.append("theme", theme);
217
+ redeemRequestUrl.searchParams.append("nonce", nonce);
218
+ redeemRequestUrl.searchParams.append("sessionId", sessionId);
219
+ redeemRequestUrl.searchParams.append("signature", signature);
220
+ return redeemRequestUrl.toString();
221
+ };
222
+ exports.redeemSessionToken = redeemSessionToken;
@@ -82,6 +82,10 @@ type SignatureProps = {
82
82
  * The theme of the embed.
83
83
  */
84
84
  theme?: string;
85
+ /**
86
+ * The UI settings for the embedded content.
87
+ */
88
+ uiSettings?: string;
85
89
  /**
86
90
  * The user attributes to be sent with the request. Expected to be a stringified JSON object.
87
91
  *
@@ -106,7 +110,7 @@ type GenerateSessionSignatureProps = Omit<SignatureProps, "loginUrl" | "prefersD
106
110
  * validated by the Omni app server to ensure the request is authentic.
107
111
  */
108
112
  export declare const signSessionRequest: ({ requestUrl, secret, ...props }: GenerateSessionSignatureProps) => string;
109
- type UseSessionSignatureProps = Pick<SignatureProps, "nonce" | "prefersDark" | "theme" | "secret"> & RequestUrl & {
113
+ type SignSessionRedemptionProps = Pick<SignatureProps, "nonce" | "prefersDark" | "theme" | "secret"> & RequestUrl & {
110
114
  sessionId: string;
111
115
  };
112
116
  /**
@@ -117,8 +121,8 @@ type UseSessionSignatureProps = Pick<SignatureProps, "nonce" | "prefersDark" | "
117
121
  * that is generated based on all other request parameters. That signature is
118
122
  * validated by the Omni app server to ensure the request is authentic.
119
123
  */
120
- export declare const signSessionRedemption: ({ requestUrl, nonce, sessionId, secret, prefersDark, theme, }: UseSessionSignatureProps) => string;
124
+ export declare const signSessionRedemption: ({ requestUrl, nonce, sessionId, secret, prefersDark, theme, }: SignSessionRedemptionProps) => string;
121
125
  export declare const TEST_ONLY: {
122
- generateStringForSignature: ({ loginUrl, contentPath, externalId, name, nonce, accessBoost, connectionRoles, customTheme, customThemeId, email, entity, entityFolderContentRole, filterSearchParam, groups, linkAccess, mode, prefersDark, theme, userAttributes, }: Omit<SignatureProps, "secret">) => string;
126
+ generateStringForSignature: ({ loginUrl, contentPath, externalId, name, nonce, accessBoost, connectionRoles, customTheme, customThemeId, email, entity, entityFolderContentRole, filterSearchParam, groups, linkAccess, mode, prefersDark, theme, uiSettings, userAttributes, }: Omit<SignatureProps, "secret">) => string;
123
127
  };
124
128
  export {};
@@ -17,7 +17,7 @@ const generateStringForSignature = ({
17
17
  // Required parameters
18
18
  loginUrl, contentPath, externalId, name, nonce,
19
19
  // Optional parameters
20
- accessBoost, connectionRoles, customTheme, customThemeId, email, entity, entityFolderContentRole, filterSearchParam, groups, linkAccess, mode, prefersDark, theme, userAttributes, }) => {
20
+ accessBoost, connectionRoles, customTheme, customThemeId, email, entity, entityFolderContentRole, filterSearchParam, groups, linkAccess, mode, prefersDark, theme, uiSettings, userAttributes, }) => {
21
21
  // We create a map of optional params and convert it to an array
22
22
  // to ensure that the order of the optional params is alphabetical and consistent.
23
23
  const optionalParamsMap = {
@@ -34,6 +34,7 @@ accessBoost, connectionRoles, customTheme, customThemeId, email, entity, entityF
34
34
  ...(mode && { mode }),
35
35
  ...(prefersDark && { prefersDark }),
36
36
  ...(theme && { theme }),
37
+ ...(uiSettings && { uiSettings }),
37
38
  ...(userAttributes && { userAttributes }),
38
39
  };
39
40
  const optionalParams = Object.keys(optionalParamsMap)
@@ -3,6 +3,35 @@ export declare enum EmbedSessionMode {
3
3
  Application = "APPLICATION",
4
4
  SingleContent = "SINGLE_CONTENT"
5
5
  }
6
+ type HostProps = {
7
+ host?: never;
8
+ /**
9
+ * @deprecated Introduced for internal testing only. For vanity domains, use the `host` prop instead.
10
+ *
11
+ * @throws Error if `host` is provided.
12
+ */
13
+ domain?: string;
14
+ /**
15
+ * Name of the organization the content belongs to. If provided, generates a default embed host
16
+ * URL in the form of `https://<organizationName>.embed-omniapp.co`.
17
+ *
18
+ * @throws Error if `host` is provided.
19
+ */
20
+ organizationName: string;
21
+ } | {
22
+ domain?: never;
23
+ organizationName?: never;
24
+ /**
25
+ * Used to set the host of signed embed URL, required when using vanity domains.
26
+ * Protocol is not required, as https is assumed.
27
+ * Port is not accepted. If required, use the `port` prop.
28
+ *
29
+ * @throws Error if `domain` or `organizationName` are provided.
30
+ * @example "omni.example.com"
31
+ * @example "omni.another-example.app"
32
+ */
33
+ host: string;
34
+ };
6
35
  type EmbedSsoBaseProps = {
7
36
  externalId: string;
8
37
  name: string;
@@ -43,37 +72,10 @@ type EmbedSsoBaseProps = {
43
72
  email?: string;
44
73
  filterSearchParam?: string;
45
74
  linkAccess?: string;
75
+ uiSettings?: Record<EmbedUiSettings, boolean>;
46
76
  nonce?: string;
47
77
  port?: number;
48
- } & ({
49
- host?: never;
50
- /**
51
- * @deprecated Introduced for internal testing only. For vanity domains, use the `host` prop instead.
52
- *
53
- * @throws Error if `host` is provided.
54
- */
55
- domain?: string;
56
- /**
57
- * Name of the organization the content belongs to. If provided, generates a default embed host
58
- * URL in the form of `https://<organizationName>.embed-omniapp.co`.
59
- *
60
- * @throws Error if `host` is provided.
61
- */
62
- organizationName: string;
63
- } | {
64
- domain?: never;
65
- organizationName?: never;
66
- /**
67
- * Used to set the host of signed embed URL, required when using vanity domains.
68
- * Protocol is not required, as https is assumed.
69
- * Port is not accepted. If required, use the `port` prop.
70
- *
71
- * @throws Error if `domain` or `organizationName` are provided.
72
- * @example "omni.example.com"
73
- * @example "omni.another-example.app"
74
- */
75
- host: string;
76
- });
78
+ } & HostProps;
77
79
  type EmbedSsoDashboardOnlyProps = {
78
80
  accessBoost?: boolean;
79
81
  };
@@ -201,6 +203,16 @@ export declare enum EmbedEntityFolderContentRoles {
201
203
  EDITOR = "EDITOR",
202
204
  VIEWER = "VIEWER"
203
205
  }
206
+ /**
207
+ * UI settings used as keys of the uiSettings sso embed parameter.
208
+ */
209
+ export declare enum EmbedUiSettings {
210
+ /**
211
+ * When false, hides all in-app navigation elements. Note that in-app navigation
212
+ * is only visible when the embed session is in APPLICATION mode.
213
+ */
214
+ SHOW_NAVIGATION = "showNavigation"
215
+ }
204
216
  export type EmbedSsoContentProps = EmbedSsoBaseProps & EmbedSsoDashboardOnlyProps & EmbedContentPathProp;
205
217
  /**
206
218
  * Consumers of this SDK will rely on these three types to embed workbooks and dashboards.
@@ -218,4 +230,10 @@ export type EmbedSsoContentDiscoveryProps = EmbedSsoBaseProps & {
218
230
  */
219
231
  path: string;
220
232
  };
233
+ export type GenerateEmbedSsoSessionProps = Omit<EmbedSsoContentProps, "prefersDark" | "theme" | "secret" | "nonce"> & {
234
+ apiKey: string;
235
+ } & HostProps;
236
+ export type RedeemEmbedSsoSessionProps = Pick<EmbedSsoContentProps, "nonce" | "prefersDark" | "secret" | "theme" | "port"> & {
237
+ sessionToken: string;
238
+ } & HostProps;
221
239
  export {};
package/lib/cjs/types.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.EmbedEntityFolderContentRoles = exports.EmbedConnectionRoles = exports.EmbeddedContent = exports.CustomThemeProperty = exports.EmbedSessionMode = void 0;
3
+ exports.EmbedUiSettings = exports.EmbedEntityFolderContentRoles = exports.EmbedConnectionRoles = exports.EmbeddedContent = exports.CustomThemeProperty = exports.EmbedSessionMode = void 0;
4
4
  var EmbedSessionMode;
5
5
  (function (EmbedSessionMode) {
6
6
  EmbedSessionMode["Application"] = "APPLICATION";
@@ -83,3 +83,14 @@ var EmbedEntityFolderContentRoles;
83
83
  EmbedEntityFolderContentRoles["EDITOR"] = "EDITOR";
84
84
  EmbedEntityFolderContentRoles["VIEWER"] = "VIEWER";
85
85
  })(EmbedEntityFolderContentRoles || (exports.EmbedEntityFolderContentRoles = EmbedEntityFolderContentRoles = {}));
86
+ /**
87
+ * UI settings used as keys of the uiSettings sso embed parameter.
88
+ */
89
+ var EmbedUiSettings;
90
+ (function (EmbedUiSettings) {
91
+ /**
92
+ * When false, hides all in-app navigation elements. Note that in-app navigation
93
+ * is only visible when the embed session is in APPLICATION mode.
94
+ */
95
+ EmbedUiSettings["SHOW_NAVIGATION"] = "showNavigation";
96
+ })(EmbedUiSettings || (exports.EmbedUiSettings = EmbedUiSettings = {}));
@@ -1,4 +1,4 @@
1
- import { EmbedSsoContentDiscoveryProps, EmbedSsoDashboardProps, EmbedSsoWorkbookProps } from "./types";
1
+ import { EmbedSsoContentDiscoveryProps, EmbedSsoDashboardProps, EmbedSsoWorkbookProps, GenerateEmbedSsoSessionProps, RedeemEmbedSsoSessionProps } from "./types";
2
2
  export declare const embedSsoDashboard: (props: EmbedSsoDashboardProps) => Promise<string>;
3
3
  export declare const embedSsoWorkbook: (props: EmbedSsoWorkbookProps) => Promise<string>;
4
4
  /**
@@ -7,3 +7,26 @@ export declare const embedSsoWorkbook: (props: EmbedSsoWorkbookProps) => Promise
7
7
  * @returns Promise wrapped around a generated embed login url.
8
8
  */
9
9
  export declare const embedSsoContentDiscovery: (props: EmbedSsoContentDiscoveryProps) => Promise<string>;
10
+ /**
11
+ * First step of the 2-step SSO embed login flow. If successful, outputs an object that
12
+ * includes an SSO embed session token. If unsuccessful, outputs an object that includes
13
+ * an error message.
14
+ *
15
+ * To elaborate, this function upserts an SSO embed user and session to the specified Omni
16
+ * organization / host. The returned session token corresponds to the generated session.
17
+ */
18
+ export declare const createSessionToken: ({ apiKey, domain, host, organizationName, port, ...restProps }: GenerateEmbedSsoSessionProps) => Promise<{
19
+ success: true;
20
+ sessionToken: string;
21
+ error?: never;
22
+ } | {
23
+ success: false;
24
+ sessionToken?: never;
25
+ error: string;
26
+ }>;
27
+ /**
28
+ * Second step of the 2-step SSO embed login flow. Uses a generated session token
29
+ * to output an session redemption URL that can be used to display embedded content.
30
+ * The returned URL should be passed into an `iframe`'s src attribute.
31
+ */
32
+ export declare const redeemSessionToken: ({ domain, host, organizationName, port, nonce: propsNonce, secret, prefersDark, sessionToken: sessionId, theme, }: RedeemEmbedSsoSessionProps) => Promise<string>;
package/lib/esm/embed.js CHANGED
@@ -1,10 +1,26 @@
1
1
  import { random32ByteString } from "./random-str";
2
- import { getSignature } from "./signature";
2
+ import { getSignature, signSessionRedemption } from "./signature";
3
3
  import { EmbeddedContent, } from "./types";
4
4
  const defaultEmbedDomain = "embed-omniapp.co";
5
5
  const embedLoginPath = "/embed/login";
6
+ const generateSsoEmbedSessionPath = "/api/unstable/embed/sso/generate-session";
7
+ const redeemSsoEmbedSessionPath = "/embed/sso/redeem-session";
8
+ const createEmbedRequestUrl = (props) => {
9
+ let host, domain, organizationName;
10
+ if ("host" in props)
11
+ host = props.host;
12
+ if ("organizationName" in props) {
13
+ domain = props.domain;
14
+ organizationName = props.organizationName;
15
+ }
16
+ // Generate the url for the embed login route
17
+ if (!host)
18
+ host = `${organizationName}.${domain !== null && domain !== void 0 ? domain : defaultEmbedDomain}`;
19
+ const url = new URL(props.requestPath, `https://${host}${props.port ? `:${props.port}` : ""}`);
20
+ return url;
21
+ };
6
22
  const validateProps = (props) => {
7
- const { externalId, name, nonce, secret, userAttributes, customTheme, customThemeId, } = props;
23
+ const { externalId, name, nonce, secret, uiSettings, userAttributes, customTheme, customThemeId, } = props;
8
24
  let host, domain, organizationName;
9
25
  if ("host" in props)
10
26
  host = props.host;
@@ -28,29 +44,28 @@ const validateProps = (props) => {
28
44
  console.warn("`domain` is deprecated and will be removed in a future v1.0.0 release. Use `host` to specify your vanity domain instead (eg `host: 'omni.my-company.com'`).");
29
45
  if (nonce && nonce.length !== 32)
30
46
  throw new Error("nonce must be 32 characters");
47
+ if (typeof uiSettings !== "object" && uiSettings)
48
+ throw new Error("uiSettings must be an object");
31
49
  if (typeof userAttributes !== "object" && userAttributes)
32
50
  throw new Error("userAttributes must be an object");
33
51
  };
34
52
  const embedSsoContent = async (props) => {
35
53
  validateProps(props);
36
- let { accessBoost, connectionRoles: rawConnectionRoles, contentPath, customTheme: rawCustomTheme, customThemeId, email, externalId, filterSearchParam, linkAccess, mode, name, nonce, port, secret, userAttributes: rawUserAttributes, entity, prefersDark, theme, entityFolderContentRole, groups: rawGroups, } = props;
37
- let host, domain, organizationName;
38
- if ("host" in props)
39
- host = props.host;
40
- if ("organizationName" in props) {
41
- domain = props.domain;
42
- organizationName = props.organizationName;
43
- }
54
+ let { accessBoost, connectionRoles: rawConnectionRoles, contentPath, customTheme: rawCustomTheme, customThemeId, domain, email, externalId, filterSearchParam, host, linkAccess, mode, name, nonce, organizationName, port, secret, uiSettings: rawUiSettings, userAttributes: rawUserAttributes, entity, prefersDark, theme, entityFolderContentRole, groups: rawGroups, } = props;
44
55
  // Handle defaults
45
56
  nonce = nonce !== null && nonce !== void 0 ? nonce : (await random32ByteString());
57
+ const uiSettings = rawUiSettings && JSON.stringify(rawUiSettings);
46
58
  const userAttributes = rawUserAttributes && JSON.stringify(rawUserAttributes);
47
59
  const customTheme = rawCustomTheme && JSON.stringify(rawCustomTheme);
48
60
  const connectionRoles = rawConnectionRoles && JSON.stringify(rawConnectionRoles);
49
61
  const groups = rawGroups && JSON.stringify(rawGroups);
50
- // Generate the url for the embed login route
51
- if (!host)
52
- host = `${organizationName}.${domain !== null && domain !== void 0 ? domain : defaultEmbedDomain}`;
53
- const url = new URL(embedLoginPath, `https://${host}${port ? `:${port}` : ""}`);
62
+ const url = createEmbedRequestUrl({
63
+ domain,
64
+ host,
65
+ organizationName,
66
+ port,
67
+ requestPath: embedLoginPath,
68
+ });
54
69
  // Get a signature for the request
55
70
  const signature = getSignature({
56
71
  // Required parameters
@@ -74,6 +89,7 @@ const embedSsoContent = async (props) => {
74
89
  mode,
75
90
  prefersDark,
76
91
  theme,
92
+ uiSettings,
77
93
  userAttributes,
78
94
  });
79
95
  // Build and return the signed url
@@ -100,6 +116,7 @@ const embedSsoContent = async (props) => {
100
116
  url.searchParams.append("entityFolderContentRole", entityFolderContentRole);
101
117
  groups && url.searchParams.append("groups", groups);
102
118
  email && url.searchParams.append("email", email);
119
+ uiSettings && url.searchParams.append("uiSettings", uiSettings);
103
120
  return url.toString();
104
121
  };
105
122
  const buildContentPath = (contentType, contentId) => `/${contentType}/${contentId}`;
@@ -119,9 +136,79 @@ export const embedSsoWorkbook = async (props) => {
119
136
  export const embedSsoContentDiscovery = async (props) => {
120
137
  // Entity paths for the Content Discovery embed no longer require stripping the leading `/`.
121
138
  // This check maintains backwards compatibility with the old behavior.
122
- const contentPath = props.path.startsWith('/') ? props.path : `/${props.path}`;
139
+ const contentPath = props.path.startsWith("/")
140
+ ? props.path
141
+ : `/${props.path}`;
123
142
  return embedSsoContent({
124
143
  ...props,
125
144
  contentPath,
126
145
  });
127
146
  };
147
+ /**
148
+ * First step of the 2-step SSO embed login flow. If successful, outputs an object that
149
+ * includes an SSO embed session token. If unsuccessful, outputs an object that includes
150
+ * an error message.
151
+ *
152
+ * To elaborate, this function upserts an SSO embed user and session to the specified Omni
153
+ * organization / host. The returned session token corresponds to the generated session.
154
+ */
155
+ export const createSessionToken = async ({ apiKey, domain, host, organizationName, port, ...restProps }) => {
156
+ const generateRequestUrl = createEmbedRequestUrl({
157
+ domain,
158
+ host,
159
+ organizationName,
160
+ port,
161
+ requestPath: generateSsoEmbedSessionPath,
162
+ });
163
+ // Hit the endpoint with the endpoint params + signature
164
+ const fetchResponse = await fetch(generateRequestUrl, {
165
+ method: "POST",
166
+ body: JSON.stringify({
167
+ ...restProps,
168
+ }),
169
+ headers: {
170
+ Authorization: `Bearer ${apiKey}`,
171
+ "Content-type": "application/json",
172
+ },
173
+ });
174
+ if (!fetchResponse.ok)
175
+ return {
176
+ error: `Failed to generate embed session: ${fetchResponse.statusText}`,
177
+ success: false,
178
+ };
179
+ const fetchData = await fetchResponse.json();
180
+ if (!fetchData.sessionId)
181
+ return { error: "No session ID returned from endpoint.", success: false };
182
+ return { sessionToken: fetchData.sessionId, success: true };
183
+ };
184
+ /**
185
+ * Second step of the 2-step SSO embed login flow. Uses a generated session token
186
+ * to output an session redemption URL that can be used to display embedded content.
187
+ * The returned URL should be passed into an `iframe`'s src attribute.
188
+ */
189
+ export const redeemSessionToken = async ({ domain, host, organizationName, port, nonce: propsNonce, secret, prefersDark, sessionToken: sessionId, // sessionToken is just a facade for the sessionId
190
+ theme, }) => {
191
+ const redeemRequestUrl = createEmbedRequestUrl({
192
+ domain,
193
+ host,
194
+ organizationName,
195
+ port,
196
+ requestPath: redeemSsoEmbedSessionPath,
197
+ });
198
+ const nonce = propsNonce !== null && propsNonce !== void 0 ? propsNonce : (await random32ByteString());
199
+ const signature = signSessionRedemption({
200
+ requestUrl: redeemRequestUrl.toString(),
201
+ nonce,
202
+ prefersDark,
203
+ sessionId,
204
+ secret,
205
+ theme,
206
+ });
207
+ prefersDark !== undefined &&
208
+ redeemRequestUrl.searchParams.append("prefersDark", prefersDark);
209
+ theme !== undefined && redeemRequestUrl.searchParams.append("theme", theme);
210
+ redeemRequestUrl.searchParams.append("nonce", nonce);
211
+ redeemRequestUrl.searchParams.append("sessionId", sessionId);
212
+ redeemRequestUrl.searchParams.append("signature", signature);
213
+ return redeemRequestUrl.toString();
214
+ };
@@ -82,6 +82,10 @@ type SignatureProps = {
82
82
  * The theme of the embed.
83
83
  */
84
84
  theme?: string;
85
+ /**
86
+ * The UI settings for the embedded content.
87
+ */
88
+ uiSettings?: string;
85
89
  /**
86
90
  * The user attributes to be sent with the request. Expected to be a stringified JSON object.
87
91
  *
@@ -106,7 +110,7 @@ type GenerateSessionSignatureProps = Omit<SignatureProps, "loginUrl" | "prefersD
106
110
  * validated by the Omni app server to ensure the request is authentic.
107
111
  */
108
112
  export declare const signSessionRequest: ({ requestUrl, secret, ...props }: GenerateSessionSignatureProps) => string;
109
- type UseSessionSignatureProps = Pick<SignatureProps, "nonce" | "prefersDark" | "theme" | "secret"> & RequestUrl & {
113
+ type SignSessionRedemptionProps = Pick<SignatureProps, "nonce" | "prefersDark" | "theme" | "secret"> & RequestUrl & {
110
114
  sessionId: string;
111
115
  };
112
116
  /**
@@ -117,8 +121,8 @@ type UseSessionSignatureProps = Pick<SignatureProps, "nonce" | "prefersDark" | "
117
121
  * that is generated based on all other request parameters. That signature is
118
122
  * validated by the Omni app server to ensure the request is authentic.
119
123
  */
120
- export declare const signSessionRedemption: ({ requestUrl, nonce, sessionId, secret, prefersDark, theme, }: UseSessionSignatureProps) => string;
124
+ export declare const signSessionRedemption: ({ requestUrl, nonce, sessionId, secret, prefersDark, theme, }: SignSessionRedemptionProps) => string;
121
125
  export declare const TEST_ONLY: {
122
- generateStringForSignature: ({ loginUrl, contentPath, externalId, name, nonce, accessBoost, connectionRoles, customTheme, customThemeId, email, entity, entityFolderContentRole, filterSearchParam, groups, linkAccess, mode, prefersDark, theme, userAttributes, }: Omit<SignatureProps, "secret">) => string;
126
+ generateStringForSignature: ({ loginUrl, contentPath, externalId, name, nonce, accessBoost, connectionRoles, customTheme, customThemeId, email, entity, entityFolderContentRole, filterSearchParam, groups, linkAccess, mode, prefersDark, theme, uiSettings, userAttributes, }: Omit<SignatureProps, "secret">) => string;
123
127
  };
124
128
  export {};
@@ -10,7 +10,7 @@ const generateStringForSignature = ({
10
10
  // Required parameters
11
11
  loginUrl, contentPath, externalId, name, nonce,
12
12
  // Optional parameters
13
- accessBoost, connectionRoles, customTheme, customThemeId, email, entity, entityFolderContentRole, filterSearchParam, groups, linkAccess, mode, prefersDark, theme, userAttributes, }) => {
13
+ accessBoost, connectionRoles, customTheme, customThemeId, email, entity, entityFolderContentRole, filterSearchParam, groups, linkAccess, mode, prefersDark, theme, uiSettings, userAttributes, }) => {
14
14
  // We create a map of optional params and convert it to an array
15
15
  // to ensure that the order of the optional params is alphabetical and consistent.
16
16
  const optionalParamsMap = {
@@ -27,6 +27,7 @@ accessBoost, connectionRoles, customTheme, customThemeId, email, entity, entityF
27
27
  ...(mode && { mode }),
28
28
  ...(prefersDark && { prefersDark }),
29
29
  ...(theme && { theme }),
30
+ ...(uiSettings && { uiSettings }),
30
31
  ...(userAttributes && { userAttributes }),
31
32
  };
32
33
  const optionalParams = Object.keys(optionalParamsMap)
@@ -3,6 +3,35 @@ export declare enum EmbedSessionMode {
3
3
  Application = "APPLICATION",
4
4
  SingleContent = "SINGLE_CONTENT"
5
5
  }
6
+ type HostProps = {
7
+ host?: never;
8
+ /**
9
+ * @deprecated Introduced for internal testing only. For vanity domains, use the `host` prop instead.
10
+ *
11
+ * @throws Error if `host` is provided.
12
+ */
13
+ domain?: string;
14
+ /**
15
+ * Name of the organization the content belongs to. If provided, generates a default embed host
16
+ * URL in the form of `https://<organizationName>.embed-omniapp.co`.
17
+ *
18
+ * @throws Error if `host` is provided.
19
+ */
20
+ organizationName: string;
21
+ } | {
22
+ domain?: never;
23
+ organizationName?: never;
24
+ /**
25
+ * Used to set the host of signed embed URL, required when using vanity domains.
26
+ * Protocol is not required, as https is assumed.
27
+ * Port is not accepted. If required, use the `port` prop.
28
+ *
29
+ * @throws Error if `domain` or `organizationName` are provided.
30
+ * @example "omni.example.com"
31
+ * @example "omni.another-example.app"
32
+ */
33
+ host: string;
34
+ };
6
35
  type EmbedSsoBaseProps = {
7
36
  externalId: string;
8
37
  name: string;
@@ -43,37 +72,10 @@ type EmbedSsoBaseProps = {
43
72
  email?: string;
44
73
  filterSearchParam?: string;
45
74
  linkAccess?: string;
75
+ uiSettings?: Record<EmbedUiSettings, boolean>;
46
76
  nonce?: string;
47
77
  port?: number;
48
- } & ({
49
- host?: never;
50
- /**
51
- * @deprecated Introduced for internal testing only. For vanity domains, use the `host` prop instead.
52
- *
53
- * @throws Error if `host` is provided.
54
- */
55
- domain?: string;
56
- /**
57
- * Name of the organization the content belongs to. If provided, generates a default embed host
58
- * URL in the form of `https://<organizationName>.embed-omniapp.co`.
59
- *
60
- * @throws Error if `host` is provided.
61
- */
62
- organizationName: string;
63
- } | {
64
- domain?: never;
65
- organizationName?: never;
66
- /**
67
- * Used to set the host of signed embed URL, required when using vanity domains.
68
- * Protocol is not required, as https is assumed.
69
- * Port is not accepted. If required, use the `port` prop.
70
- *
71
- * @throws Error if `domain` or `organizationName` are provided.
72
- * @example "omni.example.com"
73
- * @example "omni.another-example.app"
74
- */
75
- host: string;
76
- });
78
+ } & HostProps;
77
79
  type EmbedSsoDashboardOnlyProps = {
78
80
  accessBoost?: boolean;
79
81
  };
@@ -201,6 +203,16 @@ export declare enum EmbedEntityFolderContentRoles {
201
203
  EDITOR = "EDITOR",
202
204
  VIEWER = "VIEWER"
203
205
  }
206
+ /**
207
+ * UI settings used as keys of the uiSettings sso embed parameter.
208
+ */
209
+ export declare enum EmbedUiSettings {
210
+ /**
211
+ * When false, hides all in-app navigation elements. Note that in-app navigation
212
+ * is only visible when the embed session is in APPLICATION mode.
213
+ */
214
+ SHOW_NAVIGATION = "showNavigation"
215
+ }
204
216
  export type EmbedSsoContentProps = EmbedSsoBaseProps & EmbedSsoDashboardOnlyProps & EmbedContentPathProp;
205
217
  /**
206
218
  * Consumers of this SDK will rely on these three types to embed workbooks and dashboards.
@@ -218,4 +230,10 @@ export type EmbedSsoContentDiscoveryProps = EmbedSsoBaseProps & {
218
230
  */
219
231
  path: string;
220
232
  };
233
+ export type GenerateEmbedSsoSessionProps = Omit<EmbedSsoContentProps, "prefersDark" | "theme" | "secret" | "nonce"> & {
234
+ apiKey: string;
235
+ } & HostProps;
236
+ export type RedeemEmbedSsoSessionProps = Pick<EmbedSsoContentProps, "nonce" | "prefersDark" | "secret" | "theme" | "port"> & {
237
+ sessionToken: string;
238
+ } & HostProps;
221
239
  export {};
package/lib/esm/types.js CHANGED
@@ -80,3 +80,14 @@ export var EmbedEntityFolderContentRoles;
80
80
  EmbedEntityFolderContentRoles["EDITOR"] = "EDITOR";
81
81
  EmbedEntityFolderContentRoles["VIEWER"] = "VIEWER";
82
82
  })(EmbedEntityFolderContentRoles || (EmbedEntityFolderContentRoles = {}));
83
+ /**
84
+ * UI settings used as keys of the uiSettings sso embed parameter.
85
+ */
86
+ export var EmbedUiSettings;
87
+ (function (EmbedUiSettings) {
88
+ /**
89
+ * When false, hides all in-app navigation elements. Note that in-app navigation
90
+ * is only visible when the embed session is in APPLICATION mode.
91
+ */
92
+ EmbedUiSettings["SHOW_NAVIGATION"] = "showNavigation";
93
+ })(EmbedUiSettings || (EmbedUiSettings = {}));
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.6.0",
2
+ "version": "0.8.0",
3
3
  "license": "MIT",
4
4
  "name": "@omni-co/embed",
5
5
  "author": "Nate Agrin <nate@exploreomni.com>",