@open-xamu-co/firebase-nuxt 1.0.0 → 1.1.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 (133) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +0 -14
  3. package/dist/module.d.mts +50 -0
  4. package/dist/module.json +12 -0
  5. package/dist/module.mjs +137 -0
  6. package/dist/runtime/client/types/entities/base.d.ts +29 -0
  7. package/dist/runtime/client/types/entities/base.js +0 -0
  8. package/dist/runtime/client/types/entities/instance.d.ts +42 -0
  9. package/dist/runtime/client/types/entities/instance.js +0 -0
  10. package/dist/runtime/client/types/entities/logs.d.ts +9 -0
  11. package/dist/runtime/client/types/entities/logs.js +0 -0
  12. package/dist/runtime/client/types/entities/user.d.ts +27 -0
  13. package/dist/runtime/client/types/entities/user.js +0 -0
  14. package/dist/runtime/client/types/firestore.d.ts +55 -0
  15. package/dist/runtime/client/types/firestore.js +0 -0
  16. package/dist/runtime/client/types/index.d.ts +5 -0
  17. package/dist/runtime/client/types/index.js +5 -0
  18. package/dist/runtime/client/utils/locale.d.ts +185 -0
  19. package/dist/runtime/client/utils/locale.js +63 -0
  20. package/dist/runtime/client/utils/logger.d.ts +14 -0
  21. package/dist/runtime/client/utils/logger.js +65 -0
  22. package/dist/runtime/client/utils/resolver.d.ts +14 -0
  23. package/dist/runtime/client/utils/resolver.js +88 -0
  24. package/dist/runtime/components/ValueCellphone.d.vue.ts +24 -0
  25. package/dist/runtime/components/ValueCellphone.vue +21 -0
  26. package/dist/runtime/components/ValueCellphone.vue.d.ts +24 -0
  27. package/dist/runtime/components/ValueID.d.vue.ts +18 -0
  28. package/dist/runtime/components/ValueID.vue +14 -0
  29. package/dist/runtime/components/ValueID.vue.d.ts +18 -0
  30. package/dist/runtime/components/ValueIP.d.vue.ts +18 -0
  31. package/dist/runtime/components/ValueIP.vue +28 -0
  32. package/dist/runtime/components/ValueIP.vue.d.ts +18 -0
  33. package/dist/runtime/components/ValueLocation.d.vue.ts +21 -0
  34. package/dist/runtime/components/ValueLocation.vue +20 -0
  35. package/dist/runtime/components/ValueLocation.vue.d.ts +21 -0
  36. package/dist/runtime/components/ValuePrice.d.vue.ts +11 -0
  37. package/dist/runtime/components/ValuePrice.vue +12 -0
  38. package/dist/runtime/components/ValuePrice.vue.d.ts +11 -0
  39. package/dist/runtime/composables/firestore/auth.d.ts +4 -0
  40. package/dist/runtime/composables/firestore/auth.js +46 -0
  41. package/dist/runtime/composables/firestore/index.d.ts +2 -0
  42. package/dist/runtime/composables/firestore/index.js +2 -0
  43. package/dist/runtime/composables/firestore/write.d.ts +21 -0
  44. package/dist/runtime/composables/firestore/write.js +279 -0
  45. package/dist/runtime/composables/index.d.ts +4 -0
  46. package/dist/runtime/composables/index.js +4 -0
  47. package/dist/runtime/composables/store/app.d.ts +82 -0
  48. package/dist/runtime/composables/store/app.js +90 -0
  49. package/dist/runtime/composables/store/instance.d.ts +57 -0
  50. package/dist/runtime/composables/store/instance.js +94 -0
  51. package/dist/runtime/composables/store/session.d.ts +41 -0
  52. package/dist/runtime/composables/store/session.js +89 -0
  53. package/dist/runtime/composables/useAppLogger.d.ts +2 -0
  54. package/dist/runtime/composables/useAppLogger.js +8 -0
  55. package/dist/runtime/composables/useFilesUpload.d.ts +19 -0
  56. package/dist/runtime/composables/useFilesUpload.js +110 -0
  57. package/dist/runtime/composables/usePrice.d.ts +9 -0
  58. package/dist/runtime/composables/usePrice.js +6 -0
  59. package/dist/runtime/composables/useQuery.d.ts +12 -0
  60. package/dist/runtime/composables/useQuery.js +33 -0
  61. package/dist/runtime/composables/utils.d.ts +14 -0
  62. package/dist/runtime/composables/utils.js +65 -0
  63. package/dist/runtime/functions/types/entities/base.d.ts +56 -0
  64. package/dist/runtime/functions/types/entities/base.js +0 -0
  65. package/dist/runtime/functions/types/entities/instance.d.ts +73 -0
  66. package/dist/runtime/functions/types/entities/instance.js +0 -0
  67. package/dist/runtime/functions/types/entities/logs.d.ts +15 -0
  68. package/dist/runtime/functions/types/entities/logs.js +0 -0
  69. package/dist/runtime/functions/types/entities/user.d.ts +13 -0
  70. package/dist/runtime/functions/types/entities/user.js +0 -0
  71. package/dist/runtime/functions/types/index.d.ts +4 -0
  72. package/dist/runtime/functions/types/index.js +4 -0
  73. package/dist/runtime/functions/utils/encrypt.d.ts +24 -0
  74. package/dist/runtime/functions/utils/encrypt.js +17 -0
  75. package/dist/runtime/functions/utils/enums.d.ts +40 -0
  76. package/dist/runtime/functions/utils/enums.js +24 -0
  77. package/dist/runtime/functions/utils/event.d.ts +71 -0
  78. package/dist/runtime/functions/utils/event.js +91 -0
  79. package/dist/runtime/functions/utils/firebase.d.ts +7 -0
  80. package/dist/runtime/functions/utils/firebase.js +21 -0
  81. package/dist/runtime/functions/utils/logger.d.ts +3 -0
  82. package/dist/runtime/functions/utils/logger.js +17 -0
  83. package/dist/runtime/functions/utils/logs.d.ts +10 -0
  84. package/dist/runtime/functions/utils/logs.js +47 -0
  85. package/dist/runtime/functions/utils/price.d.ts +12 -0
  86. package/dist/runtime/functions/utils/price.js +26 -0
  87. package/dist/runtime/functions/utils/queue.d.ts +4 -0
  88. package/dist/runtime/functions/utils/queue.js +11 -0
  89. package/dist/runtime/functions/utils/search.d.ts +15 -0
  90. package/dist/runtime/functions/utils/search.js +38 -0
  91. package/dist/runtime/functions/utils/slugs.d.ts +2 -0
  92. package/dist/runtime/functions/utils/slugs.js +16 -0
  93. package/dist/runtime/plugins/firebase-setup.d.ts +26 -0
  94. package/dist/runtime/plugins/firebase-setup.js +35 -0
  95. package/dist/runtime/plugins/loaded.client.d.ts +2 -0
  96. package/dist/runtime/plugins/loaded.client.js +44 -0
  97. package/dist/runtime/plugins/scrollBehavior.client.d.ts +2 -0
  98. package/dist/runtime/plugins/scrollBehavior.client.js +20 -0
  99. package/dist/runtime/providers/firebase.d.ts +2 -0
  100. package/dist/runtime/providers/firebase.js +7 -0
  101. package/dist/runtime/public/js/file-upload.d.ts +1 -0
  102. package/dist/runtime/public/js/file-upload.js +67 -0
  103. package/dist/runtime/public/sample-loading.png +0 -0
  104. package/dist/runtime/public/sample-missing.png +0 -0
  105. package/dist/runtime/public/sample.png +0 -0
  106. package/dist/runtime/server/api/all-collection-document.get.d.ts +7 -0
  107. package/dist/runtime/server/api/all-collection-document.get.js +56 -0
  108. package/dist/runtime/server/api/all-collection.get.d.ts +8 -0
  109. package/dist/runtime/server/api/all-collection.get.js +67 -0
  110. package/dist/runtime/server/api/media.get.d.ts +8 -0
  111. package/dist/runtime/server/api/media.get.js +114 -0
  112. package/dist/runtime/server/middleware/0.hotlinking.d.ts +5 -0
  113. package/dist/runtime/server/middleware/0.hotlinking.js +28 -0
  114. package/dist/runtime/server/middleware/1.context.d.ts +5 -0
  115. package/dist/runtime/server/middleware/1.context.js +108 -0
  116. package/dist/runtime/server/types/index.d.ts +34 -0
  117. package/dist/runtime/server/types/index.js +0 -0
  118. package/dist/runtime/server/utils/auth.d.ts +10 -0
  119. package/dist/runtime/server/utils/auth.js +31 -0
  120. package/dist/runtime/server/utils/cache.d.ts +21 -0
  121. package/dist/runtime/server/utils/cache.js +20 -0
  122. package/dist/runtime/server/utils/environment.d.ts +45 -0
  123. package/dist/runtime/server/utils/environment.js +50 -0
  124. package/dist/runtime/server/utils/firebase.d.ts +9 -0
  125. package/dist/runtime/server/utils/firebase.js +13 -0
  126. package/dist/runtime/server/utils/firestore.d.ts +27 -0
  127. package/dist/runtime/server/utils/firestore.js +137 -0
  128. package/dist/runtime/server/utils/guards.d.ts +15 -0
  129. package/dist/runtime/server/utils/guards.js +23 -0
  130. package/dist/runtime/server/utils/instance.d.ts +21 -0
  131. package/dist/runtime/server/utils/instance.js +68 -0
  132. package/dist/types.d.mts +7 -0
  133. package/package.json +17 -17
