@croct/plug-nuxt 0.1.1 → 0.2.1

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.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.0.0"
6
6
  },
7
- "version": "0.1.1",
7
+ "version": "0.2.1",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "0.8.4",
10
10
  "unbuild": "2.0.0"
package/dist/module.mjs CHANGED
@@ -68,7 +68,7 @@ const module = defineNuxtModule({
68
68
  getContents: () => clientOptionsCode
69
69
  });
70
70
  nuxt.options.alias["#croct/client-options"] = join(nuxt.options.buildDir, "croct/client-options");
71
- addPlugin(resolver.resolve("./runtime/plugin.client"));
71
+ addPlugin(resolver.resolve("./runtime/plugin"));
72
72
  addServerHandler({
73
73
  handler: resolver.resolve("./runtime/server/middleware/croct"),
74
74
  middleware: true
@@ -113,6 +113,13 @@ function generateResolversModule(options, appResolver) {
113
113
  } else {
114
114
  lines.push("export const userIdResolver = undefined;");
115
115
  }
116
+ if (options.credentialsResolver !== void 0) {
117
+ lines.push(
118
+ `export { default as credentialsResolver } from '${appResolver.resolve(options.credentialsResolver)}';`
119
+ );
120
+ } else {
121
+ lines.push("export const credentialsResolver = undefined;");
122
+ }
116
123
  return lines.join("\n");
117
124
  }
118
125
  function generateClientOptionsModule(options, appResolver) {
@@ -1,5 +1,5 @@
1
1
  import { defineComponent } from "vue";
2
- import { useAsyncData } from "#app";
2
+ import { useAsyncData, useRequestFetch } from "#app";
3
3
  export default defineComponent({
4
4
  name: "Personalization",
5
5
  props: {
@@ -28,7 +28,7 @@ export default defineComponent({
28
28
  };
29
29
  const { data, pending, error } = await useAsyncData(
30
30
  `croct:pers:${props.query}:${JSON.stringify(options)}`,
31
- () => $fetch("/api/_croct/evaluate", {
31
+ () => useRequestFetch()("/api/_croct/evaluate", {
32
32
  method: "POST",
33
33
  body: { query: props.query, ...options }
34
34
  })
@@ -1,5 +1,5 @@
1
1
  import { defineComponent } from "vue";
2
- import { useAsyncData } from "#app";
2
+ import { useAsyncData, useRequestFetch } from "#app";
3
3
  import { resolveLocale } from "../utils/locale.js";
4
4
  export default defineComponent({
5
5
  name: "Slot",
@@ -30,7 +30,7 @@ export default defineComponent({
30
30
  };
31
31
  const { data, error } = await useAsyncData(
32
32
  `croct:slot:${props.id}:${JSON.stringify(options)}`,
33
- () => $fetch("/api/_croct/content", {
33
+ () => useRequestFetch()("/api/_croct/content", {
34
34
  method: "POST",
35
35
  body: { slotId: props.id, ...options }
36
36
  })
@@ -1,4 +1,4 @@
1
- import { useAsyncData } from "#app";
1
+ import { useAsyncData, useRequestFetch } from "#app";
2
2
  import { resolveLocale } from "../utils/locale.js";
3
3
  function useContentNuxt(slotId, options = {}) {
4
4
  const { preferredLocale, ...rest } = options;
@@ -10,7 +10,7 @@ function useContentNuxt(slotId, options = {}) {
10
10
  const cacheKey = `croct:content:${slotId}:${JSON.stringify(resolvedOptions)}`;
11
11
  return useAsyncData(
12
12
  cacheKey,
13
- () => $fetch("/api/_croct/content", {
13
+ () => useRequestFetch()("/api/_croct/content", {
14
14
  method: "POST",
15
15
  body: { slotId, ...resolvedOptions }
16
16
  })
@@ -1,9 +1,9 @@
1
- import { useAsyncData } from "#app";
1
+ import { useAsyncData, useRequestFetch } from "#app";
2
2
  function useEvaluationNuxt(query, options = {}) {
3
3
  const cacheKey = `croct:eval:${query}:${JSON.stringify(options)}`;
4
4
  return useAsyncData(
5
5
  cacheKey,
6
- () => $fetch("/api/_croct/evaluate", {
6
+ () => useRequestFetch()("/api/_croct/evaluate", {
7
7
  method: "POST",
8
8
  body: { query, ...options }
9
9
  })
@@ -1,10 +1,14 @@
1
1
  import { createCroct } from "@croct/plug-vue";
2
- import { defineNuxtPlugin, useRuntimeConfig } from "#app";
2
+ import { defineNuxtPlugin, useRuntimeConfig, useRequestEvent, useState } from "#app";
3
3
  import { urlSanitizer } from "#croct/client-options";
4
4
  export default defineNuxtPlugin((nuxtApp) => {
5
5
  const config = useRuntimeConfig().public.croct;
6
+ const appId = useState("croct:app-id", () => {
7
+ const event = useRequestEvent();
8
+ return event?.context.croctCredentials?.appId ?? config.appId;
9
+ });
6
10
  const plugin = createCroct({
7
- appId: config.appId,
11
+ appId: appId.value !== void 0 && appId.value !== "" ? appId.value : config.appId,
8
12
  disableCidMirroring: true,
9
13
  ...config.debug === true ? { debug: true } : {},
10
14
  ...config.test === true ? { test: true } : {},
@@ -2,5 +2,6 @@ import { useEvent } from "#imports";
2
2
  import { issueToken } from "../utils/security.js";
3
3
  import { setUserTokenCookie } from "../utils/cookie.js";
4
4
  export async function anonymize() {
5
- setUserTokenCookie(useEvent(), await issueToken());
5
+ const event = useEvent();
6
+ setUserTokenCookie(event, await issueToken(null, void 0, event));
6
7
  }
@@ -8,13 +8,13 @@ export async function evaluate(query, options = {}) {
8
8
  const context = event.context.croct;
9
9
  if (context === void 0) {
10
10
  throw new Error(
11
- "Croct's request context is missing. Make sure the @croct/plug-nuxt module is installed in your nuxt.config.ts. For help, see: https://croct.help/sdk/nuxt/missing-middleware"
11
+ "Croct's request context is missing. Make sure the @croct/plug-nuxt module is installed in your nuxt.config.ts. For help, see: https://croct.help/sdk/nuxt/missing-module"
12
12
  );
13
13
  }
14
14
  const config = useRuntimeConfig();
15
15
  const timeout = config.public.croct.defaultFetchTimeout;
16
16
  return executeQuery(query, {
17
- apiKey: getApiKey(),
17
+ apiKey: getApiKey(event),
18
18
  clientIp: context.clientIp ?? "127.0.0.1",
19
19
  ...context.previewToken !== void 0 ? { previewToken: context.previewToken } : {},
20
20
  ...context.userToken !== void 0 ? { userToken: context.userToken } : {},
@@ -8,14 +8,14 @@ export async function fetchContent(slotId, options = {}) {
8
8
  const context = event.context.croct;
9
9
  if (context === void 0) {
10
10
  throw new Error(
11
- "Croct's request context is missing. Make sure the @croct/plug-nuxt module is installed in your nuxt.config.ts. For help, see: https://croct.help/sdk/nuxt/missing-middleware"
11
+ "Croct's request context is missing. Make sure the @croct/plug-nuxt module is installed in your nuxt.config.ts. For help, see: https://croct.help/sdk/nuxt/missing-module"
12
12
  );
13
13
  }
14
14
  const config = useRuntimeConfig();
15
15
  const timeout = config.public.croct.defaultFetchTimeout;
16
16
  const { logger, ...rest } = options;
17
17
  const commonOptions = {
18
- apiKey: getApiKey(),
18
+ apiKey: getApiKey(event),
19
19
  ...typeof config.public.croct.baseEndpointUrl === "string" && config.public.croct.baseEndpointUrl !== "" ? { baseEndpointUrl: config.public.croct.baseEndpointUrl } : {},
20
20
  ...typeof timeout === "number" && timeout > 0 ? { timeout } : {},
21
21
  logger: logger ?? FilteredLogger.include(new ConsoleLogger(), ["warn", "error"])
@@ -2,5 +2,6 @@ import { useEvent } from "#imports";
2
2
  import { issueToken } from "../utils/security.js";
3
3
  import { setUserTokenCookie } from "../utils/cookie.js";
4
4
  export async function identify(userId) {
5
- setUserTokenCookie(useEvent(), await issueToken(userId));
5
+ const event = useEvent();
6
+ setUserTokenCookie(event, await issueToken(userId, void 0, event));
6
7
  }
@@ -1,4 +1,11 @@
1
1
  import type {H3Event} from 'h3';
2
2
 
3
+ export type CroctCredentials = {
4
+ appId: string,
5
+ apiKey: string,
6
+ };
7
+
3
8
  export type UserIdResolver = (event: H3Event) => Promise<string | null> | string | null;
4
9
  export type LocaleResolver = (event: H3Event) => Promise<string | null> | string | null;
10
+ export type CredentialsResolver = (event: H3Event) =>
11
+ Promise<CroctCredentials | null> | CroctCredentials | null;
@@ -10,7 +10,7 @@ import {
10
10
  import { Token } from "@croct/sdk/token";
11
11
  import { base64UrlDecode } from "@croct/sdk/base64Url";
12
12
  import { useRuntimeConfig } from "#imports";
13
- import { localeResolver, userIdResolver } from "#croct/resolvers";
13
+ import { credentialsResolver, localeResolver, userIdResolver } from "#croct/resolvers";
14
14
  import { setUserTokenCookie, getProductionDefaults } from "../utils/cookie.js";
15
15
  import { getAuthenticationKey, isUserTokenAuthenticationEnabled, issueToken } from "../utils/security.js";
16
16
  const CLIENT_ID_PATTERN = /^(?:[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}|[a-f0-9]{32})$/;
@@ -30,6 +30,7 @@ export default defineEventHandler(async (event) => {
30
30
  }
31
31
  const config = useRuntimeConfig();
32
32
  const { cookie } = config.public.croct;
33
+ event.context.croctCredentials = await resolveCredentials(event);
33
34
  const clientId = resolveClientId(getCookie(event, cookie.clientId.name));
34
35
  const userToken = await resolveUserToken(event, getCookie(event, cookie.userToken.name));
35
36
  const preferredLocale = await resolvePreferredLocale(event, config.public.croct.defaultPreferredLocale);
@@ -75,8 +76,26 @@ function resolveClientId(cookieValue) {
75
76
  }
76
77
  return crypto.randomUUID();
77
78
  }
79
+ async function resolveCredentials(event) {
80
+ const config = useRuntimeConfig();
81
+ const fallback = {
82
+ appId: config.public.croct.appId,
83
+ apiKey: config.croct.apiKey
84
+ };
85
+ if (credentialsResolver === void 0) {
86
+ return fallback;
87
+ }
88
+ const credentials = await credentialsResolver(event);
89
+ if (credentials === null || credentials === void 0) {
90
+ return fallback;
91
+ }
92
+ return {
93
+ appId: credentials.appId ?? fallback.appId,
94
+ apiKey: credentials.apiKey ?? fallback.apiKey
95
+ };
96
+ }
78
97
  async function resolveUserToken(event, cookieValue) {
79
- const { appId } = useRuntimeConfig().public.croct;
98
+ const { appId } = event.context.croctCredentials;
80
99
  let token = null;
81
100
  if (cookieValue !== void 0) {
82
101
  try {
@@ -85,15 +104,15 @@ async function resolveUserToken(event, cookieValue) {
85
104
  }
86
105
  }
87
106
  const userId = userIdResolver !== void 0 ? await userIdResolver(event) : void 0;
88
- if (token === null || isUserTokenAuthenticationEnabled() && !token.isSigned() || !token.isValidNow() || userId !== void 0 && (userId === null ? !token.isAnonymous() : !token.isSubject(userId))) {
89
- return issueToken(userId ?? null);
107
+ if (token === null || isUserTokenAuthenticationEnabled(event) && !token.isSigned() || !token.isValidNow() || userId !== void 0 && (userId === null ? !token.isAnonymous() : !token.isSubject(userId))) {
108
+ return issueToken(userId ?? null, void 0, event);
90
109
  }
91
110
  const tokenAppId = token.getApplicationId();
92
111
  if (tokenAppId !== null && tokenAppId !== appId) {
93
- return issueToken(userId ?? null);
112
+ return issueToken(userId ?? null, void 0, event);
94
113
  }
95
- if (token.isSigned() && !await token.matchesKeyId(getAuthenticationKey())) {
96
- return issueToken(token.getSubject(), token.getTokenId() ?? void 0);
114
+ if (token.isSigned() && !await token.matchesKeyId(getAuthenticationKey(event))) {
115
+ return issueToken(token.getSubject(), token.getTokenId() ?? void 0, event);
97
116
  }
98
117
  return token;
99
118
  }
@@ -1,6 +1,7 @@
1
+ import type { H3Event } from 'h3';
1
2
  import { ApiKey } from '@croct/sdk/apiKey';
2
3
  import { Token } from '@croct/sdk/token';
3
- export declare function getApiKey(): ApiKey;
4
- export declare function getAuthenticationKey(): ApiKey;
5
- export declare function isUserTokenAuthenticationEnabled(): boolean;
6
- export declare function issueToken(userId?: string | null, tokenId?: string): Promise<Token>;
4
+ export declare function getApiKey(event?: H3Event): ApiKey;
5
+ export declare function getAuthenticationKey(event?: H3Event): ApiKey;
6
+ export declare function isUserTokenAuthenticationEnabled(event?: H3Event): boolean;
7
+ export declare function issueToken(userId?: string | null, tokenId?: string, event?: H3Event): Promise<Token>;
@@ -1,11 +1,11 @@
1
1
  import { ApiKey } from "@croct/sdk/apiKey";
2
2
  import { Token } from "@croct/sdk/token";
3
3
  import { useRuntimeConfig } from "#imports";
4
- export function getApiKey() {
5
- const { apiKey } = useRuntimeConfig().croct;
4
+ export function getApiKey(event) {
5
+ const apiKey = resolveApiKey(event);
6
6
  if (apiKey === "") {
7
7
  throw new Error(
8
- "Croct's API key is missing. Did you forget to set the NUXT_CROCT_API_KEY environment variable? For help, see: https://croct.help/sdk/nuxt/missing-environment-variable"
8
+ "Croct's API key is missing. Did you forget to set the NUXT_CROCT_API_KEY environment variable? For help, see: https://croct.help/sdk/nuxt/missing-api-key"
9
9
  );
10
10
  }
11
11
  try {
@@ -16,8 +16,8 @@ export function getApiKey() {
16
16
  );
17
17
  }
18
18
  }
19
- export function getAuthenticationKey() {
20
- const apiKey = getApiKey();
19
+ export function getAuthenticationKey(event) {
20
+ const apiKey = getApiKey(event);
21
21
  if (!apiKey.hasPrivateKey()) {
22
22
  throw new Error(
23
23
  "Croct's API key does not have a private key. Please generate an API key with authenticate permissions and update the NUXT_CROCT_API_KEY environment variable."
@@ -25,16 +25,19 @@ export function getAuthenticationKey() {
25
25
  }
26
26
  return apiKey;
27
27
  }
28
- export function isUserTokenAuthenticationEnabled() {
29
- const config = useRuntimeConfig().croct;
30
- return config.apiKey !== "" && config.disableUserTokenAuthentication !== true;
28
+ export function isUserTokenAuthenticationEnabled(event) {
29
+ return resolveApiKey(event) !== "" && useRuntimeConfig().croct.disableUserTokenAuthentication !== true;
31
30
  }
32
- export function issueToken(userId = null, tokenId) {
33
- const config = useRuntimeConfig();
34
- const { appId } = config.public.croct;
35
- const token = Token.issue(appId, userId).withDuration(config.croct.tokenDuration);
36
- if (isUserTokenAuthenticationEnabled()) {
37
- return token.withTokenId(tokenId ?? crypto.randomUUID()).signedWith(getAuthenticationKey());
31
+ export function issueToken(userId = null, tokenId, event) {
32
+ const token = Token.issue(resolveAppId(event), userId).withDuration(useRuntimeConfig().croct.tokenDuration);
33
+ if (isUserTokenAuthenticationEnabled(event)) {
34
+ return token.withTokenId(tokenId ?? crypto.randomUUID()).signedWith(getAuthenticationKey(event));
38
35
  }
39
36
  return Promise.resolve(token);
40
37
  }
38
+ function resolveAppId(event) {
39
+ return event?.context.croctCredentials?.appId ?? useRuntimeConfig().public.croct.appId;
40
+ }
41
+ function resolveApiKey(event) {
42
+ return event?.context.croctCredentials?.apiKey ?? useRuntimeConfig().croct.apiKey;
43
+ }
@@ -2,4 +2,4 @@ export type * from '@croct/plug/sdk/json';
2
2
  export type * from '@croct/plug/slot';
3
3
  export type * from '@croct/plug/component';
4
4
  export type { UrlSanitizer } from '@croct/sdk/tab';
5
- export type { UserIdResolver, LocaleResolver } from './server/croct-resolvers.js';
5
+ export type { UserIdResolver, LocaleResolver, CredentialsResolver, CroctCredentials, } from './server/croct-resolvers.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@croct/plug-nuxt",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "Nuxt module to plug your Nuxt 3 applications into Croct.",
5
5
  "author": {
6
6
  "name": "Croct",
@@ -48,6 +48,8 @@
48
48
  "lint": "eslint .",
49
49
  "test": "vitest run",
50
50
  "test:watch": "vitest watch",
51
+ "test:e2e": "playwright test",
52
+ "test:e2e:prerender": "playwright test -c playwright.prerender.config.ts",
51
53
  "validate": "tsc --noEmit"
52
54
  },
53
55
  "peerDependencies": {