@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.
- package/dist/app-cache.d.ts +58 -0
- package/dist/app-cache.d.ts.map +1 -0
- package/dist/app-cache.js +154 -0
- package/dist/app-cache.js.map +1 -0
- package/dist/get-app-info.d.ts +9 -0
- package/dist/get-app-info.d.ts.map +1 -0
- package/dist/get-app-info.js +25 -0
- package/dist/get-app-info.js.map +1 -0
- package/dist/get-route.d.ts +83 -0
- package/dist/get-route.d.ts.map +1 -0
- package/dist/get-route.js +154 -0
- package/dist/get-route.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/log.d.ts +5 -0
- package/dist/lib/log.d.ts.map +1 -0
- package/dist/lib/log.js +11 -0
- package/dist/lib/log.js.map +1 -0
- package/dist/load-app-frame.d.ts +8 -0
- package/dist/load-app-frame.d.ts.map +1 -0
- package/dist/load-app-frame.js +38 -0
- package/dist/load-app-frame.js.map +1 -0
- package/dist/normalize-path-prefix.d.ts +8 -0
- package/dist/normalize-path-prefix.d.ts.map +1 -0
- package/dist/normalize-path-prefix.js +21 -0
- package/dist/normalize-path-prefix.js.map +1 -0
- package/dist/redirect-default-file.d.ts +18 -0
- package/dist/redirect-default-file.d.ts.map +1 -0
- package/dist/redirect-default-file.js +54 -0
- package/dist/redirect-default-file.js.map +1 -0
- package/dist/route-app.d.ts +23 -0
- package/dist/route-app.d.ts.map +1 -0
- package/dist/route-app.js +169 -0
- package/dist/route-app.js.map +1 -0
- package/package.json +5 -2
- package/src/app-cache.spec.ts +175 -0
- package/src/app-cache.ts +193 -0
- package/src/get-app-info.spec.ts +77 -0
- package/src/get-app-info.ts +31 -0
- package/src/get-route.spec.ts +585 -0
- package/src/get-route.ts +267 -0
- package/src/index.ts +5 -536
- package/src/load-app-frame.spec.ts +51 -0
- package/src/load-app-frame.ts +36 -0
- package/src/normalize-path-prefix.spec.ts +27 -0
- package/src/normalize-path-prefix.ts +18 -0
- package/src/redirect-default-file.spec.ts +98 -0
- package/src/redirect-default-file.ts +79 -0
- package/src/route-app.spec.ts +128 -0
- package/src/route-app.ts +202 -0
- package/src/index.spec.ts +0 -318
- /package/src/{index.prefix.spec.ts → get-route.prefix.spec.ts} +0 -0
package/src/get-route.ts
ADDED
|
@@ -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
|
+
}
|