@freeappstore/sdk 0.1.1 → 0.1.2

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/auth.d.ts CHANGED
@@ -11,6 +11,10 @@ export declare class Auth {
11
11
  /**
12
12
  * Redirect-based GitHub OAuth. Opens the platform's hosted OAuth start URL,
13
13
  * which redirects back to the current page with a session token in the hash.
14
+ *
15
+ * The current page's `location.hash` is dropped from `return_to` because
16
+ * the OAuth callback writes its own `#fas_session=…` and would clobber any
17
+ * hash-based router state otherwise.
14
18
  */
15
19
  signIn(): void;
16
20
  signOut(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AASpD,qBAAa,IAAI;IAKb,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAL1B,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,SAAS,CAA0C;gBAGxC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM;IAKlC,IAAI,IAAI,IAAI,IAAI,GAAG,IAAI,CAEtB;IAED,IAAI,KAAK,IAAI,MAAM,GAAG,IAAI,CAEzB;IAED,QAAQ,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,KAAK,IAAI,GAAG,WAAW;IAK5D;;;OAGG;IACH,MAAM,IAAI,IAAI;IAQd,OAAO,IAAI,IAAI;IAMf;;;;;;;;;;;;OAYG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAYb,SAAS;IAQvB,OAAO,CAAC,WAAW;IAWnB,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,IAAI;CAGb"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AASpD,qBAAa,IAAI;IAKb,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAL1B,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,SAAS,CAA0C;gBAGxC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM;IAKlC,IAAI,IAAI,IAAI,IAAI,GAAG,IAAI,CAEtB;IAED,IAAI,KAAK,IAAI,MAAM,GAAG,IAAI,CAEzB;IAED,QAAQ,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,KAAK,IAAI,GAAG,WAAW;IAK5D;;;;;;;OAOG;IACH,MAAM,IAAI,IAAI;IASd,OAAO,IAAI,IAAI;IAMf;;;;;;;;;;;;OAYG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAyBb,SAAS;IAQvB,OAAO,CAAC,WAAW;IAWnB,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,IAAI;CAGb"}
package/dist/auth.js CHANGED
@@ -22,12 +22,17 @@ export class Auth {
22
22
  /**
23
23
  * Redirect-based GitHub OAuth. Opens the platform's hosted OAuth start URL,
24
24
  * which redirects back to the current page with a session token in the hash.
25
+ *
26
+ * The current page's `location.hash` is dropped from `return_to` because
27
+ * the OAuth callback writes its own `#fas_session=…` and would clobber any
28
+ * hash-based router state otherwise.
25
29
  */
26
30
  signIn() {
27
- const returnTo = window.location.href;
31
+ const here = new URL(window.location.href);
32
+ here.hash = '';
28
33
  const url = new URL('/v1/auth/github/start', this.apiBase);
29
34
  url.searchParams.set('app_id', this.appId);
30
- url.searchParams.set('return_to', returnTo);
35
+ url.searchParams.set('return_to', here.toString());
31
36
  window.location.assign(url.toString());
32
37
  }
33
38
  signOut() {
@@ -54,11 +59,23 @@ export class Auth {
54
59
  const hash = window.location.hash;
55
60
  if (!hash.startsWith('#fas_session='))
56
61
  return;
57
- const token = decodeURIComponent(hash.slice('#fas_session='.length));
62
+ // Always clear the hash before doing anything else — even on failure.
63
+ // Otherwise a bad token gets re-tried on every reload and the user is
64
+ // permanently stuck on a "broken" URL.
65
+ history.replaceState(null, '', window.location.pathname + window.location.search);
66
+ let token;
67
+ try {
68
+ token = decodeURIComponent(hash.slice('#fas_session='.length));
69
+ }
70
+ catch {
71
+ // Malformed hash (% with nothing after, etc.). Hash already cleared.
72
+ return;
73
+ }
74
+ if (!token)
75
+ return;
58
76
  const user = await this.fetchUser(token);
59
77
  this.session = { token, user };
60
78
  this.writeStorage(this.session);
61
- history.replaceState(null, '', window.location.pathname + window.location.search);
62
79
  this.emit();
63
80
  }
64
81
  async fetchUser(token) {
package/dist/auth.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=auth.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.test.d.ts","sourceRoot":"","sources":["../src/auth.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,149 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { Auth } from './auth.js';
3
+ function makeWindow(initial) {
4
+ const url = new URL(initial.href);
5
+ if (initial.hash)
6
+ url.hash = initial.hash;
7
+ const assigns = [];
8
+ const replaces = [];
9
+ const storage = {
10
+ store: new Map(),
11
+ getItem: (k) => storage.store.get(k) ?? null,
12
+ setItem: (k, v) => void storage.store.set(k, v),
13
+ removeItem: (k) => void storage.store.delete(k),
14
+ };
15
+ return {
16
+ window: {
17
+ location: {
18
+ href: url.toString(),
19
+ hash: url.hash,
20
+ pathname: url.pathname,
21
+ search: url.search,
22
+ assign: (u) => assigns.push(u),
23
+ },
24
+ localStorage: storage,
25
+ history: {
26
+ replaceState: (_s, _t, u) => replaces.push(u),
27
+ },
28
+ },
29
+ assigns,
30
+ replaces,
31
+ };
32
+ }
33
+ beforeEach(() => {
34
+ // happy-dom-style globals; we install per-test
35
+ delete globalThis['window'];
36
+ delete globalThis['history'];
37
+ delete globalThis['fetch'];
38
+ });
39
+ afterEach(() => {
40
+ delete globalThis['window'];
41
+ delete globalThis['history'];
42
+ delete globalThis['fetch'];
43
+ });
44
+ describe('Auth.signIn', () => {
45
+ it('strips the hash from return_to so the OAuth callback does not clobber app router state', () => {
46
+ const { window, assigns } = makeWindow({
47
+ href: 'https://app.freeappstore.online/dashboard?team=42#/settings/profile',
48
+ });
49
+ globalThis['window'] = window;
50
+ const auth = new Auth('demo', 'https://api.freeappstore.online');
51
+ auth.signIn();
52
+ expect(assigns).toHaveLength(1);
53
+ const target = new URL(assigns[0]);
54
+ const returnTo = target.searchParams.get('return_to');
55
+ expect(returnTo).toBe('https://app.freeappstore.online/dashboard?team=42');
56
+ // No #/settings/profile carried into return_to.
57
+ expect(returnTo).not.toContain('#');
58
+ });
59
+ it('still preserves pathname + querystring', () => {
60
+ const { window, assigns } = makeWindow({ href: 'https://app.example/page?x=1' });
61
+ globalThis['window'] = window;
62
+ const auth = new Auth('demo', 'https://api.example');
63
+ auth.signIn();
64
+ const returnTo = new URL(assigns[0]).searchParams.get('return_to');
65
+ expect(returnTo).toBe('https://app.example/page?x=1');
66
+ });
67
+ });
68
+ describe('Auth.init — hash handling', () => {
69
+ it('clears the hash even when the auth fetch fails (regression: no stuck state)', async () => {
70
+ const { window, replaces } = makeWindow({
71
+ href: 'https://app.example/',
72
+ hash: '#fas_session=bad-token',
73
+ });
74
+ globalThis['window'] = window;
75
+ globalThis['history'] = window.history;
76
+ globalThis.fetch = vi.fn().mockResolvedValue(new Response('', { status: 401 }));
77
+ const auth = new Auth('demo', 'https://api.example');
78
+ await expect(auth.init()).rejects.toThrow();
79
+ // The hash was cleared before the fetch, so reload won't retry.
80
+ expect(replaces).toHaveLength(1);
81
+ expect(replaces[0]).toBe('/');
82
+ expect(auth.user).toBeNull();
83
+ });
84
+ it('captures session and clears hash on successful auth callback', async () => {
85
+ const { window, replaces } = makeWindow({
86
+ href: 'https://app.example/dashboard',
87
+ hash: '#fas_session=good-token',
88
+ });
89
+ globalThis['window'] = window;
90
+ globalThis['history'] = window.history;
91
+ globalThis.fetch = vi.fn().mockResolvedValue(new Response(JSON.stringify({ id: 'gh:1', login: 'alice', avatarUrl: null }), {
92
+ status: 200,
93
+ }));
94
+ const auth = new Auth('demo', 'https://api.example');
95
+ await auth.init();
96
+ expect(auth.user?.login).toBe('alice');
97
+ expect(auth.token).toBe('good-token');
98
+ expect(window.localStorage.store.get('fas:session')).toContain('good-token');
99
+ expect(replaces[0]).toBe('/dashboard');
100
+ });
101
+ it('is a no-op when no fas_session hash is present', async () => {
102
+ const { window, replaces } = makeWindow({ href: 'https://app.example/' });
103
+ globalThis['window'] = window;
104
+ globalThis['history'] = window.history;
105
+ const fetchSpy = vi.fn();
106
+ globalThis.fetch = fetchSpy;
107
+ const auth = new Auth('demo', 'https://api.example');
108
+ await auth.init();
109
+ expect(fetchSpy).not.toHaveBeenCalled();
110
+ expect(replaces).toHaveLength(0);
111
+ expect(auth.user).toBeNull();
112
+ });
113
+ });
114
+ describe('Auth — storage round-trip', () => {
115
+ it('reads cached session from localStorage in the constructor', () => {
116
+ const { window } = makeWindow({ href: 'https://app.example/' });
117
+ window.localStorage.store.set('fas:session', JSON.stringify({
118
+ token: 'cached',
119
+ user: { id: 'gh:7', login: 'returning-user', avatarUrl: null },
120
+ }));
121
+ globalThis['window'] = window;
122
+ const auth = new Auth('demo', 'https://api.example');
123
+ expect(auth.user?.login).toBe('returning-user');
124
+ expect(auth.token).toBe('cached');
125
+ });
126
+ it('signOut clears storage and notifies listeners', () => {
127
+ const { window } = makeWindow({ href: 'https://app.example/' });
128
+ window.localStorage.store.set('fas:session', JSON.stringify({
129
+ token: 'cached',
130
+ user: { id: 'gh:7', login: 'u', avatarUrl: null },
131
+ }));
132
+ globalThis['window'] = window;
133
+ const auth = new Auth('demo', 'https://api.example');
134
+ const seen = [];
135
+ auth.onChange((u) => seen.push(u));
136
+ auth.signOut();
137
+ expect(auth.user).toBeNull();
138
+ expect(window.localStorage.store.has('fas:session')).toBe(false);
139
+ expect(seen).toEqual([null]);
140
+ });
141
+ it('returns null when localStorage has corrupt JSON (does not crash)', () => {
142
+ const { window } = makeWindow({ href: 'https://app.example/' });
143
+ window.localStorage.store.set('fas:session', '{ not: json');
144
+ globalThis['window'] = window;
145
+ const auth = new Auth('demo', 'https://api.example');
146
+ expect(auth.user).toBeNull();
147
+ });
148
+ });
149
+ //# sourceMappingURL=auth.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.test.js","sourceRoot":"","sources":["../src/auth.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACzE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAejC,SAAS,UAAU,CAAC,OAAwC;IAC1D,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,IAAI,OAAO,CAAC,IAAI;QAAE,GAAG,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAC1C,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,OAAO,GAAgB;QAC3B,KAAK,EAAE,IAAI,GAAG,EAAE;QAChB,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI;QAC5C,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;QAC/C,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;KAChD,CAAC;IACF,OAAO;QACL,MAAM,EAAE;YACN,QAAQ,EAAE;gBACR,IAAI,EAAE,GAAG,CAAC,QAAQ,EAAE;gBACpB,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,QAAQ,EAAE,GAAG,CAAC,QAAQ;gBACtB,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,MAAM,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;aACvC;YACD,YAAY,EAAE,OAAO;YACrB,OAAO,EAAE;gBACP,YAAY,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;aAC9C;SACF;QACD,OAAO;QACP,QAAQ;KACT,CAAC;AACJ,CAAC;AAED,UAAU,CAAC,GAAG,EAAE;IACd,+CAA+C;IAC/C,OAAQ,UAAsC,CAAC,QAAQ,CAAC,CAAC;IACzD,OAAQ,UAAsC,CAAC,SAAS,CAAC,CAAC;IAC1D,OAAQ,UAAsC,CAAC,OAAO,CAAC,CAAC;AAC1D,CAAC,CAAC,CAAC;AAEH,SAAS,CAAC,GAAG,EAAE;IACb,OAAQ,UAAsC,CAAC,QAAQ,CAAC,CAAC;IACzD,OAAQ,UAAsC,CAAC,SAAS,CAAC,CAAC;IAC1D,OAAQ,UAAsC,CAAC,OAAO,CAAC,CAAC;AAC1D,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,EAAE,CAAC,wFAAwF,EAAE,GAAG,EAAE;QAChG,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,UAAU,CAAC;YACrC,IAAI,EAAE,qEAAqE;SAC5E,CAAC,CAAC;QACF,UAAsC,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC;QAC3D,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,MAAM,EAAE,iCAAiC,CAAC,CAAC;QAEjE,IAAI,CAAC,MAAM,EAAE,CAAC;QAEd,MAAM,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAChC,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC,CAAC;QACpC,MAAM,QAAQ,GAAG,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QACtD,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,mDAAmD,CAAC,CAAC;QAC3E,gDAAgD;QAChD,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,UAAU,CAAC,EAAE,IAAI,EAAE,8BAA8B,EAAE,CAAC,CAAC;QAChF,UAAsC,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC;QAC3D,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;QACrD,IAAI,CAAC,MAAM,EAAE,CAAC;QACd,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QACpE,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,2BAA2B,EAAE,GAAG,EAAE;IACzC,EAAE,CAAC,6EAA6E,EAAE,KAAK,IAAI,EAAE;QAC3F,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,UAAU,CAAC;YACtC,IAAI,EAAE,sBAAsB;YAC5B,IAAI,EAAE,wBAAwB;SAC/B,CAAC,CAAC;QACF,UAAsC,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC;QAC1D,UAAsC,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC;QACpE,UAAU,CAAC,KAAK,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,IAAI,QAAQ,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;QAEhF,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;QACrD,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QAE5C,gEAAgE;QAChE,MAAM,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACjC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC/B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8DAA8D,EAAE,KAAK,IAAI,EAAE;QAC5E,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,UAAU,CAAC;YACtC,IAAI,EAAE,+BAA+B;YACrC,IAAI,EAAE,yBAAyB;SAChC,CAAC,CAAC;QACF,UAAsC,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC;QAC1D,UAAsC,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC;QACpE,UAAU,CAAC,KAAK,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAC1C,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,EAAE;YAC5E,MAAM,EAAE,GAAG;SACZ,CAAC,CACH,CAAC;QAEF,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;QACrD,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAElB,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACvC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACtC,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;QAC7E,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,KAAK,IAAI,EAAE;QAC9D,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,UAAU,CAAC,EAAE,IAAI,EAAE,sBAAsB,EAAE,CAAC,CAAC;QACzE,UAAsC,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC;QAC1D,UAAsC,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC;QACpE,MAAM,QAAQ,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QACzB,UAAU,CAAC,KAAK,GAAG,QAAQ,CAAC;QAE5B,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;QACrD,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAElB,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QACxC,MAAM,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACjC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC/B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,2BAA2B,EAAE,GAAG,EAAE;IACzC,EAAE,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACnE,MAAM,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC,EAAE,IAAI,EAAE,sBAAsB,EAAE,CAAC,CAAC;QAChE,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,GAAG,CAC3B,aAAa,EACb,IAAI,CAAC,SAAS,CAAC;YACb,KAAK,EAAE,QAAQ;YACf,IAAI,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,IAAI,EAAE;SAC/D,CAAC,CACH,CAAC;QACD,UAAsC,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC;QAE3D,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;QACrD,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAChD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC,EAAE,IAAI,EAAE,sBAAsB,EAAE,CAAC,CAAC;QAChE,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,GAAG,CAC3B,aAAa,EACb,IAAI,CAAC,SAAS,CAAC;YACb,KAAK,EAAE,QAAQ;YACf,IAAI,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE;SAClD,CAAC,CACH,CAAC;QACD,UAAsC,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC;QAE3D,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;QACrD,MAAM,IAAI,GAAuB,EAAE,CAAC;QACpC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QAEnC,IAAI,CAAC,OAAO,EAAE,CAAC;QAEf,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC7B,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjE,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAC/B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kEAAkE,EAAE,GAAG,EAAE;QAC1E,MAAM,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC,EAAE,IAAI,EAAE,sBAAsB,EAAE,CAAC,CAAC;QAChE,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;QAC3D,UAAsC,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC;QAE3D,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;QACrD,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC/B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
package/dist/kv.d.ts CHANGED
@@ -1,12 +1,4 @@
1
1
  import type { Auth } from './auth.js';
2
- /**
3
- * Per-user key-value store, scoped to (appId, userId).
4
- *
5
- * Limits (enforced server-side):
6
- * - max 1MB total per user
7
- * - max 100 keys per user
8
- * - max 64KB per value
9
- */
10
2
  export declare class Kv {
11
3
  private readonly appId;
12
4
  private readonly apiBase;
package/dist/kv.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"kv.d.ts","sourceRoot":"","sources":["../src/kv.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEtC;;;;;;;GAOG;AACH,qBAAa,EAAE;IAEX,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAFJ,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,IAAI;IAGvB,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAOhD,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAatD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOxC,OAAO,CAAC,OAAO;CAahB"}
1
+ {"version":3,"file":"kv.d.ts","sourceRoot":"","sources":["../src/kv.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAyBtC,qBAAa,EAAE;IAEX,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAFJ,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,IAAI;IAGvB,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAQhD,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IActD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQxC,OAAO,CAAC,OAAO;CAahB"}
package/dist/kv.js CHANGED
@@ -5,7 +5,20 @@
5
5
  * - max 1MB total per user
6
6
  * - max 100 keys per user
7
7
  * - max 64KB per value
8
+ *
9
+ * Keys are non-empty strings ≤ 128 chars. We validate client-side so the
10
+ * server doesn't have to deal with edge-case URLs like `/kv/` or absurdly
11
+ * long path segments.
8
12
  */
13
+ const MAX_KEY_LENGTH = 128;
14
+ function assertValidKey(key) {
15
+ if (typeof key !== 'string' || key.length === 0) {
16
+ throw new Error('kv key must be a non-empty string.');
17
+ }
18
+ if (key.length > MAX_KEY_LENGTH) {
19
+ throw new Error(`kv key exceeds ${MAX_KEY_LENGTH} chars.`);
20
+ }
21
+ }
9
22
  export class Kv {
10
23
  appId;
11
24
  apiBase;
@@ -16,6 +29,7 @@ export class Kv {
16
29
  this.auth = auth;
17
30
  }
18
31
  async get(key) {
32
+ assertValidKey(key);
19
33
  const res = await this.request('GET', key);
20
34
  if (res.status === 404)
21
35
  return null;
@@ -24,6 +38,7 @@ export class Kv {
24
38
  return (await res.json());
25
39
  }
26
40
  async set(key, value) {
41
+ assertValidKey(key);
27
42
  // JSON.stringify(undefined) returns undefined, which would store an empty
28
43
  // body and break later get() calls. Reject up front instead.
29
44
  if (value === undefined) {
@@ -36,6 +51,7 @@ export class Kv {
36
51
  }
37
52
  }
38
53
  async delete(key) {
54
+ assertValidKey(key);
39
55
  const res = await this.request('DELETE', key);
40
56
  if (!res.ok && res.status !== 404) {
41
57
  throw new Error(`kv.delete failed: ${res.status}`);
package/dist/kv.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"kv.js","sourceRoot":"","sources":["../src/kv.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,MAAM,OAAO,EAAE;IAEM;IACA;IACA;IAHnB,YACmB,KAAa,EACb,OAAe,EACf,IAAU;QAFV,UAAK,GAAL,KAAK,CAAQ;QACb,YAAO,GAAP,OAAO,CAAQ;QACf,SAAI,GAAJ,IAAI,CAAM;IAC1B,CAAC;IAEJ,KAAK,CAAC,GAAG,CAAc,GAAW;QAChC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAC3C,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC;QACpC,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,kBAAkB,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;QAC7D,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAM,CAAC;IACjC,CAAC;IAED,KAAK,CAAC,GAAG,CAAc,GAAW,EAAE,KAAQ;QAC1C,0EAA0E;QAC1E,6DAA6D;QAC7D,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAC;QACrF,CAAC;QACD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;QAClE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CAAC,kBAAkB,GAAG,CAAC,MAAM,MAAM,IAAI,EAAE,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,GAAW;QACtB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;QAC9C,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAClC,MAAM,IAAI,KAAK,CAAC,qBAAqB,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;QACrD,CAAC;IACH,CAAC;IAEO,OAAO,CAAC,MAAc,EAAE,GAAW,EAAE,IAAa;QACxD,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;QAC9B,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,gBAAgB,CAAC,CAAC;QAC9C,MAAM,GAAG,GAAG,IAAI,GAAG,CACjB,YAAY,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,GAAG,CAAC,EAAE,EAC1E,IAAI,CAAC,OAAO,CACb,CAAC;QACF,MAAM,OAAO,GAA2B,EAAE,aAAa,EAAE,UAAU,KAAK,EAAE,EAAE,CAAC;QAC7E,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAAC;QACrE,MAAM,IAAI,GAAgB,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;QAC9C,IAAI,IAAI,KAAK,SAAS;YAAE,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACzC,OAAO,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAC1B,CAAC;CACF"}
1
+ {"version":3,"file":"kv.js","sourceRoot":"","sources":["../src/kv.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=kv.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kv.test.d.ts","sourceRoot":"","sources":["../src/kv.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,68 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { Kv } from './kv.js';
3
+ function fakeAuth(token) {
4
+ return { token };
5
+ }
6
+ beforeEach(() => {
7
+ // 204/304 require a null body per WHATWG; an empty string would throw.
8
+ globalThis.fetch = vi.fn().mockResolvedValue(new Response(null, { status: 204 }));
9
+ });
10
+ afterEach(() => {
11
+ delete globalThis['fetch'];
12
+ });
13
+ describe('Kv key validation', () => {
14
+ const kv = (token = 'tok') => new Kv('demo', 'https://api.example', fakeAuth(token));
15
+ it('rejects empty-string key (regression: would yield /kv/ URL and a confusing 404)', async () => {
16
+ await expect(kv().get('')).rejects.toThrow(/non-empty/);
17
+ await expect(kv().set('', 1)).rejects.toThrow(/non-empty/);
18
+ await expect(kv().delete('')).rejects.toThrow(/non-empty/);
19
+ });
20
+ it('rejects keys longer than 128 chars', async () => {
21
+ const long = 'x'.repeat(129);
22
+ await expect(kv().set(long, 1)).rejects.toThrow(/128/);
23
+ });
24
+ it('accepts a 128-char key (boundary)', async () => {
25
+ const ok = 'x'.repeat(128);
26
+ await expect(kv().set(ok, 1)).resolves.toBeUndefined();
27
+ });
28
+ it('accepts ordinary keys', async () => {
29
+ await expect(kv().set('foo', 1)).resolves.toBeUndefined();
30
+ await expect(kv().set('a/b', 1)).resolves.toBeUndefined();
31
+ await expect(kv().set('color-preference', 1)).resolves.toBeUndefined();
32
+ });
33
+ });
34
+ describe('Kv.set value validation', () => {
35
+ const kv = () => new Kv('demo', 'https://api.example', fakeAuth('tok'));
36
+ it('rejects undefined (regression: would store empty body, then break get)', async () => {
37
+ await expect(kv().set('k', undefined)).rejects.toThrow(/undefined/);
38
+ });
39
+ it('accepts null as a stored value', async () => {
40
+ await expect(kv().set('k', null)).resolves.toBeUndefined();
41
+ });
42
+ });
43
+ describe('Kv auth gate', () => {
44
+ it('throws Not signed in when no session token', async () => {
45
+ const k = new Kv('demo', 'https://api.example', fakeAuth(null));
46
+ await expect(k.get('foo')).rejects.toThrow(/not signed in/i);
47
+ });
48
+ });
49
+ describe('Kv.get behavior', () => {
50
+ it('returns null on 404, the missing-key signal', async () => {
51
+ globalThis.fetch = vi.fn().mockResolvedValue(new Response('', { status: 404 }));
52
+ const k = new Kv('demo', 'https://api.example', fakeAuth('tok'));
53
+ expect(await k.get('missing')).toBeNull();
54
+ });
55
+ it('parses JSON on 200', async () => {
56
+ globalThis.fetch = vi
57
+ .fn()
58
+ .mockResolvedValue(new Response(JSON.stringify({ x: 1 }), { status: 200 }));
59
+ const k = new Kv('demo', 'https://api.example', fakeAuth('tok'));
60
+ expect(await k.get('foo')).toEqual({ x: 1 });
61
+ });
62
+ it('throws on non-404 errors', async () => {
63
+ globalThis.fetch = vi.fn().mockResolvedValue(new Response('', { status: 500 }));
64
+ const k = new Kv('demo', 'https://api.example', fakeAuth('tok'));
65
+ await expect(k.get('foo')).rejects.toThrow(/500/);
66
+ });
67
+ });
68
+ //# sourceMappingURL=kv.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kv.test.js","sourceRoot":"","sources":["../src/kv.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@freeappstore/sdk",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Browser SDK for free apps on freeappstore.online — auth, per-user KV, light realtime rooms.",
5
5
  "license": "MIT",
6
6
  "type": "module",