@saasquatch/component-environment 1.0.0-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.
@@ -0,0 +1,52 @@
1
+ import { getAppDomain, getTenantAlias } from "./environment";
2
+ import { LOCALE_CONTEXT_NAME } from "./types";
3
+ import { debug as _debug } from "./debug";
4
+
5
+ const debug = (...args: any[]) => _debug(LOCALE_CONTEXT_NAME, ...args);
6
+
7
+ const GET_LOCALE = `
8
+ query {
9
+ viewer {
10
+ ... on User {
11
+ locale
12
+ }
13
+ }
14
+ }
15
+ `;
16
+
17
+ interface GetLocaleResponse {
18
+ viewer: {
19
+ locale: string | null;
20
+ };
21
+ }
22
+
23
+ export async function fetchLocale(): Promise<string | undefined> {
24
+ debug("Fetching locale from GraphQL for current user");
25
+
26
+ try {
27
+ const result = await fetch(
28
+ `${getAppDomain()}/api/v1/${getTenantAlias()}/graphql`,
29
+ {
30
+ method: "POST",
31
+ headers: { "Content-Type": "application/json" },
32
+ body: JSON.stringify({
33
+ query: GET_LOCALE,
34
+ }),
35
+ }
36
+ );
37
+
38
+ if (!result.ok) {
39
+ throw new Error("Failed to fetch locale");
40
+ }
41
+
42
+ const json = await result.json();
43
+ if (json.errors) {
44
+ throw new Error(JSON.stringify(json.errors, null, 2));
45
+ }
46
+
47
+ return (json as GetLocaleResponse).viewer.locale || undefined;
48
+ } catch (e) {
49
+ debug(`Failed to fetch locale for current user`, (e as Error).message);
50
+ return undefined;
51
+ }
52
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from "./types";
2
+ export * from "./environment";
3
+ export * from "./contexts/UserIdentityContext";
4
+ export * from "./contexts/LocaleContext";
5
+ export * from "./contexts/ProgramContext";
@@ -0,0 +1,51 @@
1
+ import equal from "@wry/equality";
2
+ import { ContextListener, ListenerConnectionStatus } from "dom-context";
3
+ import { USER_CONTEXT_NAME, LOCALE_CONTEXT_NAME } from "./types";
4
+ import { UserIdentity } from "./types";
5
+ import { lazilyStartLocaleContext } from "./contexts/LocaleContext";
6
+ import { lazilyStartUserContext } from "./contexts/UserIdentityContext";
7
+ import { fetchLocale } from "./fetchLocale";
8
+ import { debug as _debug } from "./debug";
9
+
10
+ const debug = (...args: any[]) => _debug(LOCALE_CONTEXT_NAME, ...args);
11
+
12
+ const userContextListenerDiv = (() => {
13
+ const id = "__environment_context_listener";
14
+ let div = document.getElementById(id);
15
+ if (!div) {
16
+ div = document.createElement("div");
17
+ div.id = id;
18
+ document.documentElement.appendChild(div);
19
+ }
20
+ return div;
21
+ })();
22
+
23
+ // Listens to user changes and refetches the locale from GraphQL
24
+ const userContextListenerForLocale = new ContextListener<
25
+ UserIdentity | undefined
26
+ >({
27
+ contextName: USER_CONTEXT_NAME,
28
+ element: userContextListenerDiv,
29
+ onChange: async (next) => {
30
+ if (next) {
31
+ const userProvider = lazilyStartUserContext();
32
+ if (!equal(userProvider.context, next)) {
33
+ debug("User context changed, refetching locale");
34
+ const locale = await fetchLocale();
35
+ const localeProvider = lazilyStartLocaleContext();
36
+ if (localeProvider.context !== locale) {
37
+ debug(`New value fetched from GraphQL [${locale}]`);
38
+ localeProvider.context =
39
+ locale ||
40
+ window.widgetIdent?.locale ||
41
+ navigator.language.replace("-", "_");
42
+ }
43
+ }
44
+ }
45
+ },
46
+ });
47
+
48
+ export function startUserContextListenerForLocale() {
49
+ debug("Starting user context listener for locale updates");
50
+ userContextListenerForLocale.start();
51
+ }
package/src/types.ts ADDED
@@ -0,0 +1,173 @@
1
+ import type { ContextProvider } from "dom-context";
2
+
3
+ declare global {
4
+ interface Window {
5
+ SquatchPortal?: PortalEnv;
6
+ widgetIdent?: WidgetIdent;
7
+ squatchUserIdentity?: ContextProvider<UserIdentity | undefined>;
8
+ squatchLocale?: ContextProvider<string | undefined>;
9
+ squatchProgramId?: ContextProvider<string | undefined>;
10
+ }
11
+ }
12
+
13
+ export type UserContextName = "sq:user-identity";
14
+ export type LocaleContextName = "sq:locale";
15
+ export type ProgramContextName = "sq:program-id";
16
+
17
+ export const USER_CONTEXT_NAME: UserContextName = "sq:user-identity" as const;
18
+ export const LOCALE_CONTEXT_NAME: LocaleContextName = "sq:locale" as const;
19
+ export const PROGRAM_CONTEXT_NAME: ProgramContextName =
20
+ "sq:program-id" as const;
21
+
22
+ /**
23
+ * The value stored in the UserContext
24
+ */
25
+ export type UserIdentity = {
26
+ id: string;
27
+ accountId: string;
28
+ jwt?: string;
29
+ managedIdentity?: {
30
+ email: string;
31
+ emailVerified: boolean;
32
+ sessionData?: { [key: string]: any };
33
+ };
34
+ };
35
+
36
+ export type UserId = {
37
+ id: string;
38
+ accountId: string;
39
+ };
40
+
41
+ export interface DecodedSquatchJWT {
42
+ exp?: number;
43
+ user: {
44
+ accountId: string;
45
+ id: string;
46
+ };
47
+ }
48
+
49
+ // NOTE: Classic theme-engine JWT's do not have a typical payload,
50
+ // they have a sub in the form base64(accountId):base64(userId)@tenantAlias:users
51
+ export interface DecodedWidgetAPIJWT {
52
+ exp?: number;
53
+ sub: string;
54
+ }
55
+
56
+ export type EngagementMedium = "EMBED" | "POPUP";
57
+ export const DEFAULT_MEDIUM: EngagementMedium = "EMBED" as const;
58
+
59
+ /**
60
+ * Provided by the SaaSquatch GraphQL backend when a widget is rendered.
61
+ *
62
+ * Source: https://github.com/saasquatch/saasquatch/blob/805e51284f818f8656b6458bcee6181f378819d3/packages/saasquatch-core/app/saasquatch/controllers/api/widget/WidgetApi.java
63
+ *
64
+ */
65
+ export interface WidgetIdent {
66
+ tenantAlias: string;
67
+ appDomain: string;
68
+ token: string;
69
+ userId: string;
70
+ accountId: string;
71
+ locale?: string;
72
+ engagementMedium?: "POPUP" | "EMBED";
73
+ programId?: string;
74
+ env?: string;
75
+ }
76
+
77
+ /**
78
+ * Portal env doesn't include User Id
79
+ */
80
+ export type PortalEnv = Pick<
81
+ WidgetIdent,
82
+ "tenantAlias" | "appDomain" | "programId"
83
+ >;
84
+
85
+ /**
86
+ * An interface for interacting with the SaaSquatch Admin Portal.
87
+ *
88
+ * Used for rendering widgets in a preview/demo mode.
89
+ */
90
+ export interface SquatchAdmin {
91
+ /**
92
+ * Provides a way of providing user feedback when a widget is rendered in the SaaSquatch admin portal
93
+ *
94
+ * @param text
95
+ */
96
+ showMessage(text: string): void;
97
+ }
98
+
99
+ /**
100
+ * Type for the Javascript environment added by https://github.com/saasquatch/squatch-android
101
+ *
102
+ * Should exist as `window.SquatchAndroid`
103
+ */
104
+ export interface SquatchAndroid {
105
+ /**
106
+ *
107
+ * @param shareLink
108
+ * @param messageLink fallback URL to redirect to if the app is not installed
109
+ */
110
+ shareOnFacebook(shareLink: string, messageLink: string): void;
111
+
112
+ /**
113
+ * Shows a native Android toast
114
+ *
115
+ * @param text
116
+ */
117
+ showToast(text: string): void;
118
+ }
119
+
120
+ /**
121
+ * An interface provided by Squatch.js V2 for widgets.
122
+ *
123
+ * See: https://github.com/saasquatch/squatch-js/blob/8f2b218c9d55567e0cc12d27d635a5fb545e6842/src/widgets/Widget.ts#L47
124
+ *
125
+ */
126
+ export interface SquatchJS2 {
127
+ /**
128
+ * Opens the current popup widget (if loaded as a popup)
129
+ */
130
+ open?: () => void;
131
+
132
+ /**
133
+ * Closes the current popup widget (if loaded as a popup)
134
+ */
135
+ close?: () => void;
136
+
137
+ /**
138
+ * DEPRECATED used to update user details from inside the widget.
139
+ *
140
+ * Should no longer be used. Replace with natively using the GraphQL API and re-rendering locally. Will be removed in a future version of Squatch.js
141
+ *
142
+ * @deprecated
143
+ */
144
+ reload(
145
+ userDetails: { email: string; firstName: string; lastName: string },
146
+ jwt: string
147
+ ): void;
148
+ }
149
+
150
+ export type Environment = EnvironmentSDK["type"];
151
+
152
+ export type EnvironmentSDK =
153
+ | {
154
+ type: "SquatchJS2";
155
+ api: SquatchJS2;
156
+ widgetIdent: WidgetIdent;
157
+ }
158
+ | {
159
+ type: "SquatchAndroid";
160
+ android: SquatchAndroid;
161
+ widgetIdent: WidgetIdent;
162
+ }
163
+ | {
164
+ type: "SquatchPortal";
165
+ env: PortalEnv;
166
+ }
167
+ | {
168
+ type: "SquatchAdmin";
169
+ adminSDK: SquatchAdmin;
170
+ }
171
+ | {
172
+ type: "None";
173
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2021",
4
+ "lib": ["ES2021", "DOM"],
5
+ "module": "es2015",
6
+ "moduleResolution": "node",
7
+ "outDir": "./dist",
8
+ "esModuleInterop": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "strict": true,
11
+ "skipLibCheck": true,
12
+ "declaration": true
13
+ },
14
+ "include": ["src/**/*"]
15
+ }