magic-link-signin-wromo 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.gitattributes ADDED
@@ -0,0 +1,2 @@
1
+ # Auto detect text files and perform LF normalization
2
+ * text=auto
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Studio Wromo Marketplace
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # magic-link-signin-wromo
2
+ Signin with magic links by firebase on static page - widgets by wromo
3
+
4
+
5
+ ## widgets includes full style preview login/register page to connect firebase.
6
+
7
+ Important: in the static model KEY is entered in json format auth-config.json : KEY ID must be strictly blocked to respond only on your domain!
8
+
9
+
10
+
11
+ ## Configuration steps:
12
+ Add the following script url in header css and js in footer exactly as below (for NPM package installations no need for auth-config.json file: Attach in .env file)
13
+
14
+ TO HEAD
15
+ ````
16
+ <link rel="stylesheet" href="./styles.css">
17
+ </head>
18
+ ````
19
+
20
+ TO BODY
21
+ ````
22
+ <div
23
+ data-wromo-auth
24
+ data-wromo-auth-config="./auth-config.json" // <-- Your file firebase KEY >
25
+ data-wromo-auth-redirect="https://RETURN_URL.com"> // <-- Your URL Return Page >
26
+ </div>
27
+
28
+ <script type="module" src="https://cdn.jsdelivr.net/npm/magic-link-signin-wromo@1.0.0/wromo-auth-widget.js" defer=""></script>
29
+
30
+ </body>
31
+ </html>
32
+ ````
33
+
34
+
35
+ Congratulations!
36
+
37
+ ![Preview webpage signin with magic links](/img/image.png)
package/img/image.png ADDED
Binary file
package/index.js ADDED
@@ -0,0 +1,14 @@
1
+ import { mount } from './wromo-auth-widget.js';
2
+
3
+ // We export the main widget object that contains the mount function
4
+ const WromoAuthWidget = {
5
+ mount: mount
6
+ };
7
+
8
+ // We allow default import
9
+ export default WromoAuthWidget;
10
+
11
+ // Attach the widget directly to the window object for maximum flexibility in the browser
12
+ if (typeof window !== 'undefined') {
13
+ window.WromoAuthWidget = WromoAuthWidget;
14
+ }
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "magic-link-signin-wromo",
3
+ "version": "1.0.0",
4
+ "description": "A universal magic link sign-in widget with Firebase Authentication. By Ghepes - with ❤️ for Wromo and the web.",
5
+ "main": "index.js",
6
+ "unpkg": "wromo-auth-widget.js",
7
+ "jsdelivr": "wromo-auth-widget.js",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/Ghepes/magic-link-signin-wromo.git"
11
+ },
12
+ "keywords": [
13
+ "magic-link",
14
+ "sign-in",
15
+ "firebase",
16
+ "authentication",
17
+ "widget"
18
+ ],
19
+ "author": "Ghepes",
20
+ "license": "MIT"
21
+ }
package/styles.css ADDED
@@ -0,0 +1,9 @@
1
+ body {
2
+ background: linear-gradient(180deg, #f7fbf6 0%, #eef4ef 100%);
3
+ font-family: "Trebuchet MS", "Segoe UI", sans-serif;
4
+ margin: 0;
5
+ min-height: 100vh;
6
+ display: flex;
7
+ align-items: center;
8
+ justify-content: center;
9
+ }
@@ -0,0 +1,206 @@
1
+ const STYLE_ID = "wromo-auth-widget-styles";
2
+ const INJECTED_STYLE = [
3
+ ":root {",
4
+ " --wa-panel: rgba(255, 255, 255, 0.96);",
5
+ " --wa-ink: #163126;",
6
+ " --wa-muted: #5d7267;",
7
+ " --wa-line: rgba(22, 49, 38, 0.12);",
8
+ " --wa-accent: #1e8e64;",
9
+ " --wa-shadow: 0 24px 48px rgba(18, 45, 35, 0.12);",
10
+ "}",
11
+ ".wa-mount {",
12
+ " width: min(400px, 100%);",
13
+ " margin: 0 auto;",
14
+ "}",
15
+ ".wa-card {",
16
+ " background: var(--wa-panel);",
17
+ " border: 1px solid var(--wa-line);",
18
+ " border-radius: 26px;",
19
+ " padding: 32px 28px;",
20
+ " box-shadow: var(--wa-shadow);",
21
+ " text-align: center;",
22
+ "}",
23
+ ".wa-title {",
24
+ " margin: 0 0 8px;",
25
+ " color: var(--wa-ink);",
26
+ " font: 700 1.4rem/1.3 'Trebuchet MS', sans-serif;",
27
+ "}",
28
+ ".wa-subtitle {",
29
+ " margin: 0 0 24px;",
30
+ " color: var(--wa-muted);",
31
+ " font: 400 0.95rem/1.5 Georgia, serif;",
32
+ "}",
33
+ ".wa-form { display: grid; gap: 16px; }",
34
+ ".wa-input {",
35
+ " width: 100%;",
36
+ " padding: 0 18px;",
37
+ " min-height: 52px;",
38
+ " border: 1px solid var(--wa-line);",
39
+ " border-radius: 999px;",
40
+ " font: 500 1rem/1.4 'Trebuchet MS', sans-serif;",
41
+ " color: var(--wa-ink);",
42
+ " background: rgba(255, 255, 255, 0.9);",
43
+ " box-sizing: border-box;",
44
+ " outline: none;",
45
+ " transition: border-color 0.2s;",
46
+ "}",
47
+ ".wa-input:focus { border-color: var(--wa-accent); }",
48
+ ".wa-btn {",
49
+ " width: 100%;",
50
+ " min-height: 52px;",
51
+ " background: var(--wa-ink);",
52
+ " color: #fff;",
53
+ " border: none;",
54
+ " border-radius: 999px;",
55
+ " font: 700 1rem/1 'Trebuchet MS', sans-serif;",
56
+ " cursor: pointer;",
57
+ " transition: background 0.2s;",
58
+ "}",
59
+ ".wa-btn:hover { background: var(--wa-accent); }",
60
+ ".wa-btn:disabled { background: var(--wa-muted); cursor: not-allowed; }",
61
+ ".wa-status {",
62
+ " margin-top: 16px;",
63
+ " font: 500 0.9rem/1.4 'Trebuchet MS', sans-serif;",
64
+ "}",
65
+ ".wa-status.success { color: var(--wa-accent); }",
66
+ ".wa-status.error { color: #d93025; }"
67
+ ].join("\n");
68
+
69
+ function injectStyles() {
70
+ if (document.getElementById(STYLE_ID)) return;
71
+ const style = document.createElement("style");
72
+ style.id = STYLE_ID;
73
+ style.textContent = INJECTED_STYLE;
74
+ document.head.appendChild(style);
75
+ }
76
+
77
+ function createAuthMarkup(target) {
78
+ const wrapper = document.createElement("div");
79
+ wrapper.className = "wa-mount";
80
+ wrapper.innerHTML = `
81
+ <div class="wa-card">
82
+ <h2 class="wa-title">Secure Login</h2>
83
+ <p class="wa-subtitle">We'll send a magic link to your inbox. No password needed.</p>
84
+ <form class="wa-form" id="wa-login-form">
85
+ <input type="email" id="wa-email" class="wa-input" placeholder="name@domain.com" required autocomplete="email">
86
+ <button type="submit" id="wa-submit" class="wa-btn">Send Magic Link</button>
87
+ </form>
88
+ <div id="wa-message" class="wa-status"></div>
89
+ </div>
90
+ `;
91
+ target.appendChild(wrapper);
92
+ return {
93
+ form: wrapper.querySelector("#wa-login-form"),
94
+ emailInput: wrapper.querySelector("#wa-email"),
95
+ submitBtn: wrapper.querySelector("#wa-submit"),
96
+ messageBox: wrapper.querySelector("#wa-message")
97
+ };
98
+ }
99
+
100
+ function showMessage(elements, text, type) {
101
+ elements.messageBox.textContent = text;
102
+ elements.messageBox.className = "wa-status " + type;
103
+ }
104
+
105
+ async function initFirebaseAuth() {
106
+ const nodes = document.querySelectorAll("[data-wromo-auth]");
107
+ if (nodes.length === 0) return;
108
+
109
+ injectStyles();
110
+
111
+ // Import Firebase Auth dynamically, without build tools
112
+ // Import Firebase Auth dynamically, using the new version 12.14.0
113
+ const { initializeApp } = await import("https://www.gstatic.com/firebasejs/12.14.0/firebase-app.js");
114
+ const { getAuth, sendSignInLinkToEmail, isSignInWithEmailLink, signInWithEmailLink } = await import("https://www.gstatic.com/firebasejs/12.14.0/firebase-auth.js");
115
+
116
+ for (const node of nodes) {
117
+ if (node.dataset.ready === "true") continue;
118
+ node.dataset.ready = "true";
119
+
120
+ const configUrl = node.getAttribute("data-wromo-auth-config");
121
+ const redirectUrl = node.getAttribute("data-wromo-auth-redirect") || window.location.href;
122
+
123
+ try {
124
+ const configRes = await fetch(configUrl);
125
+ const firebaseConfig = await configRes.json();
126
+ const app = initializeApp(firebaseConfig);
127
+ const auth = getAuth(app);
128
+
129
+ const elements = createAuthMarkup(node);
130
+
131
+ // Check if the user just returned from the email by clicking on the link
132
+ if (isSignInWithEmailLink(auth, window.location.href)) {
133
+ elements.emailInput.style.display = "none";
134
+ elements.submitBtn.style.display = "none";
135
+ showMessage(elements, "Authenticating...", "success");
136
+
137
+ let email = window.localStorage.getItem('emailForSignIn');
138
+ if (!email) {
139
+ // If they opened the link on a different device, we ask for their email for security confirmation
140
+ email = window.prompt('Please provide your email for confirmation');
141
+ }
142
+
143
+ try {
144
+ // 1. We perform the login and capture the result
145
+ const result = await signInWithEmailLink(auth, email, window.location.href);
146
+
147
+ // 2. Extract the permanent UID provided by Firebase
148
+ const userUid = result.user.uid;
149
+
150
+ // 3. Save the UID in the browser's Local Storage
151
+ window.localStorage.setItem('wromo_uid', userUid);
152
+
153
+ // Clear the temporary email used for login
154
+ window.localStorage.removeItem('emailForSignIn');
155
+
156
+ showMessage(elements, "Success! Redirecting...", "success");
157
+ window.location.href = redirectUrl;
158
+ } catch (error) {
159
+ showMessage(elements, "Link expired or invalid. Please try again.", "error");
160
+ elements.emailInput.style.display = "block";
161
+ elements.submitBtn.style.display = "block";
162
+ }
163
+ return; // We stop execution to not show the empty form
164
+ }
165
+
166
+ // Function for sending the login link
167
+ elements.form.addEventListener("submit", async (e) => {
168
+ e.preventDefault();
169
+ elements.submitBtn.disabled = true;
170
+ elements.submitBtn.textContent = "Sending...";
171
+
172
+ const email = elements.emailInput.value.trim();
173
+ const actionCodeSettings = {
174
+ url: window.location.href, // We redirect back to the same page to complete the logic
175
+ handleCodeInApp: true
176
+ };
177
+
178
+ try {
179
+ await sendSignInLinkToEmail(auth, email, actionCodeSettings);
180
+ window.localStorage.setItem('emailForSignIn', email);
181
+ showMessage(elements, "Check your inbox for the magic link!", "success");
182
+ elements.form.reset();
183
+ } catch (error) {
184
+ showMessage(elements, error.message, "error");
185
+ } finally {
186
+ elements.submitBtn.disabled = false;
187
+ elements.submitBtn.textContent = "Send Magic Link";
188
+ }
189
+ });
190
+
191
+ } catch (err) {
192
+ node.innerHTML = `<div class="wa-status error">Auth widget failed to load.</div>`;
193
+ console.error(err);
194
+ }
195
+ }
196
+ }
197
+
198
+ export { initFirebaseAuth as mount };
199
+
200
+ if (typeof window !== 'undefined') {
201
+ if (document.readyState === "loading") {
202
+ document.addEventListener("DOMContentLoaded", initFirebaseAuth);
203
+ } else {
204
+ initFirebaseAuth();
205
+ }
206
+ }