@@ -0,0 +1,56 @@
1
+ import { createError, getRouterParam, isError, setResponseStatus } from "h3";
2
+ import { defineConditionallyCachedEventHandler } from "../utils/cache.js";
3
+ import { debugFirebaseServer, resolveServerDocumentRefs } from "../utils/firestore.js";
4
+ import { apiLogger, getServerFirebase } from "../utils/firebase.js";
5
+ import { readCollection, readInstanceCollection } from "#internal/firebase-nuxt";
6
+ export default defineConditionallyCachedEventHandler(
7
+ async (event) => {
8
+ const { firebaseFirestore } = getServerFirebase("api:all:[collectionId]:[documentId]");
9
+ const { currentInstanceRef } = event.context;
10
+ try {
11
+ const collectionId = getRouterParam(event, "collectionId");
12
+ const documentId = getRouterParam(event, "documentId");
13
+ debugFirebaseServer(
14
+ event,
15
+ "api:all:[collectionId]:[documentId]",
16
+ collectionId,
17
+ documentId
18
+ );
19
+ if (!collectionId || !documentId) {
20
+ throw createError({
21
+ statusCode: 400,
22
+ statusMessage: `collectionId & documentId are required`
23
+ });
24
+ }
25
+ let collectionRef = firebaseFirestore.collection(collectionId);
26
+ if (event.path.startsWith("/api/instance/all")) {
27
+ if (!readInstanceCollection(collectionId, event.context)) {
28
+ throw createError({
29
+ statusCode: 401,
30
+ statusMessage: `Can't get "instance/${collectionId}" document`
31
+ });
32
+ } else if (!currentInstanceRef) {
33
+ throw createError({ statusCode: 401, statusMessage: "Missing instance" });
34
+ }
35
+ collectionRef = currentInstanceRef.collection(collectionId);
36
+ } else if (!readCollection(collectionId, event.context)) {
37
+ throw createError({
38
+ statusCode: 401,
39
+ statusMessage: `Can't get "${collectionId}" document`
40
+ });
41
+ }
42
+ const documentsRef = collectionRef.doc(documentId);
43
+ const documentSnapshot = await documentsRef.get();
44
+ return resolveServerDocumentRefs(event, documentSnapshot, collectionId);
45
+ } catch (err) {
46
+ if (isError(err)) {
47
+ apiLogger(event, "api:all:[collectionId]:[documentId]", err.message, err);
48
+ return setResponseStatus(event, err.statusCode || 500, err.statusMessage);
49
+ }
50
+ throw err;
51
+ }
52
+ },
53
+ {
54
+ instanceOnly: false
55
+ }
56
+ );
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Get the edges from a given instance sub-collection
3
+ *
4
+ * @auth guest
5
+ * @order createdAt
6
+ */
7
+ declare const _default: import("../types/index.js").CachedEventHandler<import("h3").EventHandlerRequest, Promise<void | import("@open-xamu-co/ui-common-types").iPageEdge<FirebaseFirestore.DocumentData, string>[] | import("@open-xamu-co/ui-common-types").iPage<FirebaseFirestore.DocumentData, string>>>;
8
+ export default _default;
@@ -0,0 +1,67 @@
1
+ import { FieldPath } from "firebase-admin/firestore";
2
+ import { createError, getQuery, getRouterParam, isError, setResponseStatus } from "h3";
3
+ import { defineConditionallyCachedEventHandler } from "../utils/cache.js";
4
+ import { getBoolean } from "../utils/guards.js";
5
+ import { getDocumentId } from "../../client/utils/resolver.js";
6
+ import {
7
+ debugFirebaseServer,
8
+ getEdgesPage,
9
+ getOrderedQuery,
10
+ getQueryAsEdges
11
+ } from "../utils/firestore.js";
12
+ import { apiLogger, getServerFirebase } from "../utils/firebase.js";
13
+ import { readCollection, readInstanceCollection } from "#internal/firebase-nuxt";
14
+ export default defineConditionallyCachedEventHandler(
15
+ async (event) => {
16
+ const fieldDocument = FieldPath.documentId();
17
+ const { firebaseFirestore } = getServerFirebase("api:all:[collectionId]");
18
+ const { currentInstanceRef } = event.context;
19
+ try {
20
+ const params = getQuery(event);
21
+ const page = getBoolean(params.page);
22
+ const collectionId = getRouterParam(event, "collectionId");
23
+ debugFirebaseServer(event, "api:all:[collectionId]", collectionId);
24
+ if (!collectionId) {
25
+ throw createError({ statusCode: 400, statusMessage: `collectionId is required` });
26
+ }
27
+ let query = firebaseFirestore.collection(collectionId);
28
+ if (event.path.startsWith("/api/instance/all")) {
29
+ if (!readInstanceCollection(collectionId, event.context)) {
30
+ throw createError({
31
+ statusCode: 401,
32
+ statusMessage: `Can't list "instance/${collectionId}"`
33
+ });
34
+ } else if (!currentInstanceRef) {
35
+ throw createError({ statusCode: 401, statusMessage: "Missing instance" });
36
+ }
37
+ query = currentInstanceRef.collection(collectionId);
38
+ } else if (!readCollection(collectionId, event.context)) {
39
+ throw createError({
40
+ statusCode: 401,
41
+ statusMessage: `Can't list "${collectionId}"`
42
+ });
43
+ }
44
+ if (params.include) {
45
+ let include = Array.isArray(params.include) ? params.include : [params.include];
46
+ include = include.filter((uid) => uid && !getBoolean(uid)).map(getDocumentId);
47
+ debugFirebaseServer(event, "api:all:[collectionId]:filtered", include);
48
+ if (!include.length) return [];
49
+ query = query.orderBy(fieldDocument).where(fieldDocument, "in", include);
50
+ return getQueryAsEdges(event, query);
51
+ }
52
+ query = getOrderedQuery(event, query);
53
+ if (page) return getEdgesPage(event, query);
54
+ const first = Math.min(Number(params.first) || 10, 100);
55
+ return getQueryAsEdges(event, query.limit(first));
56
+ } catch (err) {
57
+ if (isError(err)) {
58
+ apiLogger(event, "api:all:[collectionId]", err.message, err);
59
+ return setResponseStatus(event, err.statusCode || 500, err.statusMessage);
60
+ }
61
+ throw err;
62
+ }
63
+ },
64
+ {
65
+ instanceOnly: false
66
+ }
67
+ );
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Media endpoint
3
+ *
4
+ * Buffer check because of nitro issue:
5
+ * @see https://github.com/unjs/nitro/issues/1894
6
+ */
7
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void | Buffer<ArrayBuffer>>>;
8
+ export default _default;
@@ -0,0 +1,114 @@
1
+ import { createHash } from "node:crypto";
2
+ import { getStorage } from "firebase-admin/storage";
3
+ import { defineCachedFunction, useStorage } from "nitropack/runtime";
4
+ import {
5
+ createError,
6
+ defineEventHandler,
7
+ getRouterParam,
8
+ isError,
9
+ sendError,
10
+ setHeaders
11
+ } from "h3";
12
+ import { storageBucket } from "../utils/environment.js";
13
+ import { debugFirebaseServer } from "../utils/firestore.js";
14
+ import { apiLogger } from "../utils/firebase.js";
15
+ const maxAge = 60 * 60 * 24 * 30;
16
+ const preventCache = {
17
+ "Cache-Control": "no-cache, no-store, must-revalidate",
18
+ Pragma: "no-cache",
19
+ Expires: "0"
20
+ };
21
+ import { readCollection, readInstanceCollection } from "#internal/firebase-nuxt";
22
+ const cachedBufferHandler = defineCachedFunction(
23
+ async (event, path) => {
24
+ const { currentInstanceRef } = event.context;
25
+ debugFirebaseServer(event, "api:media", path);
26
+ if (!path) {
27
+ return { error: createError({ statusCode: 400, statusMessage: "Invalid file path" }) };
28
+ }
29
+ const [baseAndExtension] = path.split("?");
30
+ const [, collectionId, , fileOrSubCollectionId] = baseAndExtension.split("/");
31
+ if (collectionId === "instances" && !fileOrSubCollectionId.includes(".")) {
32
+ if (!readInstanceCollection(fileOrSubCollectionId, event.context)) {
33
+ throw createError({
34
+ statusCode: 401,
35
+ statusMessage: `Can't get "instance/${fileOrSubCollectionId}" file`
36
+ });
37
+ } else if (!currentInstanceRef) {
38
+ throw createError({ statusCode: 401, statusMessage: "Missing instance" });
39
+ }
40
+ } else if (!readCollection(collectionId, event.context)) {
41
+ throw createError({
42
+ statusCode: 401,
43
+ statusMessage: `Can't get "${collectionId}" file`
44
+ });
45
+ }
46
+ const serverStorage = getStorage();
47
+ const bucket = serverStorage.bucket(storageBucket);
48
+ const file = bucket.file(baseAndExtension);
49
+ const [, extension] = baseAndExtension.split(".");
50
+ switch (extension) {
51
+ case "webp": {
52
+ const [exists] = await file.exists();
53
+ const headers = { "Content-Type": "image/webp" };
54
+ if (exists) {
55
+ const [buffer] = await file.download();
56
+ return { buffer, headers };
57
+ }
58
+ const parts = baseAndExtension.split("/");
59
+ parts.pop();
60
+ const [files] = await bucket.getFiles({ prefix: parts.join("/") });
61
+ if (files.length) {
62
+ return {
63
+ headers: { ...preventCache, "Retry-After": "120" },
64
+ error: createError({
65
+ statusCode: 503,
66
+ statusMessage: `File with path: "${path}" is not ready yet`
67
+ })
68
+ };
69
+ }
70
+ break;
71
+ }
72
+ }
73
+ return {
74
+ headers: preventCache,
75
+ error: createError({
76
+ statusCode: 404,
77
+ statusMessage: `File with path: "${path}" does not exist`
78
+ })
79
+ };
80
+ },
81
+ {
82
+ name: "getMedia",
83
+ maxAge,
84
+ getKey(event, path) {
85
+ const [baseAndExtension] = path.split("?");
86
+ return createHash("sha256").update(baseAndExtension).digest("hex");
87
+ }
88
+ }
89
+ );
90
+ export default defineEventHandler(async (event) => {
91
+ const path = getRouterParam(event, "path") || "";
92
+ try {
93
+ const { buffer, headers, error } = await cachedBufferHandler(event, path);
94
+ if (headers) setHeaders(event, headers);
95
+ if (error || !buffer) {
96
+ const err = error || createError({
97
+ statusCode: 500,
98
+ statusMessage: `Something went wrong while trying to get file with path: "${path}"`
99
+ });
100
+ throw err;
101
+ }
102
+ return Buffer.from(buffer);
103
+ } catch (err) {
104
+ const storage = useStorage("cache");
105
+ const [baseAndExtension] = path.split("?");
106
+ const hash = createHash("sha256").update(baseAndExtension).digest("hex");
107
+ await storage.removeItem(`nitro:functions:getMedia:${hash}.json`);
108
+ if (isError(err)) {
109
+ if (err.statusCode !== 503) apiLogger(event, "api:media:[...path]", err.message, err);
110
+ return sendError(event, err);
111
+ }
112
+ throw err;
113
+ }
114
+ });
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Prevent hotlinking & clickjacking
3
+ */
4
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
5
+ export default _default;
@@ -0,0 +1,28 @@
1
+ import { isError, defineEventHandler, setResponseHeaders, setResponseStatus } from "h3";
2
+ import { apiLogger } from "../utils/firebase.js";
3
+ import { getRootInstance } from "../utils/instance.js";
4
+ export default defineEventHandler(async (event) => {
5
+ try {
6
+ const root = await getRootInstance(event);
7
+ if (!root?.url) return setResponseStatus(event, 500, "Missing root instance");
8
+ const { hostname } = new URL(root.url);
9
+ setResponseHeaders(event, {
10
+ // Prevent clickjacking
11
+ "X-Frame-Options": "SAMEORIGIN",
12
+ // Legacy XSS protection
13
+ "X-XSS-Protection": "1; mode=block",
14
+ // Prevent MIME type sniffing
15
+ "X-Content-Type-Options": "nosniff",
16
+ // Prevent referrer leaks
17
+ "Referrer-Policy": "strict-origin-when-cross-origin",
18
+ // CSP Defaults, safe assets only
19
+ "Content-Security-Policy": `default-src 'self'; frame-src 'self' data: https:;img-src 'self' data: https:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https:; style-src 'self' 'unsafe-inline' https:; font-src 'self' data: https:; connect-src 'self' https:; frame-ancestors 'self' https://*.${hostname};`
20
+ });
21
+ } catch (err) {
22
+ if (isError(err)) {
23
+ apiLogger(event, "api:middleware:hotlinking", err.message, err);
24
+ return setResponseStatus(event, err.statusCode, err.message);
25
+ }
26
+ throw err;
27
+ }
28
+ });
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Inject current instance and auth into context
3
+ */
4
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
5
+ export default _default;
@@ -0,0 +1,108 @@
1
+ import { createHash } from "node:crypto";
2
+ import { FirebaseAuthError } from "firebase-admin/auth";
3
+ import {
4
+ isError,
5
+ getRequestHeaders,
6
+ defineEventHandler,
7
+ setResponseHeaders,
8
+ setResponseStatus,
9
+ setResponseHeader,
10
+ getRequestHeader
11
+ } from "h3";
12
+ import { useStorage } from "nitropack/runtime";
13
+ import { production } from "../utils/environment.js";
14
+ import { apiLogger, getServerFirebase } from "../utils/firebase.js";
15
+ import { getInstance } from "../utils/instance.js";
16
+ import { getAuth } from "../utils/auth.js";
17
+ export default defineEventHandler(async (event) => {
18
+ const { firebaseFirestore } = getServerFirebase("api:middleware:context");
19
+ const headers = getRequestHeaders(event);
20
+ const corsHeaders = ["Origin", "Referer", "User-Agent"].join(", ");
21
+ if (!event.path.startsWith("/api")) {
22
+ setResponseHeaders(event, {
23
+ "Access-Control-Allow-Methods": "GET,HEAD",
24
+ Vary: "Host, Origin"
25
+ });
26
+ if (event.method === "OPTIONS") {
27
+ setResponseHeaders(event, {
28
+ "Access-Control-Allow-Headers": corsHeaders,
29
+ "Access-Control-Expose-Headers": corsHeaders
30
+ });
31
+ setResponseStatus(event, 204, "No Content");
32
+ }
33
+ return;
34
+ }
35
+ const {
36
+ host = "",
37
+ "x-forwarded-host": forwardedHost = host,
38
+ "xamu-context-hits": contextHits = "0"
39
+ } = headers;
40
+ const [cleanHost] = forwardedHost.split(":");
41
+ try {
42
+ setResponseHeader(event, "Xamu-Context-Hits", String(Number(contextHits) + 1));
43
+ const { millis, ...instance } = await getInstance(event, forwardedHost);
44
+ const instanceRef = firebaseFirestore.doc(instance.id);
45
+ event.context.currentInstance = instance;
46
+ event.context.currentInstanceRef = instanceRef;
47
+ event.context.currentInstanceMillis = millis;
48
+ event.context.currentInstanceHost = cleanHost;
49
+ const origin = getRequestHeader(event, "X-Forwarded-Origin") || getRequestHeader(event, "Origin") || `https://${cleanHost}`;
50
+ const { hostname } = new URL(origin);
51
+ let corsOrigin = production ? `https://${cleanHost}` : "*";
52
+ if (instance.config?.domains?.includes(hostname)) corsOrigin = origin;
53
+ const corsHeadersExpose = [corsHeaders, "Content-Type", "Cache-control"].join(", ");
54
+ const corsHeadersAccept = [
55
+ corsHeadersExpose,
56
+ "Authorization",
57
+ "Host",
58
+ "Xamu-Context-Source"
59
+ ].join(", ");
60
+ setResponseHeaders(event, {
61
+ "Access-Control-Allow-Methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
62
+ "Access-Control-Allow-Credentials": "true",
63
+ "Access-Control-Allow-Origin": corsOrigin,
64
+ Vary: "Host, Origin"
65
+ });
66
+ if (event.method === "OPTIONS") {
67
+ setResponseHeaders(event, {
68
+ "Access-Control-Allow-Headers": corsHeadersAccept,
69
+ "Access-Control-Expose-Headers": corsHeadersExpose
70
+ });
71
+ setResponseStatus(event, 204, "No Content");
72
+ }
73
+ const authorization = getRequestHeader(event, "X-Forwarded-Authorization") || getRequestHeader(event, "Authorization");
74
+ if (!authorization) return;
75
+ try {
76
+ const auth = await getAuth(event, authorization);
77
+ if (!auth) return;
78
+ event.context.currentAuth = auth;
79
+ event.context.currentAuthRef = instanceRef.collection("members").doc(auth.uid);
80
+ } catch (err) {
81
+ const storage = useStorage("cache");
82
+ const hash = createHash("sha256").update(authorization).digest("hex");
83
+ await storage.removeItem(`nitro:functions:getAuth:${hash}.json`);
84
+ if (err instanceof FirebaseAuthError || isError(err)) {
85
+ if ("code" in err && err.code === "auth/id-token-expired") return;
86
+ apiLogger(
87
+ event,
88
+ "api:middleware:context:auth",
89
+ `Path: "${event.path}", ${err.message}`,
90
+ err
91
+ );
92
+ }
93
+ }
94
+ } catch (err) {
95
+ const storage = useStorage("cache");
96
+ await storage.removeItem(`nitro:functions:getInstance:${cleanHost}.json`);
97
+ if (isError(err)) {
98
+ apiLogger(
99
+ event,
100
+ "api:middleware:context",
101
+ `Path: "${event.path}", ${err.message}`,
102
+ err
103
+ );
104
+ return setResponseStatus(event, err.statusCode, err.message);
105
+ }
106
+ throw err;
107
+ }
108
+ });
@@ -0,0 +1,34 @@
1
+ import type { DocumentReference } from "firebase-admin/firestore";
2
+ import type { EventHandler, EventHandlerRequest, EventHandlerResponse, H3Event, H3EventContext } from "h3";
3
+ import type { InstanceData } from "../../functions/types/index.js";
4
+ import type { Instance, User } from "../../client/types/index.js";
5
+ export interface H3Context extends H3EventContext {
6
+ currentInstance?: Instance & {
7
+ millis: string;
8
+ url: string;
9
+ id: string;
10
+ };
11
+ currentInstanceRef?: DocumentReference<InstanceData>;
12
+ /**
13
+ * Milliseconds from creation
14
+ */
15
+ currentInstanceMillis?: string;
16
+ /**
17
+ * Clean host without port
18
+ *
19
+ * @example "example.com"
20
+ * @cache used for instance cache key
21
+ */
22
+ currentInstanceHost?: string;
23
+ currentAuth?: User & {
24
+ id: string;
25
+ uid: string;
26
+ };
27
+ currentAuthRef?: DocumentReference;
28
+ }
29
+ export interface CachedH3Event<T extends EventHandlerRequest = EventHandlerRequest> extends Omit<H3Event<T>, "context"> {
30
+ context: H3Context;
31
+ }
32
+ export interface CachedEventHandler<T extends EventHandlerRequest = EventHandlerRequest, D extends EventHandlerResponse = EventHandlerResponse> extends Omit<EventHandler<T, D>, "event"> {
33
+ (event: CachedH3Event<T>): D;
34
+ }
File without changes
@@ -0,0 +1,10 @@
1
+ import { type H3Event } from "h3";
2
+ /**
3
+ * Get current auth
4
+ *
5
+ * @cache 2 minutes
6
+ */
7
+ export declare const getAuth: (_: H3Event<import("h3").EventHandlerRequest>, authorization: string | undefined) => Promise<(import("../../client/types/index.js").User & {
8
+ id: string;
9
+ uid: string;
10
+ }) | undefined>;
@@ -0,0 +1,31 @@
1
+ import { createHash } from "node:crypto";
2
+ import { defineCachedFunction } from "nitropack/runtime";
3
+ import { resolveServerRefs } from "./firestore.js";
4
+ import { getServerFirebase } from "./firebase.js";
5
+ export const getAuth = defineCachedFunction(
6
+ async function(event, authorization) {
7
+ if (!authorization) return;
8
+ const { firebaseAuth } = getServerFirebase("api:getAuth");
9
+ const { currentInstanceRef, currentInstance } = event.context;
10
+ if (!currentInstanceRef || !currentInstance) return;
11
+ const membersRef = currentInstanceRef.collection("members");
12
+ const { uid } = await firebaseAuth.verifyIdToken(authorization);
13
+ const snapshot = await membersRef.doc(uid).get();
14
+ const memberData = await resolveServerRefs(
15
+ snapshot,
16
+ { level: 1 },
17
+ false
18
+ );
19
+ const { user, ...member } = memberData || {};
20
+ return { ...user, ...member, uid, id: `${currentInstance.id}/members/${uid}` };
21
+ },
22
+ {
23
+ name: "getAuth",
24
+ maxAge: 60 * 60,
25
+ // 1 hour
26
+ getKey(_, authorization) {
27
+ if (!authorization) return "guest";
28
+ return createHash("sha256").update(authorization).digest("hex");
29
+ }
30
+ }
31
+ );
@@ -0,0 +1,21 @@
1
+ import { type EventHandlerRequest, type EventHandlerResponse } from "h3";
2
+ import type { CachedEventHandler, CachedH3Event } from "../types/index.js";
3
+ interface CachedEventHandlerOptions<T extends EventHandlerRequest = EventHandlerRequest> {
4
+ /** Optional key generator */
5
+ getKey?: (...args: [CachedH3Event<T>]) => string | Promise<string>;
6
+ /** Partition cache by instance */
7
+ instanceOnly?: boolean;
8
+ }
9
+ /**
10
+ * Conditionally cache event data.
11
+ * Bypasses cache for admin purposes
12
+ * Caches by instance by default
13
+ *
14
+ * @cache 30 seconds
15
+ *
16
+ * @param handler event handler, should have its own error handling
17
+ * @param options optional key generator and instanceOnly flag
18
+ * @returns event handler
19
+ */
20
+ export declare const defineConditionallyCachedEventHandler: <T extends EventHandlerRequest, D extends EventHandlerResponse = EventHandlerResponse>(handler: CachedEventHandler<T, D>, { getKey, instanceOnly }?: CachedEventHandlerOptions<T>) => CachedEventHandler<T, D>;
21
+ export {};
@@ -0,0 +1,20 @@
1
+ import { defineEventHandler } from "h3";
2
+ import { defineCachedEventHandler } from "nitropack/runtime";
3
+ import { debugNitro } from "../utils/environment.js";
4
+ import { sudo } from "#internal/firebase-nuxt";
5
+ export const defineConditionallyCachedEventHandler = (handler, { getKey, instanceOnly = true } = {}) => {
6
+ const cachedHandler = defineCachedEventHandler(handler, {
7
+ maxAge: 30,
8
+ // 30 seconds
9
+ getKey: instanceOnly ? (event) => {
10
+ const { currentInstanceHost } = event.context;
11
+ const key = getKey?.(event) || event.path;
12
+ if (currentInstanceHost) return `${currentInstanceHost}:${key}`;
13
+ return key;
14
+ } : getKey
15
+ });
16
+ return defineEventHandler(async (event) => {
17
+ if (sudo(event.context) || debugNitro) return handler(event);
18
+ return cachedHandler(event);
19
+ });
20
+ };
@@ -0,0 +1,45 @@
1
+ import type { FirebaseOptions } from "firebase/app";
2
+ import { eCacheControl } from "../../functions/utils/enums.js";
3
+ /**
4
+ * Public runtime config
5
+ */
6
+ export interface FirebaseNuxtPublicRuntimeConfig {
7
+ appName: string;
8
+ production: boolean;
9
+ forcedInstanceId: string;
10
+ indexable: boolean;
11
+ tenants: boolean;
12
+ firebaseConfig: Required<Omit<FirebaseOptions, "databaseURL">>;
13
+ recaptchaEnterpriseKey: string;
14
+ debugAppCheck: boolean;
15
+ debugFirebase: boolean;
16
+ cache: {
17
+ none: eCacheControl;
18
+ frequent: eCacheControl;
19
+ normal: eCacheControl;
20
+ midterm: eCacheControl;
21
+ longterm: eCacheControl;
22
+ };
23
+ }
24
+ export declare const port: number;
25
+ export declare const production: boolean;
26
+ export declare const indexable: boolean;
27
+ export declare const origin: string;
28
+ export declare const forcedInstanceId: string;
29
+ export declare const appName: string;
30
+ export declare const debugNuxt: boolean;
31
+ export declare const debugNitro: boolean;
32
+ export declare const debugCSS: boolean;
33
+ export declare const debugAppCheck: boolean;
34
+ export declare const debugFirebase: boolean;
35
+ export declare const storageBucket: string;
36
+ export declare const projectId: string;
37
+ export declare const privateKey: string;
38
+ export declare const clientEmail: string;
39
+ /** App check, public key */
40
+ export declare const recaptchaEnterpriseKey: string;
41
+ export declare const fontsApiKey: string;
42
+ /**
43
+ * Public runtime config
44
+ */
45
+ export declare const publicRuntimeConfig: FirebaseNuxtPublicRuntimeConfig;
@@ -0,0 +1,50 @@
1
+ import { defineString, defineBoolean, defineInt } from "firebase-functions/params";
2
+ import { eCacheControl } from "../../functions/utils/enums.js";
3
+ const environment = defineString("NODE_ENV", { default: "development" });
4
+ export const port = defineInt("PORT", { default: 3e3 }).value() || 3e3;
5
+ export const production = environment.equals("production").value();
6
+ export const indexable = defineBoolean("INDEXABLE", { default: false }).value();
7
+ export const origin = defineString("ORIGIN").value();
8
+ export const forcedInstanceId = defineString("INSTANCE").value();
9
+ export const appName = defineString("APP_NAME").value();
10
+ export const debugNuxt = !production && defineBoolean("DEBUG_NUXT", { default: false }).value();
11
+ export const debugNitro = !production && defineBoolean("DEBUG_NITRO", { default: false }).value();
12
+ export const debugCSS = !production && defineBoolean("DEBUG_CSS", { default: false }).value();
13
+ export const debugAppCheck = !production && defineBoolean("DEBUG_APP_CHECK", { default: false }).value();
14
+ export const debugFirebase = !production && defineBoolean("DEBUG_FIREBASE", { default: false }).value();
15
+ export const storageBucket = defineString("F_STORAGE_BUCKET").value();
16
+ export const projectId = defineString("F_PROJECT_ID").value();
17
+ export const privateKey = defineString("F_PRIVATE_KEY").value();
18
+ export const clientEmail = defineString("F_CLIENT_EMAIL").value();
19
+ export const recaptchaEnterpriseKey = defineString("RECAPTCHA_ENTERPRISE_SITE_KEY").value();
20
+ export const fontsApiKey = defineString("FONTS_API_KEY").value();
21
+ const firebaseConfig = {
22
+ projectId,
23
+ apiKey: defineString("F_API_KEY").value(),
24
+ authDomain: defineString("F_AUTH_DOMAIN").value(),
25
+ storageBucket,
26
+ messagingSenderId: defineString("F_MESSAGING_SENDER_ID").value(),
27
+ appId: defineString("F_APP_ID").value(),
28
+ measurementId: defineString("F_MEASUREMENT_ID").value()
29
+ };
30
+ export const publicRuntimeConfig = {
31
+ // App
32
+ appName,
33
+ production,
34
+ forcedInstanceId,
35
+ indexable,
36
+ // Firebase
37
+ firebaseConfig,
38
+ recaptchaEnterpriseKey,
39
+ debugAppCheck,
40
+ debugFirebase,
41
+ // Utils
42
+ cache: {
43
+ none: eCacheControl.NONE,
44
+ frequent: eCacheControl.FREQUENT,
45
+ normal: eCacheControl.NORMAL,
46
+ midterm: eCacheControl.MIDTERM,
47
+ longterm: eCacheControl.LONGTERM
48
+ },
49
+ tenants: false
50
+ };
@@ -0,0 +1,9 @@
1
+ import type { H3Event } from "h3";
2
+ import type { tLogger } from "@open-xamu-co/ui-common-types";
3
+ export declare function getServerFirebase(at?: string): {
4
+ firebaseApp: import("firebase-admin/app").App;
5
+ firebaseFirestore: FirebaseFirestore.Firestore;
6
+ firebaseAuth: import("firebase-admin/auth").Auth;
7
+ firebaseStorage: import("firebase-admin/storage").Storage;
8
+ };
9
+ export declare function apiLogger(event: H3Event, ...args: Parameters<tLogger>): void;
@@ -0,0 +1,13 @@
1
+ import { cert } from "firebase-admin/app";
2
+ import { clientEmail, privateKey, projectId } from "../utils/environment.js";
3
+ import { makeLogger } from "../../client/utils/logger.js";
4
+ import { getFirebase } from "../../functions/utils/firebase.js";
5
+ export function getServerFirebase(at = "Unknown") {
6
+ const credential = cert({ projectId, privateKey, clientEmail });
7
+ return getFirebase(at, { credential });
8
+ }
9
+ export function apiLogger(event, ...args) {
10
+ const { currentAuth, currentInstance } = event.context || {};
11
+ const logger = makeLogger({ instanceId: currentInstance?.id, authId: currentAuth?.uid });
12
+ return logger(...args);
13
+ }