postbase 0.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 (126) hide show
  1. package/.github/workflows/test.yml +74 -0
  2. package/CLA.md +60 -0
  3. package/CONTRIBUTORS.md +35 -0
  4. package/LICENSE +661 -0
  5. package/README.md +211 -0
  6. package/admin/404.html +33 -0
  7. package/admin/README.md +21 -0
  8. package/admin/index.html +15 -0
  9. package/admin/jsconfig.json +20 -0
  10. package/admin/lib/postbase.js +222 -0
  11. package/admin/package-lock.json +3746 -0
  12. package/admin/package.json +27 -0
  13. package/admin/public/assets/img/admin-ui.png +0 -0
  14. package/admin/public/assets/img/blank-profile-picture-960_720.webp +0 -0
  15. package/admin/public/assets/img/chart-active-users.png +0 -0
  16. package/admin/public/assets/img/icon-transparent.png +0 -0
  17. package/admin/src/App.jsx +48 -0
  18. package/admin/src/auth.js +11 -0
  19. package/admin/src/common/formatDateTime.js +18 -0
  20. package/admin/src/components/AuthPanel.jsx +88 -0
  21. package/admin/src/components/Header.jsx +67 -0
  22. package/admin/src/main.jsx +6 -0
  23. package/admin/src/pages/Dashboard.jsx +24 -0
  24. package/admin/src/pages/Home.jsx +52 -0
  25. package/admin/src/pages/Login.jsx +10 -0
  26. package/admin/src/pages/authentication/Users.jsx +199 -0
  27. package/admin/src/pages/firestore/Database.jsx +29 -0
  28. package/admin/src/pages/storage/files.jsx +29 -0
  29. package/admin/src/postbase.js +15 -0
  30. package/admin/src/styles.css +3 -0
  31. package/admin/tailwind.config.cjs +11 -0
  32. package/admin/template.env +2 -0
  33. package/admin/vite.config.js +21 -0
  34. package/assets/img/HomePageScreenshot.png +0 -0
  35. package/assets/img/better-auth-logo-dark.136b122f.png +0 -0
  36. package/assets/img/better-auth-logo-light.4b03f444.png +0 -0
  37. package/assets/img/expresjs.png +0 -0
  38. package/assets/img/icon-transparent.png +0 -0
  39. package/assets/img/icon.png +0 -0
  40. package/assets/img/letsencrypt-logo-horizontal.png +0 -0
  41. package/assets/img/logo.png +0 -0
  42. package/assets/img/node.js_logo.png +0 -0
  43. package/assets/img/nodejsLight.svg +39 -0
  44. package/assets/img/postgres.png +0 -0
  45. package/backend/README.md +49 -0
  46. package/backend/admin/auth.js +9 -0
  47. package/backend/app.js +68 -0
  48. package/backend/auth.js +92 -0
  49. package/backend/env.js +12 -0
  50. package/backend/lib/postbase/adminClient.js +520 -0
  51. package/backend/lib/postbase/compat/admin.js +44 -0
  52. package/backend/lib/postbase/db.js +17 -0
  53. package/backend/lib/postbase/genericRouter.js +603 -0
  54. package/backend/lib/postbase/local-storage.js +56 -0
  55. package/backend/lib/postbase/metadataCache.js +32 -0
  56. package/backend/lib/postbase/middlewares/auth.js +57 -0
  57. package/backend/lib/postbase/migrations/1765239687559_rtdb-nodes.js +93 -0
  58. package/backend/lib/postbase/package-lock.json +5873 -0
  59. package/backend/lib/postbase/package.json +19 -0
  60. package/backend/lib/postbase/rtdb/router.js +190 -0
  61. package/backend/lib/postbase/rtdb/rulesEngine.js +63 -0
  62. package/backend/lib/postbase/rtdb/ws.js +84 -0
  63. package/backend/lib/postbase/rulesEngine.js +62 -0
  64. package/backend/lib/postbase/storage.js +130 -0
  65. package/backend/lib/postbase/tests/README.md +22 -0
  66. package/backend/lib/postbase/tests/db.js +9 -0
  67. package/backend/lib/postbase/tests/rtdb.rest.test.js +46 -0
  68. package/backend/lib/postbase/tests/rtdb.ws.test.js +113 -0
  69. package/backend/lib/postbase/tests/rules.js +26 -0
  70. package/backend/lib/postbase/tests/testServer.js +46 -0
  71. package/backend/lib/postbase/websocket.js +131 -0
  72. package/backend/local.js +6 -0
  73. package/backend/main.js +20 -0
  74. package/backend/middlewares/auth_middleware.js +10 -0
  75. package/backend/migrations/1762137399366-init.sql +98 -0
  76. package/backend/migrations/1762137399367_init_jsonb_schema.js +68 -0
  77. package/backend/migrations/1762149999999_enable_realtime_changes.js +48 -0
  78. package/backend/migrations/1765224247654_rtdb-nodes.js +93 -0
  79. package/backend/package-lock.json +2374 -0
  80. package/backend/package.json +27 -0
  81. package/backend/postbase_db_rules.js +128 -0
  82. package/backend/postbase_rtdb_rules.js +27 -0
  83. package/backend/postbase_storage_rules.js +45 -0
  84. package/backend/template.env +10 -0
  85. package/backend-systemd/README.md +39 -0
  86. package/backend-systemd/your_website.com.service +12 -0
  87. package/frontend/404.html +33 -0
  88. package/frontend/README.md +25 -0
  89. package/frontend/index.html +15 -0
  90. package/frontend/jsconfig.json +20 -0
  91. package/frontend/lib/postbase/auth.js +132 -0
  92. package/frontend/lib/postbase/compat/firebase/app.js +3 -0
  93. package/frontend/lib/postbase/compat/firebase/auth.js +115 -0
  94. package/frontend/lib/postbase/compat/firebase/database.js +11 -0
  95. package/frontend/lib/postbase/compat/firebase/firestore/lite.js +61 -0
  96. package/frontend/lib/postbase/compat/firebase/storage.js +10 -0
  97. package/frontend/lib/postbase/db.js +657 -0
  98. package/frontend/lib/postbase/package-lock.json +6284 -0
  99. package/frontend/lib/postbase/package.json +17 -0
  100. package/frontend/lib/postbase/rtdb.js +108 -0
  101. package/frontend/lib/postbase/storage.js +293 -0
  102. package/frontend/lib/postbase/tests/rtdb.client.test.js +88 -0
  103. package/frontend/lib/postbase/tests/waitFor.js +13 -0
  104. package/frontend/lib/postbase/utils.js +1 -0
  105. package/frontend/package-lock.json +2977 -0
  106. package/frontend/package.json +24 -0
  107. package/frontend/src/App.jsx +38 -0
  108. package/frontend/src/auth.js +52 -0
  109. package/frontend/src/components/AuthPanel.jsx +85 -0
  110. package/frontend/src/components/Header.jsx +54 -0
  111. package/frontend/src/main.jsx +5 -0
  112. package/frontend/src/pages/Dashboard.jsx +24 -0
  113. package/frontend/src/pages/Home.jsx +178 -0
  114. package/frontend/src/pages/Login.jsx +10 -0
  115. package/frontend/src/postbase.js +14 -0
  116. package/frontend/src/styles.css +1 -0
  117. package/frontend/tailwind.config.cjs +11 -0
  118. package/frontend/template.env +2 -0
  119. package/frontend/vite.config.js +18 -0
  120. package/git/hooks/README.md +31 -0
  121. package/git/hooks/post-receive +26 -0
  122. package/nginx/README.md +84 -0
  123. package/nginx/apt/www.your_website.com.conf +80 -0
  124. package/nginx/homebrew/www.your_website.com.conf +80 -0
  125. package/nginx/letsencrypt/README +14 -0
  126. package/package.json +8 -0
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "postbase-backend",
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "author": "Umair Ashraf",
6
+ "url": "http://github.com/umrashrf/postbase",
7
+ "scripts": {
8
+ "dev": "nodemon local.js",
9
+ "migrate:up": "node-pg-migrate up",
10
+ "migrate:down": "node-pg-migrate down",
11
+ "migrate:redo": "node-pg-migrate redo",
12
+ "migrate:create": "node-pg-migrate create"
13
+ },
14
+ "dependencies": {
15
+ "better-auth": "^1.3.34",
16
+ "cors": "^2.8.5",
17
+ "dotenv": "^16.6.1",
18
+ "express": "^4.21.2",
19
+ "multer": "^2.0.2",
20
+ "pg": "^8.16.3",
21
+ "uuid": "^9.0.0",
22
+ "ws": "^8.18.3"
23
+ },
24
+ "devDependencies": {
25
+ "node-pg-migrate": "^8.0.3"
26
+ }
27
+ }
@@ -0,0 +1,128 @@
1
+ // server/rules.js
2
+ import { RuleHelpers as H } from './lib/postbase/rulesEngine.js';
3
+
4
+ /**
5
+ * This ruleset mirrors your Firestore rules.
6
+ * Each table corresponds to a Firestore collection.
7
+ * All functions receive (request, resource)
8
+ * - request.auth may be null or { uid, ... }
9
+ * - request.resource.data equivalent → request.resource (on create)
10
+ * - resource.data → resource (for read/update/delete)
11
+ */
12
+ export default {
13
+ tables: {
14
+ /** === USERS === */
15
+ users: {
16
+ // Allow read and update if the auth.id matches the userId (row id)
17
+ read: (req, res) => H.isAuth(req) && (req.auth.id === String(res.id) || req.user?.role === "admin"),
18
+ update: (req, res) => H.isAuth(req) && req.auth.id === String(res.id),
19
+
20
+ // Allow create if auth.id == resource.userId
21
+ create: (req, res) => H.isAuth(req) && req.auth.id === String(res.id),
22
+
23
+ // Delete not allowed
24
+ delete: () => false,
25
+ },
26
+
27
+ /** === REVIEWS === */
28
+ reviews: {
29
+ // Everyone can read
30
+ read: () => true,
31
+
32
+ // Authenticated users can create reviews with proper fields and constraints
33
+ create: (req, res) => {
34
+ if (!H.isAuth(req)) return false;
35
+ const d = req.resource || {};
36
+ const isString = (v) => typeof v === 'string' && v.trim().length > 0;
37
+ const isInt = (v) => Number.isInteger(v);
38
+
39
+ const valid =
40
+ isString(d.name) &&
41
+ isString(d.email) &&
42
+ isString(d.comment) &&
43
+ isInt(d.stars) &&
44
+ d.stars >= 1 &&
45
+ d.stars <= 5 &&
46
+ // check createdAt equals request.time: approximated by presence of field
47
+ !!d.createdAt;
48
+
49
+ return valid;
50
+ },
51
+
52
+ // No updates or deletes
53
+ update: () => false,
54
+ delete: () => false,
55
+ },
56
+
57
+ /** === DEV_REQUESTS === */
58
+ dev_requests: {
59
+ // Authenticated user can read their own requests
60
+ read: (req, res) =>
61
+ H.isAuth(req) && req.auth.id === String(res.user_id || res.userId),
62
+
63
+ // Authenticated user can create their own requests with valid data
64
+ create: (req, res) => {
65
+ if (!H.isAuth(req)) return false;
66
+ const d = req.resource || {};
67
+ const isString = (v) => typeof v === 'string' && v.trim().length > 0;
68
+
69
+ return (
70
+ req.auth.id === String(d.user_id || d.userId) &&
71
+ isString(d.name) &&
72
+ isString(d.email) &&
73
+ isString(d.description)
74
+ );
75
+ },
76
+
77
+ // No updates or deletes
78
+ update: () => false,
79
+ delete: () => false,
80
+ },
81
+
82
+ /** === API_KEYS === */
83
+ api_keys: {
84
+ // Read / update / delete allowed only for owner
85
+ read: (req, res) => {
86
+ if (!res) return H.isAuth(req);
87
+ return H.isAuth(req) && req.auth.id === String(res.user_id || res.userId);
88
+ },
89
+ update: (req, res) =>
90
+ H.isAuth(req) && req.auth.id === String(res.user_id || res.userId),
91
+ delete: (req, res) =>
92
+ H.isAuth(req) && req.auth.id === String(res.user_id || res.userId),
93
+
94
+ // Create: must be owner and valid key string with length >= 16
95
+ create: (req, res) => {
96
+ if (!H.isAuth(req)) return false;
97
+ const d = req.resource || {};
98
+ const isString = (v) => typeof v === 'string' && v.trim().length >= 16;
99
+ return (
100
+ req.auth.id === String(d.user_id || d.userId) &&
101
+ isString(d.key)
102
+ );
103
+ },
104
+ },
105
+
106
+ /** === BILLING === */
107
+ billing: {
108
+ // Read and write allowed only for owner
109
+ read: (req, res) =>
110
+ H.isAuth(req) && req.auth.id === String(res.user_id || res.userId),
111
+ create: (req, res) =>
112
+ H.isAuth(req) && req.auth.id === String(res.user_id || res.userId),
113
+ update: (req, res) =>
114
+ H.isAuth(req) && req.auth.id === String(res.user_id || res.userId),
115
+ delete: (req, res) =>
116
+ H.isAuth(req) && req.auth.id === String(res.user_id || res.userId),
117
+ },
118
+ },
119
+
120
+ /** === GLOBAL DEFAULTS === */
121
+ default: {
122
+ // Default deny everything (read/write false)
123
+ read: () => false,
124
+ create: () => false,
125
+ update: () => false,
126
+ delete: () => false,
127
+ },
128
+ };
@@ -0,0 +1,27 @@
1
+ // backend/postbase_rtdb_rules.js
2
+ export default {
3
+ paths: {
4
+ "/users/$uid": {
5
+ read: (req, ctx) =>
6
+ !!req.auth && req.auth.id === ctx.params.uid,
7
+
8
+ write: (req, ctx) =>
9
+ !!req.auth && req.auth.id === ctx.params.uid,
10
+
11
+ delete: (req, ctx) =>
12
+ !!req.auth && req.auth.id === ctx.params.uid,
13
+ },
14
+
15
+ "/public": {
16
+ read: true,
17
+ write: false,
18
+ delete: false,
19
+ },
20
+ },
21
+
22
+ default: {
23
+ read: false,
24
+ write: false,
25
+ delete: false,
26
+ }
27
+ };
@@ -0,0 +1,45 @@
1
+ // fileRules.js
2
+ import { RuleHelpers as H } from './lib/postbase/rulesEngine.js'; // adjust import path to your evaluator helpers
3
+
4
+ export default {
5
+ tables: {
6
+ files: {
7
+ // read: allowed if authenticated and auth.id equals owner OR auth.id in allowedUsers
8
+ read: (req, resource) => {
9
+ if (!resource) return false;
10
+ if (!H.isAuth(req)) return false;
11
+ const uid = req.auth.id;
12
+ if (!uid) return false;
13
+ if (resource.owner && String(resource.owner) === String(uid)) return true;
14
+ if (Array.isArray(resource.allowedUsers) && resource.allowedUsers.map(String).includes(String(uid))) return true;
15
+ return false;
16
+ },
17
+
18
+ // create: allow if auth.id equals incoming owner OR auth.id in incoming allowedUsers
19
+ create: (req, resource) => {
20
+ if (!H.isAuth(req)) return false;
21
+ const uid = req.auth.id;
22
+ if (!uid) return false;
23
+ // resource is the incoming metadata object: must specify owner or allowedUsers
24
+ if (resource.owner && String(resource.owner) === String(uid)) return true;
25
+ if (Array.isArray(resource.allowedUsers) && resource.allowedUsers.map(String).includes(String(uid))) return true;
26
+ return false;
27
+ },
28
+
29
+ // delete: only owner
30
+ delete: (req, resource) => {
31
+ if (!H.isAuth(req)) return false;
32
+ const uid = req.auth.id;
33
+ return resource && resource.owner && String(resource.owner) === String(uid);
34
+ }
35
+ }
36
+ },
37
+
38
+ // default denies everything
39
+ default: {
40
+ read: () => false,
41
+ create: () => false,
42
+ update: () => false,
43
+ delete: () => false
44
+ }
45
+ };
@@ -0,0 +1,10 @@
1
+ API_BASE=https://www.your_website.com/api
2
+ BETTER_AUTH_SECRET=
3
+ GOOGLE_CLIENT_ID=
4
+ GOOGLE_CLIENT_SECRET=
5
+ POSTBASE_BACKEND_HTTP_PORT=8081
6
+ POSTBASE_BACKEND_HTTPS_PORT=4431
7
+ LETSENCRYPT_CERT="/Users/your_username/your_website/nginx/letsencrypt/cert.pem"
8
+ LETSENCRYPT_KEY="/Users/your_username/your_website/nginx/letsencrypt/privkey.pem"
9
+ DATABASE_URL="postgresql://postgres:yoursecretpassword@127.0.0.1:5432/postgres"
10
+ ADMIN_USER_ID=
@@ -0,0 +1,39 @@
1
+ # Systemd for backend process
2
+
3
+ ### Reload the systemd user configuration to recognize the new service:
4
+
5
+ ```
6
+ systemctl --user daemon-reload
7
+ ```
8
+
9
+ ### Start the service.
10
+
11
+ ```
12
+ systemctl --user start your_website.com.service
13
+ ```
14
+
15
+ ### Enable the service to start automatically on login:
16
+
17
+ ```
18
+ systemctl --user enable your_website.com.service
19
+ ```
20
+
21
+ ### Check the service status.
22
+
23
+ ```
24
+ systemctl --user status your_website.com.service
25
+ ```
26
+
27
+ ### Restart the service.
28
+
29
+ ```
30
+ systemctl --user restart your_website.com.service
31
+ ```
32
+
33
+ ## Lingering
34
+
35
+ ### Lingering: If you want your user service to continue running even after you log out, you need to enable user lingering:
36
+
37
+ ```
38
+ loginctl enable-linger your_username
39
+ ```
@@ -0,0 +1,12 @@
1
+ [Unit]
2
+ Description=your_website.com
3
+ After=network.target
4
+
5
+ [Service]
6
+ ExecStart=/path/to/bin/node main.js
7
+ Restart=always
8
+ User=your_username
9
+ WorkingDirectory=/home/your_username/my-app-directory/
10
+
11
+ [Install]
12
+ WantedBy=default.target
@@ -0,0 +1,33 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Page Not Found</title>
7
+
8
+ <style media="screen">
9
+ body { background: #ECEFF1; color: rgba(0,0,0,0.87); font-family: Roboto, Helvetica, Arial, sans-serif; margin: 0; padding: 0; }
10
+ #message { background: white; max-width: 360px; margin: 100px auto 16px; padding: 32px 24px 16px; border-radius: 3px; }
11
+ #message h3 { color: #888; font-weight: normal; font-size: 16px; margin: 16px 0 12px; }
12
+ #message h2 { color: #ffa100; font-weight: bold; font-size: 16px; margin: 0 0 8px; }
13
+ #message h1 { font-size: 22px; font-weight: 300; color: rgba(0,0,0,0.6); margin: 0 0 16px;}
14
+ #message p { line-height: 140%; margin: 16px 0 24px; font-size: 14px; }
15
+ #message a { display: block; text-align: center; background: #039be5; text-transform: uppercase; text-decoration: none; color: white; padding: 16px; border-radius: 4px; }
16
+ #message, #message a { box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); }
17
+ #load { color: rgba(0,0,0,0.4); text-align: center; font-size: 13px; }
18
+ @media (max-width: 600px) {
19
+ body, #message { margin-top: 0; background: white; box-shadow: none; }
20
+ body { border-top: 16px solid #ffa100; }
21
+ }
22
+ </style>
23
+ </head>
24
+ <body>
25
+ <div id="message">
26
+ <h2>404</h2>
27
+ <h1>Page Not Found</h1>
28
+ <p>The specified file was not found on this website. Please check the URL for mistakes and try again.</p>
29
+ <h3>Why am I seeing this?</h3>
30
+ <p>This page was generated by the Firebase Command-Line Interface. To modify it, edit the <code>404.html</code> file in your project's configured <code>public</code> directory.</p>
31
+ </div>
32
+ </body>
33
+ </html>
@@ -0,0 +1,25 @@
1
+ # Frontend
2
+
3
+ ## Install
4
+
5
+ ```
6
+ cd frontend
7
+ cp template.env .env
8
+ npm insall
9
+ ```
10
+
11
+ Make sure your backend is running and double check frontend/.env file is accurate.
12
+
13
+ ## Run
14
+
15
+ This will watch for changes and build while running the server.
16
+
17
+ ```
18
+ npm run dev
19
+ ```
20
+
21
+ ## Build (Nginx)
22
+
23
+ ```
24
+ npm run build
25
+ ```
@@ -0,0 +1,15 @@
1
+ <!doctype html>
2
+ <html>
3
+
4
+ <head>
5
+ <meta charset="utf-8" />
6
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
7
+ <title>Postbase Demo</title>
8
+ </head>
9
+
10
+ <body>
11
+ <div id="app"></div>
12
+ <script type="module" src="/src/main.jsx"></script>
13
+ </body>
14
+
15
+ </html>
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "noEmit": true,
7
+ "allowJs": true,
8
+ "checkJs": true,
9
+
10
+ /* Preact Config */
11
+ "jsx": "react-jsx",
12
+ "jsxImportSource": "preact",
13
+ "skipLibCheck": true,
14
+ "paths": {
15
+ "react": ["./node_modules/preact/compat/"],
16
+ "react-dom": ["./node_modules/preact/compat/"]
17
+ }
18
+ },
19
+ "include": ["node_modules/vite/client.d.ts", "**/*"]
20
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * A wrapper around better-auth/client's createAuthClient
3
+ * that adds Firebase-like helpers:
4
+ * - onAuthStateChanged(callback)
5
+ * - getIdToken()
6
+ * - currentUser
7
+ */
8
+ export function createAuthClient(betterAuthClient) {
9
+ // Track current user state
10
+ let currentUser = null;
11
+ const subscribers = new Set();
12
+
13
+ function notifySubscribers(user) {
14
+ for (const cb of subscribers) {
15
+ try {
16
+ cb(user);
17
+ } catch (err) {
18
+ console.error("Error in onAuthStateChanged listener:", err);
19
+ }
20
+ }
21
+ }
22
+
23
+ // Poll for session changes (adjust interval as needed)
24
+ async function checkSession() {
25
+ try {
26
+ const { data } = await betterAuthClient.getSession();
27
+ const newUser = data?.user ?? null;
28
+ if (newUser) {
29
+ newUser.uid = newUser.id;
30
+ }
31
+
32
+ if (JSON.stringify(newUser) !== JSON.stringify(currentUser)) {
33
+ currentUser = newUser;
34
+ if (currentUser) {
35
+ currentUser.getIdToken = getIdToken;
36
+ }
37
+ notifySubscribers(currentUser);
38
+ }
39
+ } catch (err) {
40
+ console.error("Error checking session:", err);
41
+ }
42
+ }
43
+
44
+ // Simple polling loop (every 5 seconds by default)
45
+ const POLL_INTERVAL = 5000;
46
+ let pollTimer = setInterval(checkSession, POLL_INTERVAL);
47
+
48
+ /**
49
+ * Adds a listener for auth state changes.
50
+ * Returns an unsubscribe function (for React/Preact useEffect cleanup).
51
+ */
52
+ function onAuthStateChanged(callback) {
53
+ subscribers.add(callback);
54
+
55
+ // Immediately invoke with current user
56
+ (async () => {
57
+ const { data } = await betterAuthClient.getSession();
58
+ const newUser = data?.user ?? null;
59
+ if (newUser) {
60
+ newUser.uid = newUser.id;
61
+ }
62
+ currentUser = newUser;
63
+ if (currentUser) {
64
+ currentUser.getIdToken = getIdToken;
65
+ }
66
+ try {
67
+ callback(newUser);
68
+ } catch (err) {
69
+ console.error("Error in onAuthStateChanged listener:", err);
70
+ }
71
+ })();
72
+
73
+ // Return unsubscribe
74
+ return () => {
75
+ subscribers.delete(callback);
76
+ if (subscribers.size === 0) {
77
+ clearInterval(pollTimer);
78
+ pollTimer = null;
79
+ }
80
+ };
81
+ }
82
+
83
+ /**
84
+ * Firebase-like getIdToken() equivalent.
85
+ * Returns the current access token (JWT) if a session exists.
86
+ */
87
+ async function getIdToken(refresh = false) {
88
+ const { data } = await betterAuthClient.getSession();
89
+ const token =
90
+ data?.session?.token ?? data?.session?.token ?? null;
91
+ return token;
92
+ }
93
+
94
+ async function getBetterAuthToken() {
95
+ const authToken = window.sessionStorage.getItem('authToken');
96
+ if (authToken) {
97
+ return authToken;
98
+ }
99
+ const { data } = await betterAuthClient.getSession();
100
+ if (data && data.hasOwnProperty('session') && data.session?.token) {
101
+ window.sessionStorage.setItem('authToken', data.session?.token);
102
+ return data.session?.token;
103
+ }
104
+ return null;
105
+ }
106
+
107
+ const signOut = (...args) => {
108
+ const authToken = window.sessionStorage.getItem('authToken');
109
+ if (authToken) {
110
+ window.sessionStorage.removeItem('authToken');
111
+ }
112
+ // at the end because it can trigger navigation
113
+ betterAuthClient.signOut.apply(this, ...args);
114
+ }
115
+
116
+ // Return wrapped client with added helpers and dynamic currentUser getter
117
+ return {
118
+ ...betterAuthClient,
119
+
120
+ /**
121
+ * Returns the most recent known user (Firebase-like).
122
+ * This does not fetch the server — it reflects the last known session state.
123
+ */
124
+ get currentUser() {
125
+ return currentUser;
126
+ },
127
+
128
+ onAuthStateChanged,
129
+ getBetterAuthToken,
130
+ signOut,
131
+ };
132
+ }
@@ -0,0 +1,3 @@
1
+ export function initializeApp(firebaseConfig) {
2
+ return firebaseConfig;
3
+ }
@@ -0,0 +1,115 @@
1
+ //import { createAuthClient } from '../../auth';
2
+
3
+ export function createFirebaseAuthClient(postbaseAuthClientWithBetterAuthFunctions) {
4
+ // if (!(auth instanceof createAuthClient)) {
5
+ // throw new Error("");
6
+ // }
7
+
8
+ const createUserWithEmailAndPassword = async (auth, email, password, name = null) => {
9
+ try {
10
+ // better-auth requires name whereas firebase auth doesn't
11
+ let _name = name;
12
+ if (!_name) {
13
+ _name = email.split('@')[0];
14
+ }
15
+
16
+ await auth.signUp.email({
17
+ email,
18
+ password,
19
+ name,
20
+ //callbackURL: "/dashboard",
21
+ });
22
+
23
+ const { data } = await auth.getSession();
24
+ const user = data?.user ?? null;
25
+
26
+ const userCredential = { user };
27
+
28
+ return userCredential;
29
+
30
+ } catch (err) {
31
+ throw {
32
+ code: '500',
33
+ message: err.message,
34
+ };
35
+ }
36
+ };
37
+
38
+ const signInWithEmailAndPassword = async (auth, email, password) => {
39
+ try {
40
+ await auth.signIn.email({
41
+ email,
42
+ password,
43
+ //callbackURL: '/dashboard',
44
+ });
45
+
46
+ const { data } = await auth.getSession();
47
+ const user = data?.user ?? null;
48
+
49
+ const userCredential = { user };
50
+
51
+ return userCredential;
52
+
53
+ } catch (err) {
54
+ throw {
55
+ code: '500',
56
+ message: err.message,
57
+ };
58
+ }
59
+ };
60
+
61
+ const sendEmailVerification = async (currentUser) => {
62
+ /** // Must have following function on the server:
63
+ * // https://better-auth.com/docs/concepts/email
64
+ * import { betterAuth } from 'better-auth';
65
+ import { sendEmail } from './email'; // your email sending function
66
+ export const auth = betterAuth({
67
+ emailVerification: {
68
+ sendVerificationEmail: async ({ user, url, token }, request) => {
69
+ void sendEmail({
70
+ to: user.email,
71
+ subject: 'Verify your email address',
72
+ text: `Click the link to verify your email: ${url}`
73
+ })
74
+ }
75
+ }
76
+ })
77
+ */
78
+ await postbaseAuthClientWithBetterAuthFunctions.sendVerificationEmail({
79
+ email: currentUser.email
80
+ });
81
+ };
82
+
83
+ const updateProfile = async () => { };
84
+
85
+ const updateEmail = async () => { };
86
+
87
+ const updatePassword = async () => { };
88
+
89
+ const sendPasswordResetEmail = async (auth, email) => {
90
+ await auth.requestPasswordReset({
91
+ email,
92
+ });
93
+
94
+ };
95
+
96
+ const signOut = async (auth) => {
97
+ const authToken = window.sessionStorage.getItem('authToken');
98
+ if (authToken) {
99
+ window.sessionStorage.removeItem('authToken');
100
+ }
101
+ // at the end because it can trigger navigation
102
+ auth.signOut.apply(this, []);
103
+ };
104
+
105
+ return {
106
+ createUserWithEmailAndPassword,
107
+ sendEmailVerification,
108
+ sendPasswordResetEmail,
109
+ signInWithEmailAndPassword,
110
+ updateProfile,
111
+ updateEmail,
112
+ updatePassword,
113
+ signOut,
114
+ };
115
+ }
@@ -0,0 +1,11 @@
1
+ import { getBetterAuthToken } from "../../../../src/auth";
2
+ import { RtdbClient } from "../../rtdb";
3
+
4
+ export function getDatabase(app) {
5
+ // app is just {} having baseUrl and getAuthToken
6
+ return new RtdbClient({
7
+ restUrl: app.baseUrl,
8
+ wsUrl: app.baseUrl.replace('https://', 'wss://'),
9
+ getAuthToken: getBetterAuthToken,
10
+ });
11
+ }