@pwrdrvr/microapps-router-lib 0.4.0-alpha.8 → 1.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.
Files changed (54) hide show
  1. package/dist/app-cache.d.ts +58 -0
  2. package/dist/app-cache.d.ts.map +1 -0
  3. package/dist/app-cache.js +154 -0
  4. package/dist/app-cache.js.map +1 -0
  5. package/dist/get-app-info.d.ts +9 -0
  6. package/dist/get-app-info.d.ts.map +1 -0
  7. package/dist/get-app-info.js +25 -0
  8. package/dist/get-app-info.js.map +1 -0
  9. package/dist/get-route.d.ts +83 -0
  10. package/dist/get-route.d.ts.map +1 -0
  11. package/dist/get-route.js +154 -0
  12. package/dist/get-route.js.map +1 -0
  13. package/dist/index.d.ts +6 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +14 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/lib/log.d.ts +5 -0
  18. package/dist/lib/log.d.ts.map +1 -0
  19. package/dist/lib/log.js +11 -0
  20. package/dist/lib/log.js.map +1 -0
  21. package/dist/load-app-frame.d.ts +8 -0
  22. package/dist/load-app-frame.d.ts.map +1 -0
  23. package/dist/load-app-frame.js +38 -0
  24. package/dist/load-app-frame.js.map +1 -0
  25. package/dist/normalize-path-prefix.d.ts +8 -0
  26. package/dist/normalize-path-prefix.d.ts.map +1 -0
  27. package/dist/normalize-path-prefix.js +21 -0
  28. package/dist/normalize-path-prefix.js.map +1 -0
  29. package/dist/redirect-default-file.d.ts +18 -0
  30. package/dist/redirect-default-file.d.ts.map +1 -0
  31. package/dist/redirect-default-file.js +54 -0
  32. package/dist/redirect-default-file.js.map +1 -0
  33. package/dist/route-app.d.ts +23 -0
  34. package/dist/route-app.d.ts.map +1 -0
  35. package/dist/route-app.js +169 -0
  36. package/dist/route-app.js.map +1 -0
  37. package/package.json +5 -2
  38. package/src/app-cache.spec.ts +175 -0
  39. package/src/app-cache.ts +193 -0
  40. package/src/get-app-info.spec.ts +77 -0
  41. package/src/get-app-info.ts +31 -0
  42. package/src/get-route.spec.ts +585 -0
  43. package/src/get-route.ts +267 -0
  44. package/src/index.ts +5 -536
  45. package/src/load-app-frame.spec.ts +51 -0
  46. package/src/load-app-frame.ts +36 -0
  47. package/src/normalize-path-prefix.spec.ts +27 -0
  48. package/src/normalize-path-prefix.ts +18 -0
  49. package/src/redirect-default-file.spec.ts +98 -0
  50. package/src/redirect-default-file.ts +79 -0
  51. package/src/route-app.spec.ts +128 -0
  52. package/src/route-app.ts +202 -0
  53. package/src/index.spec.ts +0 -318
  54. /package/src/{index.prefix.spec.ts → get-route.prefix.spec.ts} +0 -0
