@enfyra/sdk-nuxt 0.3.6 → 0.3.8
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/module.cjs +0 -10
- package/dist/module.json +1 -1
- package/dist/module.mjs +0 -10
- package/dist/utils/server/refreshToken.mjs +24 -4
- package/package.json +1 -1
- package/src/module.ts +0 -11
- package/src/utils/server/refreshToken.ts +30 -5
- package/dist/runtime/server/api/extension_definition/[id].patch.d.ts +0 -0
- package/dist/runtime/server/api/extension_definition/[id].patch.js +0 -40
- package/dist/runtime/server/api/extension_definition/[id].patch.mjs +0 -40
- package/dist/runtime/server/api/extension_definition.post.d.ts +0 -0
- package/dist/runtime/server/api/extension_definition.post.js +0 -40
- package/dist/runtime/server/api/extension_definition.post.mjs +0 -40
- package/dist/utils/server/extension/compiler.d.ts +0 -0
- package/dist/utils/server/extension/compiler.mjs +0 -64
- package/dist/utils/server/extension/index.d.ts +0 -0
- package/dist/utils/server/extension/index.mjs +0 -5
- package/dist/utils/server/extension/naming.d.ts +0 -0
- package/dist/utils/server/extension/naming.mjs +0 -13
- package/dist/utils/server/extension/processor.d.ts +0 -0
- package/dist/utils/server/extension/processor.mjs +0 -34
- package/dist/utils/server/extension/validation.d.ts +0 -0
- package/dist/utils/server/extension/validation.mjs +0 -67
- package/dist/utils/server/extension.d.ts +0 -0
- package/dist/utils/server/extension.mjs +0 -1
- package/src/runtime/server/api/extension_definition/[id].patch.ts +0 -48
- package/src/runtime/server/api/extension_definition.post.ts +0 -48
- package/src/utils/server/extension/compiler.ts +0 -76
- package/src/utils/server/extension/index.ts +0 -5
- package/src/utils/server/extension/naming.ts +0 -18
- package/src/utils/server/extension/processor.ts +0 -48
- package/src/utils/server/extension/validation.ts +0 -91
- package/src/utils/server/extension.ts +0 -1
package/dist/module.cjs
CHANGED
|
@@ -56,16 +56,6 @@ enfyraSDK: {
|
|
|
56
56
|
handler: resolve("./runtime/server/api/logout.post"),
|
|
57
57
|
method: "post"
|
|
58
58
|
});
|
|
59
|
-
kit.addServerHandler({
|
|
60
|
-
route: `${config_mjs.ENFYRA_API_PREFIX}/extension_definition`,
|
|
61
|
-
handler: resolve("./runtime/server/api/extension_definition.post"),
|
|
62
|
-
method: "post"
|
|
63
|
-
});
|
|
64
|
-
kit.addServerHandler({
|
|
65
|
-
route: `${config_mjs.ENFYRA_API_PREFIX}/extension_definition/**`,
|
|
66
|
-
handler: resolve("./runtime/server/api/extension_definition/[id].patch"),
|
|
67
|
-
method: "patch"
|
|
68
|
-
});
|
|
69
59
|
kit.addServerHandler({
|
|
70
60
|
route: "/assets/**",
|
|
71
61
|
handler: resolve("./runtime/server/api/all")
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -53,16 +53,6 @@ enfyraSDK: {
|
|
|
53
53
|
handler: resolve("./runtime/server/api/logout.post"),
|
|
54
54
|
method: "post"
|
|
55
55
|
});
|
|
56
|
-
addServerHandler({
|
|
57
|
-
route: `${ENFYRA_API_PREFIX}/extension_definition`,
|
|
58
|
-
handler: resolve("./runtime/server/api/extension_definition.post"),
|
|
59
|
-
method: "post"
|
|
60
|
-
});
|
|
61
|
-
addServerHandler({
|
|
62
|
-
route: `${ENFYRA_API_PREFIX}/extension_definition/**`,
|
|
63
|
-
handler: resolve("./runtime/server/api/extension_definition/[id].patch"),
|
|
64
|
-
method: "patch"
|
|
65
|
-
});
|
|
66
56
|
addServerHandler({
|
|
67
57
|
route: "/assets/**",
|
|
68
58
|
handler: resolve("./runtime/server/api/all")
|
|
@@ -5,14 +5,34 @@ import {
|
|
|
5
5
|
REFRESH_TOKEN_KEY,
|
|
6
6
|
EXP_TIME_KEY
|
|
7
7
|
} from "../../constants/auth";
|
|
8
|
+
export function decodeJWT(token) {
|
|
9
|
+
try {
|
|
10
|
+
const parts = token.split(".");
|
|
11
|
+
if (parts.length !== 3) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
const payload = parts[1];
|
|
15
|
+
const decodedPayload = Buffer.from(payload, "base64url").toString("utf-8");
|
|
16
|
+
return JSON.parse(decodedPayload);
|
|
17
|
+
} catch (error) {
|
|
18
|
+
console.warn("Failed to decode JWT:", error);
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function isAccessTokenExpired(accessToken) {
|
|
23
|
+
const decoded = decodeJWT(accessToken);
|
|
24
|
+
if (!decoded || !decoded.exp) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
const expirationTime = decoded.exp * 1e3;
|
|
28
|
+
return Date.now() >= expirationTime;
|
|
29
|
+
}
|
|
8
30
|
export function validateTokens(event) {
|
|
9
31
|
const accessToken = getCookie(event, ACCESS_TOKEN_KEY);
|
|
10
32
|
const refreshToken = getCookie(event, REFRESH_TOKEN_KEY);
|
|
11
|
-
|
|
12
|
-
const isTokenExpired = expTime && Date.now() > parseInt(expTime);
|
|
13
|
-
if (accessToken && !isTokenExpired) {
|
|
33
|
+
if (accessToken && !isAccessTokenExpired(accessToken)) {
|
|
14
34
|
return { accessToken, needsRefresh: false };
|
|
15
|
-
} else if (refreshToken && (
|
|
35
|
+
} else if (refreshToken && (!accessToken || isAccessTokenExpired(accessToken))) {
|
|
16
36
|
return { accessToken: null, needsRefresh: true };
|
|
17
37
|
}
|
|
18
38
|
return { accessToken: null, needsRefresh: false };
|
package/package.json
CHANGED
package/src/module.ts
CHANGED
|
@@ -68,17 +68,6 @@ export default defineNuxtModule({
|
|
|
68
68
|
method: "post",
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
-
addServerHandler({
|
|
72
|
-
route: `${ENFYRA_API_PREFIX}/extension_definition`,
|
|
73
|
-
handler: resolve("./runtime/server/api/extension_definition.post"),
|
|
74
|
-
method: "post",
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
addServerHandler({
|
|
78
|
-
route: `${ENFYRA_API_PREFIX}/extension_definition/**`,
|
|
79
|
-
handler: resolve("./runtime/server/api/extension_definition/[id].patch"),
|
|
80
|
-
method: "patch",
|
|
81
|
-
});
|
|
82
71
|
|
|
83
72
|
addServerHandler({
|
|
84
73
|
route: "/assets/**",
|
|
@@ -11,16 +11,41 @@ interface TokenValidationResult {
|
|
|
11
11
|
needsRefresh: boolean;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
export function decodeJWT(token: string): any {
|
|
15
|
+
try {
|
|
16
|
+
const parts = token.split(".");
|
|
17
|
+
if (parts.length !== 3) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Decode the payload (second part)
|
|
22
|
+
const payload = parts[1];
|
|
23
|
+
const decodedPayload = Buffer.from(payload, "base64url").toString("utf-8");
|
|
24
|
+
return JSON.parse(decodedPayload);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.warn("Failed to decode JWT:", error);
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function isAccessTokenExpired(accessToken: string): boolean {
|
|
32
|
+
const decoded = decodeJWT(accessToken);
|
|
33
|
+
if (!decoded || !decoded.exp) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// JWT exp is in seconds, Date.now() is in milliseconds
|
|
38
|
+
const expirationTime = decoded.exp * 1000;
|
|
39
|
+
return Date.now() >= expirationTime;
|
|
40
|
+
}
|
|
41
|
+
|
|
14
42
|
export function validateTokens(event: H3Event): TokenValidationResult {
|
|
15
43
|
const accessToken = getCookie(event, ACCESS_TOKEN_KEY);
|
|
16
44
|
const refreshToken = getCookie(event, REFRESH_TOKEN_KEY);
|
|
17
|
-
const expTime = getCookie(event, EXP_TIME_KEY);
|
|
18
|
-
|
|
19
|
-
const isTokenExpired = expTime && Date.now() > parseInt(expTime);
|
|
20
45
|
|
|
21
|
-
if (accessToken && !
|
|
46
|
+
if (accessToken && !isAccessTokenExpired(accessToken)) {
|
|
22
47
|
return { accessToken, needsRefresh: false };
|
|
23
|
-
} else if (refreshToken && (
|
|
48
|
+
} else if (refreshToken && (!accessToken || isAccessTokenExpired(accessToken))) {
|
|
24
49
|
return { accessToken: null, needsRefresh: true };
|
|
25
50
|
}
|
|
26
51
|
|
|
File without changes
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
defineEventHandler,
|
|
3
|
-
readBody,
|
|
4
|
-
getHeader,
|
|
5
|
-
createError
|
|
6
|
-
} from "h3";
|
|
7
|
-
import { useRuntimeConfig } from "#imports";
|
|
8
|
-
import { $fetch } from "ofetch";
|
|
9
|
-
import {
|
|
10
|
-
processExtensionDefinition
|
|
11
|
-
} from "../../../../utils/server/extension";
|
|
12
|
-
export default defineEventHandler(async (event) => {
|
|
13
|
-
const method = event.method;
|
|
14
|
-
try {
|
|
15
|
-
let body = await readBody(event);
|
|
16
|
-
const { processedBody, compiledCode } = await processExtensionDefinition(body, method);
|
|
17
|
-
body = processedBody;
|
|
18
|
-
const config = useRuntimeConfig();
|
|
19
|
-
const apiPath = event.path.replace("/enfyra/api", "");
|
|
20
|
-
const targetUrl = `${config.public?.enfyraSDK?.apiUrl}${apiPath}`;
|
|
21
|
-
const response = await $fetch(targetUrl, {
|
|
22
|
-
method: "PATCH",
|
|
23
|
-
headers: {
|
|
24
|
-
cookie: getHeader(event, "cookie") || "",
|
|
25
|
-
authorization: event.context.proxyHeaders?.authorization || "",
|
|
26
|
-
"Content-Type": "application/json"
|
|
27
|
-
},
|
|
28
|
-
body: body || void 0
|
|
29
|
-
});
|
|
30
|
-
return response;
|
|
31
|
-
} catch (error) {
|
|
32
|
-
if (error.statusCode) {
|
|
33
|
-
throw error;
|
|
34
|
-
}
|
|
35
|
-
throw createError({
|
|
36
|
-
statusCode: 500,
|
|
37
|
-
statusMessage: error.message || `Failed to process extension definition ${method}`
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
});
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
defineEventHandler,
|
|
3
|
-
readBody,
|
|
4
|
-
getHeader,
|
|
5
|
-
createError
|
|
6
|
-
} from "h3";
|
|
7
|
-
import { useRuntimeConfig } from "#imports";
|
|
8
|
-
import { $fetch } from "ofetch";
|
|
9
|
-
import {
|
|
10
|
-
processExtensionDefinition
|
|
11
|
-
} from "../../../../utils/server/extension";
|
|
12
|
-
export default defineEventHandler(async (event) => {
|
|
13
|
-
const method = event.method;
|
|
14
|
-
try {
|
|
15
|
-
let body = await readBody(event);
|
|
16
|
-
const { processedBody, compiledCode } = await processExtensionDefinition(body, method);
|
|
17
|
-
body = processedBody;
|
|
18
|
-
const config = useRuntimeConfig();
|
|
19
|
-
const apiPath = event.path.replace("/enfyra/api", "");
|
|
20
|
-
const targetUrl = `${config.public?.enfyraSDK?.apiUrl}${apiPath}`;
|
|
21
|
-
const response = await $fetch(targetUrl, {
|
|
22
|
-
method: "PATCH",
|
|
23
|
-
headers: {
|
|
24
|
-
cookie: getHeader(event, "cookie") || "",
|
|
25
|
-
authorization: event.context.proxyHeaders?.authorization || "",
|
|
26
|
-
"Content-Type": "application/json"
|
|
27
|
-
},
|
|
28
|
-
body: body || void 0
|
|
29
|
-
});
|
|
30
|
-
return response;
|
|
31
|
-
} catch (error) {
|
|
32
|
-
if (error.statusCode) {
|
|
33
|
-
throw error;
|
|
34
|
-
}
|
|
35
|
-
throw createError({
|
|
36
|
-
statusCode: 500,
|
|
37
|
-
statusMessage: error.message || `Failed to process extension definition ${method}`
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
});
|
|
File without changes
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
defineEventHandler,
|
|
3
|
-
readBody,
|
|
4
|
-
getHeader,
|
|
5
|
-
createError
|
|
6
|
-
} from "h3";
|
|
7
|
-
import { useRuntimeConfig } from "#imports";
|
|
8
|
-
import { $fetch } from "ofetch";
|
|
9
|
-
import {
|
|
10
|
-
processExtensionDefinition
|
|
11
|
-
} from "../../../utils/server/extension";
|
|
12
|
-
export default defineEventHandler(async (event) => {
|
|
13
|
-
const method = event.method;
|
|
14
|
-
try {
|
|
15
|
-
let body = await readBody(event);
|
|
16
|
-
const { processedBody, compiledCode } = await processExtensionDefinition(body, method);
|
|
17
|
-
body = processedBody;
|
|
18
|
-
const config = useRuntimeConfig();
|
|
19
|
-
const apiPath = event.path.replace("/enfyra/api", "");
|
|
20
|
-
const targetUrl = `${config.public?.enfyraSDK?.apiUrl}${apiPath}`;
|
|
21
|
-
const response = await $fetch(targetUrl, {
|
|
22
|
-
method,
|
|
23
|
-
headers: {
|
|
24
|
-
cookie: getHeader(event, "cookie") || "",
|
|
25
|
-
authorization: event.context.proxyHeaders?.authorization || "",
|
|
26
|
-
"Content-Type": "application/json"
|
|
27
|
-
},
|
|
28
|
-
body: body || void 0
|
|
29
|
-
});
|
|
30
|
-
return response;
|
|
31
|
-
} catch (error) {
|
|
32
|
-
if (error.statusCode) {
|
|
33
|
-
throw error;
|
|
34
|
-
}
|
|
35
|
-
throw createError({
|
|
36
|
-
statusCode: 500,
|
|
37
|
-
statusMessage: error.message || `Failed to process extension definition ${method}`
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
});
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
defineEventHandler,
|
|
3
|
-
readBody,
|
|
4
|
-
getHeader,
|
|
5
|
-
createError
|
|
6
|
-
} from "h3";
|
|
7
|
-
import { useRuntimeConfig } from "#imports";
|
|
8
|
-
import { $fetch } from "ofetch";
|
|
9
|
-
import {
|
|
10
|
-
processExtensionDefinition
|
|
11
|
-
} from "../../../utils/server/extension";
|
|
12
|
-
export default defineEventHandler(async (event) => {
|
|
13
|
-
const method = event.method;
|
|
14
|
-
try {
|
|
15
|
-
let body = await readBody(event);
|
|
16
|
-
const { processedBody, compiledCode } = await processExtensionDefinition(body, method);
|
|
17
|
-
body = processedBody;
|
|
18
|
-
const config = useRuntimeConfig();
|
|
19
|
-
const apiPath = event.path.replace("/enfyra/api", "");
|
|
20
|
-
const targetUrl = `${config.public?.enfyraSDK?.apiUrl}${apiPath}`;
|
|
21
|
-
const response = await $fetch(targetUrl, {
|
|
22
|
-
method,
|
|
23
|
-
headers: {
|
|
24
|
-
cookie: getHeader(event, "cookie") || "",
|
|
25
|
-
authorization: event.context.proxyHeaders?.authorization || "",
|
|
26
|
-
"Content-Type": "application/json"
|
|
27
|
-
},
|
|
28
|
-
body: body || void 0
|
|
29
|
-
});
|
|
30
|
-
return response;
|
|
31
|
-
} catch (error) {
|
|
32
|
-
if (error.statusCode) {
|
|
33
|
-
throw error;
|
|
34
|
-
}
|
|
35
|
-
throw createError({
|
|
36
|
-
statusCode: 500,
|
|
37
|
-
statusMessage: error.message || `Failed to process extension definition ${method}`
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
});
|
|
File without changes
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "crypto";
|
|
2
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
3
|
-
import { join } from "path";
|
|
4
|
-
import { build } from "vite";
|
|
5
|
-
import vue from "@vitejs/plugin-vue";
|
|
6
|
-
import { createError } from "h3";
|
|
7
|
-
export async function buildExtensionWithVite(vueContent, extensionId) {
|
|
8
|
-
const buildId = `${extensionId}-${Date.now()}-${randomUUID()}`;
|
|
9
|
-
const tempDir = join(process.cwd(), ".temp-extension-builds", buildId);
|
|
10
|
-
const tempExtensionFile = join(tempDir, "extension.vue");
|
|
11
|
-
const tempEntryFile = join(tempDir, "entry.js");
|
|
12
|
-
try {
|
|
13
|
-
if (!existsSync(tempDir)) {
|
|
14
|
-
mkdirSync(tempDir, { recursive: true });
|
|
15
|
-
}
|
|
16
|
-
writeFileSync(tempExtensionFile, vueContent);
|
|
17
|
-
writeFileSync(tempEntryFile, `
|
|
18
|
-
import ExtensionComponent from './extension.vue'
|
|
19
|
-
export default ExtensionComponent
|
|
20
|
-
`);
|
|
21
|
-
await build({
|
|
22
|
-
root: tempDir,
|
|
23
|
-
build: {
|
|
24
|
-
lib: {
|
|
25
|
-
entry: tempEntryFile,
|
|
26
|
-
name: extensionId,
|
|
27
|
-
fileName: () => "extension.js",
|
|
28
|
-
formats: ["umd"]
|
|
29
|
-
},
|
|
30
|
-
outDir: join(tempDir, "dist"),
|
|
31
|
-
emptyOutDir: true,
|
|
32
|
-
write: true,
|
|
33
|
-
rollupOptions: {
|
|
34
|
-
external: ["vue"],
|
|
35
|
-
output: {
|
|
36
|
-
globals: {
|
|
37
|
-
vue: "Vue"
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
plugins: [vue()]
|
|
43
|
-
});
|
|
44
|
-
const compiledFile = join(tempDir, "dist", "extension.js");
|
|
45
|
-
const compiledCode = readFileSync(compiledFile, "utf-8");
|
|
46
|
-
return compiledCode;
|
|
47
|
-
} catch (error) {
|
|
48
|
-
throw createError({
|
|
49
|
-
statusCode: 500,
|
|
50
|
-
statusMessage: `Failed to build extension: ${error.message || "Unknown error"}`
|
|
51
|
-
});
|
|
52
|
-
} finally {
|
|
53
|
-
await cleanupTempDirectory(tempDir);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
async function cleanupTempDirectory(tempDir) {
|
|
57
|
-
try {
|
|
58
|
-
if (existsSync(tempDir)) {
|
|
59
|
-
const fs = await import("fs/promises");
|
|
60
|
-
await fs.rm(tempDir, { recursive: true, force: true });
|
|
61
|
-
}
|
|
62
|
-
} catch (cleanupError) {
|
|
63
|
-
}
|
|
64
|
-
}
|
|
File without changes
|
|
File without changes
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "crypto";
|
|
2
|
-
const EXTENSION_UUID_PATTERN = /^extension_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
3
|
-
export function autoAssignExtensionName(body) {
|
|
4
|
-
const currentExtensionId = body.extensionId || "";
|
|
5
|
-
if (!currentExtensionId || !EXTENSION_UUID_PATTERN.test(currentExtensionId)) {
|
|
6
|
-
const uuid = randomUUID();
|
|
7
|
-
body.extensionId = `extension_${uuid}`;
|
|
8
|
-
}
|
|
9
|
-
return body;
|
|
10
|
-
}
|
|
11
|
-
export function isValidExtensionId(extensionId) {
|
|
12
|
-
return EXTENSION_UUID_PATTERN.test(extensionId);
|
|
13
|
-
}
|
|
File without changes
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { createError } from "h3";
|
|
2
|
-
import { autoAssignExtensionName } from "./naming.mjs";
|
|
3
|
-
import { isProbablyVueSFC, assertValidVueSFC, assertValidJsBundleSyntax } from "./validation.mjs";
|
|
4
|
-
import { buildExtensionWithVite } from "./compiler.mjs";
|
|
5
|
-
export function isExtensionDefinitionPath(path) {
|
|
6
|
-
return path.includes("/extension_definition");
|
|
7
|
-
}
|
|
8
|
-
export async function processExtensionDefinition(body, method) {
|
|
9
|
-
if (method !== "POST" && method !== "PATCH") {
|
|
10
|
-
return { processedBody: body };
|
|
11
|
-
}
|
|
12
|
-
if (!body || typeof body.code !== "string") {
|
|
13
|
-
return { processedBody: body };
|
|
14
|
-
}
|
|
15
|
-
body = autoAssignExtensionName(body);
|
|
16
|
-
const code = body.code;
|
|
17
|
-
const extensionId = body.id || body.name || "extension_" + Date.now();
|
|
18
|
-
if (isProbablyVueSFC(code)) {
|
|
19
|
-
assertValidVueSFC(code);
|
|
20
|
-
try {
|
|
21
|
-
const compiledCode = await buildExtensionWithVite(code, body.extensionId);
|
|
22
|
-
body.compiledCode = compiledCode;
|
|
23
|
-
return { processedBody: body, compiledCode };
|
|
24
|
-
} catch (compileError) {
|
|
25
|
-
throw createError({
|
|
26
|
-
statusCode: 400,
|
|
27
|
-
statusMessage: compileError?.statusMessage || `Failed to build Vue SFC for ${extensionId}: ${compileError?.message || "Unknown error"}`
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
} else {
|
|
31
|
-
assertValidJsBundleSyntax(code);
|
|
32
|
-
return { processedBody: body };
|
|
33
|
-
}
|
|
34
|
-
}
|
|
File without changes
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { createError } from "h3";
|
|
2
|
-
export function isProbablyVueSFC(content) {
|
|
3
|
-
if (typeof content !== "string") return false;
|
|
4
|
-
const trimmed = content.trim();
|
|
5
|
-
if (!trimmed) return false;
|
|
6
|
-
const hasSfcTags = /<template[\s>]|<script[\s>]|<style[\s>]/i.test(trimmed);
|
|
7
|
-
const hasClosing = /<\/template>|<\/script>|<\/style>/i.test(trimmed);
|
|
8
|
-
return hasSfcTags && hasClosing;
|
|
9
|
-
}
|
|
10
|
-
export function assertValidVueSFC(content) {
|
|
11
|
-
const templateOpen = (content.match(/<template[^>]*>/g) || []).length;
|
|
12
|
-
const templateClose = (content.match(/<\/template>/g) || []).length;
|
|
13
|
-
const scriptOpen = (content.match(/<script[^>]*>/g) || []).length;
|
|
14
|
-
const scriptClose = (content.match(/<\/script>/g) || []).length;
|
|
15
|
-
const styleOpen = (content.match(/<style[^>]*>/g) || []).length;
|
|
16
|
-
const styleClose = (content.match(/<\/style>/g) || []).length;
|
|
17
|
-
if (templateOpen !== templateClose || scriptOpen !== scriptClose || styleOpen !== styleClose) {
|
|
18
|
-
throw createError({
|
|
19
|
-
statusCode: 400,
|
|
20
|
-
statusMessage: "Invalid Vue SFC: unbalanced tags"
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
if (templateOpen === 0 && scriptOpen === 0) {
|
|
24
|
-
throw createError({
|
|
25
|
-
statusCode: 400,
|
|
26
|
-
statusMessage: "Invalid Vue SFC: must have at least <template> or <script>"
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
if (scriptOpen > 0) {
|
|
30
|
-
const scriptContent = content.match(/<script[^>]*>([\s\S]*?)<\/script>/i);
|
|
31
|
-
if (scriptContent && scriptContent[1]) {
|
|
32
|
-
const script = scriptContent[1];
|
|
33
|
-
if (script.includes("export default") && !script.includes("{")) {
|
|
34
|
-
throw createError({
|
|
35
|
-
statusCode: 400,
|
|
36
|
-
statusMessage: "Invalid Vue SFC: script must have proper export default syntax"
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
export function assertValidJsBundleSyntax(code) {
|
|
43
|
-
const brackets = { "(": 0, ")": 0, "{": 0, "}": 0, "[": 0, "]": 0 };
|
|
44
|
-
for (const char of code) {
|
|
45
|
-
if (char in brackets) {
|
|
46
|
-
brackets[char]++;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
if (brackets["("] !== brackets[")"] || brackets["{"] !== brackets["}"] || brackets["["] !== brackets["]"]) {
|
|
50
|
-
throw createError({
|
|
51
|
-
statusCode: 400,
|
|
52
|
-
statusMessage: "Invalid JS syntax: unbalanced brackets"
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
if (!code.includes("export") && !code.includes("module.exports") && !code.includes("window.")) {
|
|
56
|
-
throw createError({
|
|
57
|
-
statusCode: 400,
|
|
58
|
-
statusMessage: "Invalid JS bundle: must have export statement or module.exports"
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
if (code.includes("function(") && !code.includes(")")) {
|
|
62
|
-
throw createError({
|
|
63
|
-
statusCode: 400,
|
|
64
|
-
statusMessage: "Invalid JS syntax: incomplete function declaration"
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
}
|
|
File without changes
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./extension/index.mjs";
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
defineEventHandler,
|
|
3
|
-
readBody,
|
|
4
|
-
getHeader,
|
|
5
|
-
createError,
|
|
6
|
-
} from "h3";
|
|
7
|
-
import { useRuntimeConfig } from "#imports";
|
|
8
|
-
import { $fetch } from "ofetch";
|
|
9
|
-
import {
|
|
10
|
-
processExtensionDefinition,
|
|
11
|
-
} from "../../../../utils/server/extension";
|
|
12
|
-
|
|
13
|
-
export default defineEventHandler(async (event) => {
|
|
14
|
-
const method = event.method;
|
|
15
|
-
|
|
16
|
-
try {
|
|
17
|
-
let body = await readBody(event);
|
|
18
|
-
|
|
19
|
-
const { processedBody, compiledCode } = await processExtensionDefinition(body, method);
|
|
20
|
-
body = processedBody;
|
|
21
|
-
|
|
22
|
-
const config = useRuntimeConfig();
|
|
23
|
-
const apiPath = event.path.replace("/enfyra/api", "");
|
|
24
|
-
const targetUrl = `${config.public?.enfyraSDK?.apiUrl}${apiPath}`;
|
|
25
|
-
|
|
26
|
-
const response = await $fetch(targetUrl, {
|
|
27
|
-
method: "PATCH",
|
|
28
|
-
headers: {
|
|
29
|
-
cookie: getHeader(event, "cookie") || "",
|
|
30
|
-
authorization: event.context.proxyHeaders?.authorization || "",
|
|
31
|
-
"Content-Type": "application/json",
|
|
32
|
-
},
|
|
33
|
-
body: body || undefined,
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
return response;
|
|
37
|
-
} catch (error: any) {
|
|
38
|
-
if (error.statusCode) {
|
|
39
|
-
throw error;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
throw createError({
|
|
43
|
-
statusCode: 500,
|
|
44
|
-
statusMessage:
|
|
45
|
-
error.message || `Failed to process extension definition ${method}`,
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
});
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
defineEventHandler,
|
|
3
|
-
readBody,
|
|
4
|
-
getHeader,
|
|
5
|
-
createError,
|
|
6
|
-
} from "h3";
|
|
7
|
-
import { useRuntimeConfig } from "#imports";
|
|
8
|
-
import { $fetch } from "ofetch";
|
|
9
|
-
import {
|
|
10
|
-
processExtensionDefinition,
|
|
11
|
-
} from "../../../utils/server/extension";
|
|
12
|
-
|
|
13
|
-
export default defineEventHandler(async (event) => {
|
|
14
|
-
const method = event.method;
|
|
15
|
-
|
|
16
|
-
try {
|
|
17
|
-
let body = await readBody(event);
|
|
18
|
-
|
|
19
|
-
const { processedBody, compiledCode } = await processExtensionDefinition(body, method);
|
|
20
|
-
body = processedBody;
|
|
21
|
-
|
|
22
|
-
const config = useRuntimeConfig();
|
|
23
|
-
const apiPath = event.path.replace("/enfyra/api", "");
|
|
24
|
-
const targetUrl = `${config.public?.enfyraSDK?.apiUrl}${apiPath}`;
|
|
25
|
-
|
|
26
|
-
const response = await $fetch(targetUrl, {
|
|
27
|
-
method: method as any,
|
|
28
|
-
headers: {
|
|
29
|
-
cookie: getHeader(event, "cookie") || "",
|
|
30
|
-
authorization: event.context.proxyHeaders?.authorization || "",
|
|
31
|
-
"Content-Type": "application/json",
|
|
32
|
-
},
|
|
33
|
-
body: body || undefined,
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
return response;
|
|
37
|
-
} catch (error: any) {
|
|
38
|
-
if (error.statusCode) {
|
|
39
|
-
throw error;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
throw createError({
|
|
43
|
-
statusCode: 500,
|
|
44
|
-
statusMessage:
|
|
45
|
-
error.message || `Failed to process extension definition ${method}`,
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
});
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "crypto";
|
|
2
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
3
|
-
import { join } from "path";
|
|
4
|
-
import { build } from "vite";
|
|
5
|
-
import vue from "@vitejs/plugin-vue";
|
|
6
|
-
import { createError } from "h3";
|
|
7
|
-
|
|
8
|
-
export async function buildExtensionWithVite(
|
|
9
|
-
vueContent: string,
|
|
10
|
-
extensionId: string
|
|
11
|
-
): Promise<string> {
|
|
12
|
-
const buildId = `${extensionId}-${Date.now()}-${randomUUID()}`;
|
|
13
|
-
const tempDir = join(process.cwd(), ".temp-extension-builds", buildId);
|
|
14
|
-
const tempExtensionFile = join(tempDir, "extension.vue");
|
|
15
|
-
const tempEntryFile = join(tempDir, "entry.js");
|
|
16
|
-
|
|
17
|
-
try {
|
|
18
|
-
if (!existsSync(tempDir)) {
|
|
19
|
-
mkdirSync(tempDir, { recursive: true });
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
writeFileSync(tempExtensionFile, vueContent);
|
|
23
|
-
writeFileSync(tempEntryFile, `
|
|
24
|
-
import ExtensionComponent from './extension.vue'
|
|
25
|
-
export default ExtensionComponent
|
|
26
|
-
`);
|
|
27
|
-
|
|
28
|
-
await build({
|
|
29
|
-
root: tempDir,
|
|
30
|
-
build: {
|
|
31
|
-
lib: {
|
|
32
|
-
entry: tempEntryFile,
|
|
33
|
-
name: extensionId,
|
|
34
|
-
fileName: () => "extension.js",
|
|
35
|
-
formats: ["umd"],
|
|
36
|
-
},
|
|
37
|
-
outDir: join(tempDir, "dist"),
|
|
38
|
-
emptyOutDir: true,
|
|
39
|
-
write: true,
|
|
40
|
-
rollupOptions: {
|
|
41
|
-
external: ["vue"],
|
|
42
|
-
output: {
|
|
43
|
-
globals: {
|
|
44
|
-
vue: "Vue",
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
plugins: [vue()],
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
const compiledFile = join(tempDir, "dist", "extension.js");
|
|
53
|
-
const compiledCode = readFileSync(compiledFile, "utf-8");
|
|
54
|
-
|
|
55
|
-
return compiledCode;
|
|
56
|
-
} catch (error: any) {
|
|
57
|
-
throw createError({
|
|
58
|
-
statusCode: 500,
|
|
59
|
-
statusMessage: `Failed to build extension: ${
|
|
60
|
-
error.message || "Unknown error"
|
|
61
|
-
}`,
|
|
62
|
-
});
|
|
63
|
-
} finally {
|
|
64
|
-
await cleanupTempDirectory(tempDir);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async function cleanupTempDirectory(tempDir: string): Promise<void> {
|
|
69
|
-
try {
|
|
70
|
-
if (existsSync(tempDir)) {
|
|
71
|
-
const fs = await import("fs/promises");
|
|
72
|
-
await fs.rm(tempDir, { recursive: true, force: true });
|
|
73
|
-
}
|
|
74
|
-
} catch (cleanupError) {
|
|
75
|
-
}
|
|
76
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "crypto";
|
|
2
|
-
|
|
3
|
-
const EXTENSION_UUID_PATTERN = /^extension_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
4
|
-
|
|
5
|
-
export function autoAssignExtensionName(body: any): any {
|
|
6
|
-
const currentExtensionId = body.extensionId || "";
|
|
7
|
-
|
|
8
|
-
if (!currentExtensionId || !EXTENSION_UUID_PATTERN.test(currentExtensionId)) {
|
|
9
|
-
const uuid = randomUUID();
|
|
10
|
-
body.extensionId = `extension_${uuid}`;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
return body;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function isValidExtensionId(extensionId: string): boolean {
|
|
17
|
-
return EXTENSION_UUID_PATTERN.test(extensionId);
|
|
18
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { createError } from "h3";
|
|
2
|
-
import { autoAssignExtensionName } from "./naming";
|
|
3
|
-
import { isProbablyVueSFC, assertValidVueSFC, assertValidJsBundleSyntax } from "./validation";
|
|
4
|
-
import { buildExtensionWithVite } from "./compiler";
|
|
5
|
-
|
|
6
|
-
export function isExtensionDefinitionPath(path: string): boolean {
|
|
7
|
-
return path.includes('/extension_definition');
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export async function processExtensionDefinition(
|
|
11
|
-
body: any,
|
|
12
|
-
method: string
|
|
13
|
-
): Promise<{ processedBody: any; compiledCode?: string }> {
|
|
14
|
-
if (method !== "POST" && method !== "PATCH") {
|
|
15
|
-
return { processedBody: body };
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
if (!body || typeof body.code !== "string") {
|
|
19
|
-
return { processedBody: body };
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
body = autoAssignExtensionName(body);
|
|
23
|
-
|
|
24
|
-
const code: string = body.code;
|
|
25
|
-
const extensionId = body.id || body.name || "extension_" + Date.now();
|
|
26
|
-
|
|
27
|
-
if (isProbablyVueSFC(code)) {
|
|
28
|
-
assertValidVueSFC(code);
|
|
29
|
-
|
|
30
|
-
try {
|
|
31
|
-
const compiledCode = await buildExtensionWithVite(code, body.extensionId);
|
|
32
|
-
body.compiledCode = compiledCode;
|
|
33
|
-
return { processedBody: body, compiledCode };
|
|
34
|
-
} catch (compileError: any) {
|
|
35
|
-
throw createError({
|
|
36
|
-
statusCode: 400,
|
|
37
|
-
statusMessage:
|
|
38
|
-
compileError?.statusMessage ||
|
|
39
|
-
`Failed to build Vue SFC for ${extensionId}: ${
|
|
40
|
-
compileError?.message || "Unknown error"
|
|
41
|
-
}`,
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
} else {
|
|
45
|
-
assertValidJsBundleSyntax(code);
|
|
46
|
-
return { processedBody: body };
|
|
47
|
-
}
|
|
48
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { createError } from "h3";
|
|
2
|
-
|
|
3
|
-
export function isProbablyVueSFC(content: string): boolean {
|
|
4
|
-
if (typeof content !== "string") return false;
|
|
5
|
-
const trimmed = content.trim();
|
|
6
|
-
if (!trimmed) return false;
|
|
7
|
-
|
|
8
|
-
const hasSfcTags = /<template[\s>]|<script[\s>]|<style[\s>]/i.test(trimmed);
|
|
9
|
-
const hasClosing = /<\/template>|<\/script>|<\/style>/i.test(trimmed);
|
|
10
|
-
|
|
11
|
-
return hasSfcTags && hasClosing;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function assertValidVueSFC(content: string): void {
|
|
15
|
-
const templateOpen = (content.match(/<template[^>]*>/g) || []).length;
|
|
16
|
-
const templateClose = (content.match(/<\/template>/g) || []).length;
|
|
17
|
-
const scriptOpen = (content.match(/<script[^>]*>/g) || []).length;
|
|
18
|
-
const scriptClose = (content.match(/<\/script>/g) || []).length;
|
|
19
|
-
const styleOpen = (content.match(/<style[^>]*>/g) || []).length;
|
|
20
|
-
const styleClose = (content.match(/<\/style>/g) || []).length;
|
|
21
|
-
|
|
22
|
-
if (
|
|
23
|
-
templateOpen !== templateClose ||
|
|
24
|
-
scriptOpen !== scriptClose ||
|
|
25
|
-
styleOpen !== styleClose
|
|
26
|
-
) {
|
|
27
|
-
throw createError({
|
|
28
|
-
statusCode: 400,
|
|
29
|
-
statusMessage: "Invalid Vue SFC: unbalanced tags",
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (templateOpen === 0 && scriptOpen === 0) {
|
|
34
|
-
throw createError({
|
|
35
|
-
statusCode: 400,
|
|
36
|
-
statusMessage: "Invalid Vue SFC: must have at least <template> or <script>",
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (scriptOpen > 0) {
|
|
41
|
-
const scriptContent = content.match(/<script[^>]*>([\s\S]*?)<\/script>/i);
|
|
42
|
-
if (scriptContent && scriptContent[1]) {
|
|
43
|
-
const script = scriptContent[1];
|
|
44
|
-
if (script.includes("export default") && !script.includes("{")) {
|
|
45
|
-
throw createError({
|
|
46
|
-
statusCode: 400,
|
|
47
|
-
statusMessage: "Invalid Vue SFC: script must have proper export default syntax",
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function assertValidJsBundleSyntax(code: string): void {
|
|
55
|
-
const brackets = { "(": 0, ")": 0, "{": 0, "}": 0, "[": 0, "]": 0 };
|
|
56
|
-
|
|
57
|
-
for (const char of code) {
|
|
58
|
-
if (char in brackets) {
|
|
59
|
-
brackets[char as keyof typeof brackets]++;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (
|
|
64
|
-
brackets["("] !== brackets[")"] ||
|
|
65
|
-
brackets["{"] !== brackets["}"] ||
|
|
66
|
-
brackets["["] !== brackets["]"]
|
|
67
|
-
) {
|
|
68
|
-
throw createError({
|
|
69
|
-
statusCode: 400,
|
|
70
|
-
statusMessage: "Invalid JS syntax: unbalanced brackets",
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (
|
|
75
|
-
!code.includes("export") &&
|
|
76
|
-
!code.includes("module.exports") &&
|
|
77
|
-
!code.includes("window.")
|
|
78
|
-
) {
|
|
79
|
-
throw createError({
|
|
80
|
-
statusCode: 400,
|
|
81
|
-
statusMessage: "Invalid JS bundle: must have export statement or module.exports",
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (code.includes("function(") && !code.includes(")")) {
|
|
86
|
-
throw createError({
|
|
87
|
-
statusCode: 400,
|
|
88
|
-
statusMessage: "Invalid JS syntax: incomplete function declaration",
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./extension/index";
|