@onjunctiondev/auth 0.2.0 → 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/index.cjs +87 -7
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +87 -7
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -61,29 +61,80 @@ function localStorageStorage(key) {
|
|
|
61
61
|
}
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
|
+
function cookieStorage(key) {
|
|
65
|
+
const canUse = typeof globalThis !== "undefined" && typeof globalThis.document !== "undefined";
|
|
66
|
+
return {
|
|
67
|
+
get() {
|
|
68
|
+
if (!canUse) return null;
|
|
69
|
+
try {
|
|
70
|
+
const jar = globalThis.document.cookie;
|
|
71
|
+
if (!jar) return null;
|
|
72
|
+
const prefix = `${key}=`;
|
|
73
|
+
for (const part of jar.split("; ")) {
|
|
74
|
+
if (part.startsWith(prefix)) {
|
|
75
|
+
const value = part.slice(prefix.length);
|
|
76
|
+
if (!value) return null;
|
|
77
|
+
return JSON.parse(decodeURIComponent(value));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
} catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
set(session) {
|
|
86
|
+
if (!canUse) return;
|
|
87
|
+
try {
|
|
88
|
+
const doc = globalThis.document;
|
|
89
|
+
const loc = globalThis.location;
|
|
90
|
+
const isHttps = loc?.protocol === "https:";
|
|
91
|
+
const secure = isHttps ? "; Secure" : "";
|
|
92
|
+
if (session) {
|
|
93
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
94
|
+
const maxAge = Math.max(0, session.expires_at - now);
|
|
95
|
+
const value = encodeURIComponent(JSON.stringify(session));
|
|
96
|
+
doc.cookie = `${key}=${value}; Path=/; Max-Age=${maxAge}; SameSite=Lax${secure}`;
|
|
97
|
+
} else {
|
|
98
|
+
doc.cookie = `${key}=; Path=/; Max-Age=0; SameSite=Lax${secure}`;
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
64
105
|
|
|
65
106
|
// src/client.ts
|
|
66
|
-
var DEFAULT_STORAGE_KEY = "
|
|
67
|
-
function pickStorage(opts) {
|
|
68
|
-
const key = opts.storageKey ?? DEFAULT_STORAGE_KEY;
|
|
107
|
+
var DEFAULT_STORAGE_KEY = "__junction_session";
|
|
108
|
+
function pickStorage(opts, key) {
|
|
69
109
|
if (opts.storage === "memory") return memoryStorage();
|
|
70
|
-
if (
|
|
71
|
-
|
|
72
|
-
|
|
110
|
+
if (opts.storage === "localStorage") return localStorageStorage(key);
|
|
111
|
+
const hasDocument = typeof globalThis !== "undefined" && typeof globalThis.document !== "undefined";
|
|
112
|
+
if (hasDocument) return cookieStorage(key);
|
|
73
113
|
return memoryStorage();
|
|
74
114
|
}
|
|
75
115
|
var AuthClient = class {
|
|
76
116
|
#deps;
|
|
77
117
|
#listeners = /* @__PURE__ */ new Set();
|
|
118
|
+
#channel;
|
|
119
|
+
#selfId;
|
|
78
120
|
// Refresh in-flight — concurrent 401s share one refresh so we don't
|
|
79
121
|
// race-rotate the single-use token on the server.
|
|
80
122
|
#inflightRefresh = null;
|
|
81
123
|
constructor(opts) {
|
|
124
|
+
const key = opts.storageKey ?? DEFAULT_STORAGE_KEY;
|
|
82
125
|
this.#deps = {
|
|
83
126
|
url: opts.url.replace(/\/+$/, ""),
|
|
84
127
|
fetchFn: opts.fetch ?? ((...args) => fetch(...args)),
|
|
85
|
-
storage: pickStorage(opts)
|
|
128
|
+
storage: pickStorage(opts, key)
|
|
86
129
|
};
|
|
130
|
+
this.#selfId = randomId();
|
|
131
|
+
this.#channel = openChannel(`junction.auth.${key}`);
|
|
132
|
+
this.#channel?.addEventListener("message", (e) => {
|
|
133
|
+
const payload = e.data;
|
|
134
|
+
if (!payload || payload.from === this.#selfId) return;
|
|
135
|
+
this.#deps.storage.set(payload.session);
|
|
136
|
+
this.#emitLocal(payload.event, payload.session);
|
|
137
|
+
});
|
|
87
138
|
}
|
|
88
139
|
// --- public API --------------------------------------------------------
|
|
89
140
|
async signUp(credentials) {
|
|
@@ -202,7 +253,22 @@ var AuthClient = class {
|
|
|
202
253
|
async #rawFetch(path, init) {
|
|
203
254
|
return this.#deps.fetchFn(`${this.#deps.url}${path}`, init);
|
|
204
255
|
}
|
|
256
|
+
// Emits locally AND broadcasts to peer tabs.
|
|
205
257
|
#emit(event, session) {
|
|
258
|
+
this.#emitLocal(event, session);
|
|
259
|
+
try {
|
|
260
|
+
this.#channel?.postMessage({
|
|
261
|
+
from: this.#selfId,
|
|
262
|
+
event,
|
|
263
|
+
session
|
|
264
|
+
});
|
|
265
|
+
} catch {
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
// Emits to in-process subscribers only — used both by local actions
|
|
269
|
+
// (via #emit, which also broadcasts) and by inbound broadcast messages
|
|
270
|
+
// (direct, so we don't re-broadcast and loop).
|
|
271
|
+
#emitLocal(event, session) {
|
|
206
272
|
for (const l of this.#listeners) {
|
|
207
273
|
try {
|
|
208
274
|
l(event, session);
|
|
@@ -211,6 +277,20 @@ var AuthClient = class {
|
|
|
211
277
|
}
|
|
212
278
|
}
|
|
213
279
|
};
|
|
280
|
+
function openChannel(name) {
|
|
281
|
+
const Ctor = globalThis.BroadcastChannel;
|
|
282
|
+
if (!Ctor) return null;
|
|
283
|
+
try {
|
|
284
|
+
return new Ctor(name);
|
|
285
|
+
} catch {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
function randomId() {
|
|
290
|
+
const crypto = globalThis.crypto;
|
|
291
|
+
if (crypto?.randomUUID) return crypto.randomUUID();
|
|
292
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
293
|
+
}
|
|
214
294
|
function errorFrom(code, message, status) {
|
|
215
295
|
return { code, message, status };
|
|
216
296
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -34,7 +34,7 @@ type AuthEvent = "SIGNED_IN" | "SIGNED_OUT" | "TOKEN_REFRESHED" | "USER_UPDATED"
|
|
|
34
34
|
type AuthStateChangeHandler = (event: AuthEvent, session: AuthSession | null) => void;
|
|
35
35
|
type ClientOptions = {
|
|
36
36
|
url: string;
|
|
37
|
-
storage?: "localStorage" | "memory";
|
|
37
|
+
storage?: "cookie" | "localStorage" | "memory";
|
|
38
38
|
storageKey?: string;
|
|
39
39
|
fetch?: typeof fetch;
|
|
40
40
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ type AuthEvent = "SIGNED_IN" | "SIGNED_OUT" | "TOKEN_REFRESHED" | "USER_UPDATED"
|
|
|
34
34
|
type AuthStateChangeHandler = (event: AuthEvent, session: AuthSession | null) => void;
|
|
35
35
|
type ClientOptions = {
|
|
36
36
|
url: string;
|
|
37
|
-
storage?: "localStorage" | "memory";
|
|
37
|
+
storage?: "cookie" | "localStorage" | "memory";
|
|
38
38
|
storageKey?: string;
|
|
39
39
|
fetch?: typeof fetch;
|
|
40
40
|
};
|
package/dist/index.js
CHANGED
|
@@ -34,29 +34,80 @@ function localStorageStorage(key) {
|
|
|
34
34
|
}
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
|
+
function cookieStorage(key) {
|
|
38
|
+
const canUse = typeof globalThis !== "undefined" && typeof globalThis.document !== "undefined";
|
|
39
|
+
return {
|
|
40
|
+
get() {
|
|
41
|
+
if (!canUse) return null;
|
|
42
|
+
try {
|
|
43
|
+
const jar = globalThis.document.cookie;
|
|
44
|
+
if (!jar) return null;
|
|
45
|
+
const prefix = `${key}=`;
|
|
46
|
+
for (const part of jar.split("; ")) {
|
|
47
|
+
if (part.startsWith(prefix)) {
|
|
48
|
+
const value = part.slice(prefix.length);
|
|
49
|
+
if (!value) return null;
|
|
50
|
+
return JSON.parse(decodeURIComponent(value));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
set(session) {
|
|
59
|
+
if (!canUse) return;
|
|
60
|
+
try {
|
|
61
|
+
const doc = globalThis.document;
|
|
62
|
+
const loc = globalThis.location;
|
|
63
|
+
const isHttps = loc?.protocol === "https:";
|
|
64
|
+
const secure = isHttps ? "; Secure" : "";
|
|
65
|
+
if (session) {
|
|
66
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
67
|
+
const maxAge = Math.max(0, session.expires_at - now);
|
|
68
|
+
const value = encodeURIComponent(JSON.stringify(session));
|
|
69
|
+
doc.cookie = `${key}=${value}; Path=/; Max-Age=${maxAge}; SameSite=Lax${secure}`;
|
|
70
|
+
} else {
|
|
71
|
+
doc.cookie = `${key}=; Path=/; Max-Age=0; SameSite=Lax${secure}`;
|
|
72
|
+
}
|
|
73
|
+
} catch {
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
37
78
|
|
|
38
79
|
// src/client.ts
|
|
39
|
-
var DEFAULT_STORAGE_KEY = "
|
|
40
|
-
function pickStorage(opts) {
|
|
41
|
-
const key = opts.storageKey ?? DEFAULT_STORAGE_KEY;
|
|
80
|
+
var DEFAULT_STORAGE_KEY = "__junction_session";
|
|
81
|
+
function pickStorage(opts, key) {
|
|
42
82
|
if (opts.storage === "memory") return memoryStorage();
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
|
|
83
|
+
if (opts.storage === "localStorage") return localStorageStorage(key);
|
|
84
|
+
const hasDocument = typeof globalThis !== "undefined" && typeof globalThis.document !== "undefined";
|
|
85
|
+
if (hasDocument) return cookieStorage(key);
|
|
46
86
|
return memoryStorage();
|
|
47
87
|
}
|
|
48
88
|
var AuthClient = class {
|
|
49
89
|
#deps;
|
|
50
90
|
#listeners = /* @__PURE__ */ new Set();
|
|
91
|
+
#channel;
|
|
92
|
+
#selfId;
|
|
51
93
|
// Refresh in-flight — concurrent 401s share one refresh so we don't
|
|
52
94
|
// race-rotate the single-use token on the server.
|
|
53
95
|
#inflightRefresh = null;
|
|
54
96
|
constructor(opts) {
|
|
97
|
+
const key = opts.storageKey ?? DEFAULT_STORAGE_KEY;
|
|
55
98
|
this.#deps = {
|
|
56
99
|
url: opts.url.replace(/\/+$/, ""),
|
|
57
100
|
fetchFn: opts.fetch ?? ((...args) => fetch(...args)),
|
|
58
|
-
storage: pickStorage(opts)
|
|
101
|
+
storage: pickStorage(opts, key)
|
|
59
102
|
};
|
|
103
|
+
this.#selfId = randomId();
|
|
104
|
+
this.#channel = openChannel(`junction.auth.${key}`);
|
|
105
|
+
this.#channel?.addEventListener("message", (e) => {
|
|
106
|
+
const payload = e.data;
|
|
107
|
+
if (!payload || payload.from === this.#selfId) return;
|
|
108
|
+
this.#deps.storage.set(payload.session);
|
|
109
|
+
this.#emitLocal(payload.event, payload.session);
|
|
110
|
+
});
|
|
60
111
|
}
|
|
61
112
|
// --- public API --------------------------------------------------------
|
|
62
113
|
async signUp(credentials) {
|
|
@@ -175,7 +226,22 @@ var AuthClient = class {
|
|
|
175
226
|
async #rawFetch(path, init) {
|
|
176
227
|
return this.#deps.fetchFn(`${this.#deps.url}${path}`, init);
|
|
177
228
|
}
|
|
229
|
+
// Emits locally AND broadcasts to peer tabs.
|
|
178
230
|
#emit(event, session) {
|
|
231
|
+
this.#emitLocal(event, session);
|
|
232
|
+
try {
|
|
233
|
+
this.#channel?.postMessage({
|
|
234
|
+
from: this.#selfId,
|
|
235
|
+
event,
|
|
236
|
+
session
|
|
237
|
+
});
|
|
238
|
+
} catch {
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// Emits to in-process subscribers only — used both by local actions
|
|
242
|
+
// (via #emit, which also broadcasts) and by inbound broadcast messages
|
|
243
|
+
// (direct, so we don't re-broadcast and loop).
|
|
244
|
+
#emitLocal(event, session) {
|
|
179
245
|
for (const l of this.#listeners) {
|
|
180
246
|
try {
|
|
181
247
|
l(event, session);
|
|
@@ -184,6 +250,20 @@ var AuthClient = class {
|
|
|
184
250
|
}
|
|
185
251
|
}
|
|
186
252
|
};
|
|
253
|
+
function openChannel(name) {
|
|
254
|
+
const Ctor = globalThis.BroadcastChannel;
|
|
255
|
+
if (!Ctor) return null;
|
|
256
|
+
try {
|
|
257
|
+
return new Ctor(name);
|
|
258
|
+
} catch {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
function randomId() {
|
|
263
|
+
const crypto = globalThis.crypto;
|
|
264
|
+
if (crypto?.randomUUID) return crypto.randomUUID();
|
|
265
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
266
|
+
}
|
|
187
267
|
function errorFrom(code, message, status) {
|
|
188
268
|
return { code, message, status };
|
|
189
269
|
}
|
package/package.json
CHANGED