@@ -0,0 +1,267 @@
1
+ import Log from './lib/log';
2
+ import { GetAppInfo } from './get-app-info';
3
+ import { RouteApp } from './route-app';
4
+ import { RedirectToDefaultFile } from './redirect-default-file';
5
+ import { DBManager } from '@pwrdrvr/microapps-datalib';
6
+ import { AppVersionCache } from './app-cache';
7
+
8
+ const log = Log.Instance;
9
+
10
+ export interface IGetRouteResult {
11
+ /**
12
+ * HTTP status code for immediate response, immediate redirect, and errors
13
+ */
14
+ readonly statusCode?: number;
15
+
16
+ /**
17
+ * Error message for errors
18
+ */
19
+ readonly errorMessage?: string;
20
+
21
+ /**
22
+ * Location to redirect to
23
+ */
24
+ readonly redirectLocation?: string;
25
+
26
+ /**
27
+ * Optional headers for immediate response, immediate redirect, and errors
28
+ */
29
+ readonly headers?: Record<string, string>;
30
+
31
+ /**
32
+ *
33
+ *
34
+ * @example /myapp/1.0.0/index.html
35
+ * @example /myapp/1.0.1
36
+ * @example /myapp/1.0.2/some/path?query=string
37
+ */
38
+ readonly iFrameAppVersionPath?: string;
39
+
40
+ /**
41
+ * Name of the app if resolved
42
+ */
43
+ readonly appName?: string;
44
+
45
+ /**
46
+ * Version of the app if resolved
47
+ */
48
+ readonly semVer?: string;
49
+
50
+ /**
51
+ * Type of the app
52
+ */
53
+ readonly type?: 'apigwy' | 'lambda-url' | 'url' | 'static';
54
+
55
+ /**
56
+ * Startup type of the app (indirect with iframe or direct)
57
+ */
58
+ readonly startupType?: 'iframe' | 'direct';
59
+
60
+ /**
61
+ * URL to the app if resolved
62
+ */
63
+ readonly url?: string;
64
+
65
+ /**
66
+ * Does the extra app path start with /api/
67
+ */
68
+ readonly isAPIPath?: boolean;
69
+ }
70
+
71
+ export interface IGetRouteEvent {
72
+ readonly dbManager: DBManager;
73
+
74
+ /**
75
+ * rawPath from the Lambda event
76
+ */
77
+ readonly rawPath: string;
78
+
79
+ /**
80
+ * List of locale prefixes after the normalizedPathPrefix
81
+ *
82
+ * @default []
83
+ */
84
+ readonly locales?: string[];
85
+
86
+ /**
87
+ * Configured prefix of the deployment, must start with a / and not end with a /
88
+ */
89
+ readonly normalizedPathPrefix?: string;
90
+
91
+ /**
92
+ * Query string params, if any
93
+ * Checked for `appver=1.2.3` to override the app version
94
+ */
95
+ readonly queryStringParameters?: URLSearchParams;
96
+ }
97
+
98
+ /**
99
+ * Get information about immediate redirect, immediate response,
100
+ * or which host to route the request to.
101
+ *
102
+ * @param event
103
+ *
104
+ * @returns IGetRouteResult
105
+ */
106
+
107
+ export async function GetRoute(event: IGetRouteEvent): Promise<IGetRouteResult> {
108
+ const { dbManager, normalizedPathPrefix = '', queryStringParameters, locales = [] } = event;
109
+
110
+ try {
111
+ const appVersionCache = AppVersionCache.GetInstance({ dbManager });
112
+
113
+ if (!!normalizedPathPrefix && !event.rawPath.startsWith(normalizedPathPrefix)) {
114
+ // The prefix is required if configured, if missing we cannot serve this app
115
+ return { statusCode: 404, errorMessage: 'Request not routable' };
116
+ }
117
+
118
+ const pathAfterPrefix =
119
+ normalizedPathPrefix && event.rawPath.startsWith(normalizedPathPrefix)
120
+ ? event.rawPath.slice(normalizedPathPrefix.length - 1)
121
+ : event.rawPath;
122
+
123
+ const pathAfterPrefixAndLocale = locales.reduce((path, locale) => {
124
+ if (path.startsWith(`/${locale}/`)) {
125
+ return path.slice(locale.length + 1);
126
+ }
127
+ return path;
128
+ }, pathAfterPrefix);
129
+
130
+ // /someapp will split into length 2 with ["", "someapp"] as results
131
+ // /someapp/somepath will split into length 3 with ["", "someapp", "somepath"] as results
132
+ // /someapp/somepath/ will split into length 3 with ["", "someapp", "somepath", ""] as results
133
+ // /someapp/somepath/somefile.foo will split into length 4 with ["", "someapp", "somepath", "somefile.foo", ""] as results
134
+ const partsAfterPrefixAndLocale = pathAfterPrefixAndLocale.split('/');
135
+
136
+ // Handle ${prefix}/_next/data/${semver}[/${locale}]/${appname}/route
137
+ let appName: string | undefined;
138
+ if (
139
+ partsAfterPrefixAndLocale.length >= 4 &&
140
+ partsAfterPrefixAndLocale[1] === '_next' &&
141
+ partsAfterPrefixAndLocale[2] === 'data'
142
+ ) {
143
+ // Remove locale if present after SemVer
144
+ const localeIsPresent =
145
+ partsAfterPrefixAndLocale.length >= 5 &&
146
+ locales.some((locale) => partsAfterPrefixAndLocale[4] === locale);
147
+ const possibleAppNamePart = localeIsPresent
148
+ ? partsAfterPrefixAndLocale[5]
149
+ : partsAfterPrefixAndLocale[4];
150
+
151
+ // if partsAfterPrefix[4] has .json suffix, strip it
152
+ const possibleAppName = possibleAppNamePart.endsWith('.json')
153
+ ? possibleAppNamePart.slice(0, possibleAppNamePart.length - 5)
154
+ : possibleAppNamePart;
155
+
156
+ appName = await GetAppInfo({
157
+ dbManager,
158
+ appName: possibleAppName,
159
+ });
160
+ }
161
+
162
+ if (!appName) {
163
+ appName = await GetAppInfo({
164
+ dbManager,
165
+ appName: partsAfterPrefixAndLocale.length >= 2 ? partsAfterPrefixAndLocale[1] : '[root]',
166
+ });
167
+ }
168
+
169
+ if (!appName) {
170
+ return { statusCode: 404, errorMessage: 'App not found' };
171
+ }
172
+
173
+ const isRootApp = appName === '[root]';
174
+ const appNameOrRootTrailingSlash = isRootApp ? '' : `${appName}/`;
175
+
176
+ // Strip the appName from the start of the path, if there was one
177
+ const pathAfterAppName = isRootApp
178
+ ? pathAfterPrefixAndLocale
179
+ : pathAfterPrefixAndLocale.slice(appName.length + 1);
180
+ const partsAfterAppName = pathAfterAppName.split('/');
181
+
182
+ // Pass any parts after the appName/Version to the route handler
183
+ let additionalParts = '';
184
+ if (partsAfterAppName.length >= 2 && partsAfterAppName[1] !== '') {
185
+ additionalParts = partsAfterAppName.slice(1).join('/');
186
+ }
187
+
188
+ // Route an app and version (only) to include the defaultFile
189
+ // If the second part is not a version that exists, fall through to
190
+ // routing the app and glomming the rest of the path on to the end
191
+ if (
192
+ partsAfterAppName.length === 2 ||
193
+ (partsAfterAppName.length === 3 && !partsAfterAppName[2])
194
+ ) {
195
+ // / semVer /
196
+ // ^ ^^^^^^ ^
197
+ // 0 1 2
198
+ // This may be an app and a version only
199
+ // If the request got here it's likely a static app that has no
200
+ // Lambda function
201
+
202
+ // Let's check if the part is a version, if it is, route to the default file
203
+ const versionInfo = await appVersionCache.GetVersionInfo({
204
+ key: { AppName: appName, SemVer: partsAfterAppName[1] },
205
+ });
206
+
207
+ if (versionInfo) {
208
+ const response = await RedirectToDefaultFile({
209
+ dbManager,
210
+ appName,
211
+ normalizedPathPrefix,
212
+ semVer: partsAfterAppName[1],
213
+ appNameOrRootTrailingSlash,
214
+ });
215
+ if (response) {
216
+ return response;
217
+ }
218
+ }
219
+ }
220
+
221
+ // Check for a version in the path
222
+ // Examples
223
+ // / semVer / somepath
224
+ // / _next / data / semVer / somepath
225
+ // Get the version afer `/_next/data/` from partsAfterPrefix
226
+ const possibleSemVerPathNextDataBasePath =
227
+ partsAfterAppName.length >= 4 ? partsAfterAppName[3] : '';
228
+ const possibleSemVerPathNextData =
229
+ partsAfterPrefixAndLocale.length >= 4 &&
230
+ partsAfterPrefixAndLocale[1] === '_next' &&
231
+ partsAfterPrefixAndLocale[2] == 'data'
232
+ ? partsAfterPrefixAndLocale[3]
233
+ : possibleSemVerPathNextDataBasePath;
234
+
235
+ const possibleSemVerPathAfterApp = partsAfterAppName.length >= 2 ? partsAfterAppName[1] : '';
236
+
237
+ // (/ something)?
238
+ // ^ ^^^^^^^^^^^^
239
+ // 0 1
240
+ // Got at least an application name, try to route it
241
+ const response = await RouteApp({
242
+ dbManager,
243
+ normalizedPathPrefix,
244
+ event,
245
+ appName,
246
+ possibleSemVerPathNextData,
247
+ possibleSemVerPathAfterApp,
248
+ possibleSemVerQuery: queryStringParameters?.get('appver') || '',
249
+ additionalParts,
250
+ appNameOrRootTrailingSlash,
251
+ });
252
+ if (response) {
253
+ return response;
254
+ }
255
+
256
+ return {
257
+ statusCode: 599,
258
+ errorMessage: `Router - Could not route: ${event.rawPath}, no matching route`,
259
+ };
260
+ } catch (error: any) {
261
+ log.error('unexpected exception - returning 599', { statusCode: 599, error });
262
+ return {
263
+ statusCode: 599,
264
+ errorMessage: `Router - Could not route: ${event.rawPath}, ${error.message}`,
265
+ };
266
+ }
267
+ }