@freeappstore/sdk 0.1.1 → 0.2.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/LICENSE +21 -0
- package/dist/auth.d.ts +4 -0
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +21 -4
- package/dist/auth.js.map +1 -1
- package/dist/auth.test.d.ts +2 -0
- package/dist/auth.test.d.ts.map +1 -0
- package/dist/auth.test.js +149 -0
- package/dist/auth.test.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/kv.d.ts +0 -8
- package/dist/kv.d.ts.map +1 -1
- package/dist/kv.js +16 -0
- package/dist/kv.js.map +1 -1
- package/dist/kv.test.d.ts +2 -0
- package/dist/kv.test.d.ts.map +1 -0
- package/dist/kv.test.js +68 -0
- package/dist/kv.test.js.map +1 -0
- package/dist/proxy.d.ts +40 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +62 -0
- package/dist/proxy.js.map +1 -0
- package/dist/proxy.test.d.ts +2 -0
- package/dist/proxy.test.d.ts.map +1 -0
- package/dist/proxy.test.js +77 -0
- package/dist/proxy.test.js.map +1 -0
- package/dist/rooms.test.d.ts +2 -0
- package/dist/rooms.test.d.ts.map +1 -0
- package/dist/rooms.test.js +256 -0
- package/dist/rooms.test.js.map +1 -0
- package/package.json +14 -12
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 FreeAppStore
|
|
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/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;
|
package/dist/auth.d.ts.map
CHANGED
|
@@ -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
|
|
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
|
|
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',
|
|
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
|
-
|
|
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":"AAEA,MAAM,WAAW,GAAG,aAAa,CAAC;AAOlC,MAAM,OAAO,IAAI;IAKI;IACA;IALX,OAAO,GAAmB,IAAI,CAAC;IAC/B,SAAS,GAAG,IAAI,GAAG,EAA+B,CAAC;IAE3D,YACmB,KAAa,EACb,OAAe;QADf,UAAK,GAAL,KAAK,CAAQ;QACb,YAAO,GAAP,OAAO,CAAQ;QAEhC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IACpC,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,OAAO,EAAE,IAAI,IAAI,IAAI,CAAC;IACpC,CAAC;IAED,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,OAAO,EAAE,KAAK,IAAI,IAAI,CAAC;IACrC,CAAC;IAED,QAAQ,CAAC,QAAqC;QAC5C,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7B,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC/C,CAAC;IAED
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,GAAG,aAAa,CAAC;AAOlC,MAAM,OAAO,IAAI;IAKI;IACA;IALX,OAAO,GAAmB,IAAI,CAAC;IAC/B,SAAS,GAAG,IAAI,GAAG,EAA+B,CAAC;IAE3D,YACmB,KAAa,EACb,OAAe;QADf,UAAK,GAAL,KAAK,CAAQ;QACb,YAAO,GAAP,OAAO,CAAQ;QAEhC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IACpC,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,OAAO,EAAE,IAAI,IAAI,IAAI,CAAC;IACpC,CAAC;IAED,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,OAAO,EAAE,KAAK,IAAI,IAAI,CAAC;IACrC,CAAC;IAED,QAAQ,CAAC,QAAqC;QAC5C,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7B,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC/C,CAAC;IAED;;;;;;;OAOG;IACH,MAAM;QACJ,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC3C,IAAI,CAAC,IAAI,GAAG,EAAE,CAAC;QACf,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,uBAAuB,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QAC3D,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3C,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;QACnD,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;IACzC,CAAC;IAED,OAAO;QACL,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;QAC5C,IAAI,CAAC,IAAI,EAAE,CAAC;IACd,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,IAAI;QACR,IAAI,OAAO,MAAM,KAAK,WAAW;YAAE,OAAO;QAC1C,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;QAClC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,eAAe,CAAC;YAAE,OAAO;QAE9C,sEAAsE;QACtE,sEAAsE;QACtE,uCAAuC;QACvC,OAAO,CAAC,YAAY,CAAC,IAAI,EAAE,EAAE,EAAE,MAAM,CAAC,QAAQ,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAElF,IAAI,KAAa,CAAC;QAClB,IAAI,CAAC;YACH,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC;QACjE,CAAC;QAAC,MAAM,CAAC;YACP,qEAAqE;YACrE,OAAO;QACT,CAAC;QACD,IAAI,CAAC,KAAK;YAAE,OAAO;QAEnB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACzC,IAAI,CAAC,OAAO,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QAC/B,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAChC,IAAI,CAAC,IAAI,EAAE,CAAC;IACd,CAAC;IAEO,KAAK,CAAC,SAAS,CAAC,KAAa;QACnC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,IAAI,GAAG,CAAC,aAAa,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE;YAC5D,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,KAAK,EAAE,EAAE;SAC9C,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,gBAAgB,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;QAC3D,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAS,CAAC;IACpC,CAAC;IAEO,WAAW;QACjB,IAAI,OAAO,MAAM,KAAK,WAAW;YAAE,OAAO,IAAI,CAAC;QAC/C,MAAM,GAAG,GAAG,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QACrD,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QACtB,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAY,CAAC;QACpC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAEO,YAAY,CAAC,OAAgB;QACnC,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;IACpE,CAAC;IAEO,IAAI;QACV,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS;YAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7D,CAAC;CACF"}
|
|
@@ -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":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Auth } from './auth.js';
|
|
2
2
|
import { Kv } from './kv.js';
|
|
3
3
|
import { Rooms } from './rooms.js';
|
|
4
|
+
import { Proxy } from './proxy.js';
|
|
4
5
|
import type { FasInitOptions } from './types.js';
|
|
5
6
|
export type { User, FasInitOptions, Unsubscribe } from './types.js';
|
|
6
7
|
export type { Room, RoomMessage, RoomPeer, ConnectionState } from './rooms.js';
|
|
@@ -8,6 +9,7 @@ export declare class FreeAppStore {
|
|
|
8
9
|
readonly auth: Auth;
|
|
9
10
|
readonly kv: Kv;
|
|
10
11
|
readonly rooms: Rooms;
|
|
12
|
+
readonly proxy: Proxy;
|
|
11
13
|
constructor(opts: FasInitOptions);
|
|
12
14
|
}
|
|
13
15
|
export declare function initApp(opts: FasInitOptions): FreeAppStore;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAC7B,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,YAAY,EAAE,IAAI,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACpE,YAAY,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE/E,qBAAa,YAAY;IACvB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC;IAChB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;gBAEV,IAAI,EAAE,cAAc;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAC7B,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,YAAY,EAAE,IAAI,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACpE,YAAY,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE/E,qBAAa,YAAY;IACvB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC;IAChB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;gBAEV,IAAI,EAAE,cAAc;CAOjC;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,cAAc,GAAG,YAAY,CAE1D"}
|
package/dist/index.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { Auth } from './auth.js';
|
|
2
2
|
import { Kv } from './kv.js';
|
|
3
3
|
import { Rooms } from './rooms.js';
|
|
4
|
+
import { Proxy } from './proxy.js';
|
|
4
5
|
export class FreeAppStore {
|
|
5
6
|
auth;
|
|
6
7
|
kv;
|
|
7
8
|
rooms;
|
|
9
|
+
proxy;
|
|
8
10
|
constructor(opts) {
|
|
9
11
|
const apiBase = opts.apiBase ?? 'https://api.freeappstore.online';
|
|
10
12
|
this.auth = new Auth(opts.appId, apiBase);
|
|
11
13
|
this.kv = new Kv(opts.appId, apiBase, this.auth);
|
|
12
14
|
this.rooms = new Rooms(opts.appId, apiBase, this.auth);
|
|
15
|
+
this.proxy = new Proxy(opts.appId, apiBase, this.auth);
|
|
13
16
|
}
|
|
14
17
|
}
|
|
15
18
|
export function initApp(opts) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAC7B,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAMnC,MAAM,OAAO,YAAY;IACd,IAAI,CAAO;IACX,EAAE,CAAK;IACP,KAAK,CAAQ;IAEtB,YAAY,IAAoB;QAC9B,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,iCAAiC,CAAC;QAClE,IAAI,CAAC,IAAI,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAC1C,IAAI,CAAC,EAAE,GAAG,IAAI,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QACjD,IAAI,CAAC,KAAK,GAAG,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IACzD,CAAC;CACF;AAED,MAAM,UAAU,OAAO,CAAC,IAAoB;IAC1C,OAAO,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC;AAChC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAC7B,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAMnC,MAAM,OAAO,YAAY;IACd,IAAI,CAAO;IACX,EAAE,CAAK;IACP,KAAK,CAAQ;IACb,KAAK,CAAQ;IAEtB,YAAY,IAAoB;QAC9B,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,iCAAiC,CAAC;QAClE,IAAI,CAAC,IAAI,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAC1C,IAAI,CAAC,EAAE,GAAG,IAAI,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QACjD,IAAI,CAAC,KAAK,GAAG,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QACvD,IAAI,CAAC,KAAK,GAAG,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IACzD,CAAC;CACF;AAED,MAAM,UAAU,OAAO,CAAC,IAAoB;IAC1C,OAAO,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC;AAChC,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;
|
|
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
|
|
1
|
+
{"version":3,"file":"kv.js","sourceRoot":"","sources":["../src/kv.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kv.test.d.ts","sourceRoot":"","sources":["../src/kv.test.ts"],"names":[],"mappings":""}
|
package/dist/kv.test.js
ADDED
|
@@ -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/dist/proxy.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Auth } from './auth.js';
|
|
2
|
+
/**
|
|
3
|
+
* Browser-side wrapper around the platform's per-app secret-injecting proxy.
|
|
4
|
+
*
|
|
5
|
+
* Pattern:
|
|
6
|
+
* const fas = initApp({ appId: 'weather' });
|
|
7
|
+
* const res = await fas.proxy.fetch(
|
|
8
|
+
* 'api.openweathermap.org/data/2.5/weather?q=London',
|
|
9
|
+
* );
|
|
10
|
+
*
|
|
11
|
+
* The first segment is the upstream host; the rest is path + query. The
|
|
12
|
+
* platform Worker authenticates the call with the user's session token,
|
|
13
|
+
* matches the URL against the app's allowlist, decrypts the developer's
|
|
14
|
+
* stored API key, and forwards the request server-side.
|
|
15
|
+
*
|
|
16
|
+
* The developer's secret never touches the browser.
|
|
17
|
+
*/
|
|
18
|
+
export declare class Proxy {
|
|
19
|
+
private readonly appId;
|
|
20
|
+
private readonly apiBase;
|
|
21
|
+
private readonly auth;
|
|
22
|
+
constructor(appId: string, apiBase: string, auth: Auth);
|
|
23
|
+
/**
|
|
24
|
+
* Fetch via the proxy. Accepts either:
|
|
25
|
+
* - "host/path?query" (preferred, matches the SDK's CLI register form)
|
|
26
|
+
* - a full "https://host/path?query" URL (we strip the scheme)
|
|
27
|
+
*/
|
|
28
|
+
fetch(target: string, init?: RequestInit): Promise<Response>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Strip a leading scheme (`https://`, `http://`) so callers can paste either
|
|
32
|
+
* form and get the same result. Throws on schemes other than http(s) — the
|
|
33
|
+
* proxy only ever forwards over https upstream and we want a loud error
|
|
34
|
+
* rather than a silent rewrite.
|
|
35
|
+
*
|
|
36
|
+
* Scheme detection is case-insensitive: `HTTPS://api.example.com/x` should
|
|
37
|
+
* normalize the same as `https://api.example.com/x`.
|
|
38
|
+
*/
|
|
39
|
+
export declare function normalizeTarget(target: string): string;
|
|
40
|
+
//# sourceMappingURL=proxy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEtC;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,KAAK;IAEd,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;IAG7B;;;;OAIG;IACG,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC;CASnE;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAWtD"}
|
package/dist/proxy.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-side wrapper around the platform's per-app secret-injecting proxy.
|
|
3
|
+
*
|
|
4
|
+
* Pattern:
|
|
5
|
+
* const fas = initApp({ appId: 'weather' });
|
|
6
|
+
* const res = await fas.proxy.fetch(
|
|
7
|
+
* 'api.openweathermap.org/data/2.5/weather?q=London',
|
|
8
|
+
* );
|
|
9
|
+
*
|
|
10
|
+
* The first segment is the upstream host; the rest is path + query. The
|
|
11
|
+
* platform Worker authenticates the call with the user's session token,
|
|
12
|
+
* matches the URL against the app's allowlist, decrypts the developer's
|
|
13
|
+
* stored API key, and forwards the request server-side.
|
|
14
|
+
*
|
|
15
|
+
* The developer's secret never touches the browser.
|
|
16
|
+
*/
|
|
17
|
+
export class Proxy {
|
|
18
|
+
appId;
|
|
19
|
+
apiBase;
|
|
20
|
+
auth;
|
|
21
|
+
constructor(appId, apiBase, auth) {
|
|
22
|
+
this.appId = appId;
|
|
23
|
+
this.apiBase = apiBase;
|
|
24
|
+
this.auth = auth;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Fetch via the proxy. Accepts either:
|
|
28
|
+
* - "host/path?query" (preferred, matches the SDK's CLI register form)
|
|
29
|
+
* - a full "https://host/path?query" URL (we strip the scheme)
|
|
30
|
+
*/
|
|
31
|
+
async fetch(target, init) {
|
|
32
|
+
if (!this.auth.token) {
|
|
33
|
+
throw new Error('proxy.fetch: not signed in. Call fas.auth.login() first.');
|
|
34
|
+
}
|
|
35
|
+
const url = `${this.apiBase}/v1/apps/${this.appId}/proxy/${normalizeTarget(target)}`;
|
|
36
|
+
const headers = new Headers(init?.headers);
|
|
37
|
+
headers.set('Authorization', `Bearer ${this.auth.token}`);
|
|
38
|
+
return fetch(url, { ...init, headers });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Strip a leading scheme (`https://`, `http://`) so callers can paste either
|
|
43
|
+
* form and get the same result. Throws on schemes other than http(s) — the
|
|
44
|
+
* proxy only ever forwards over https upstream and we want a loud error
|
|
45
|
+
* rather than a silent rewrite.
|
|
46
|
+
*
|
|
47
|
+
* Scheme detection is case-insensitive: `HTTPS://api.example.com/x` should
|
|
48
|
+
* normalize the same as `https://api.example.com/x`.
|
|
49
|
+
*/
|
|
50
|
+
export function normalizeTarget(target) {
|
|
51
|
+
const schemeMatch = /^([a-z][a-z0-9+.-]*):\/\//i.exec(target);
|
|
52
|
+
if (schemeMatch) {
|
|
53
|
+
const scheme = schemeMatch[1].toLowerCase();
|
|
54
|
+
if (scheme === 'http' || scheme === 'https') {
|
|
55
|
+
return target.slice(schemeMatch[0].length);
|
|
56
|
+
}
|
|
57
|
+
throw new Error('proxy.fetch: only http(s) targets are supported');
|
|
58
|
+
}
|
|
59
|
+
// Already in "host/path" form.
|
|
60
|
+
return target.replace(/^\/+/, '');
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=proxy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,OAAO,KAAK;IAEG;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;;;;OAIG;IACH,KAAK,CAAC,KAAK,CAAC,MAAc,EAAE,IAAkB;QAC5C,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAC;QAC9E,CAAC;QACD,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,OAAO,YAAY,IAAI,CAAC,KAAK,UAAU,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC;QACrF,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC3C,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,UAAU,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;QAC1D,OAAO,KAAK,CAAC,GAAG,EAAE,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IAC1C,CAAC;CACF;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAAC,MAAc;IAC5C,MAAM,WAAW,GAAG,4BAA4B,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC9D,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,MAAM,GAAG,WAAW,CAAC,CAAC,CAAE,CAAC,WAAW,EAAE,CAAC;QAC7C,IAAI,MAAM,KAAK,MAAM,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;YAC5C,OAAO,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QAC7C,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;IACrE,CAAC;IACD,+BAA+B;IAC/B,OAAO,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACpC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy.test.d.ts","sourceRoot":"","sources":["../src/proxy.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { Proxy, normalizeTarget } from './proxy.js';
|
|
3
|
+
describe('normalizeTarget', () => {
|
|
4
|
+
it('passes through bare host/path form', () => {
|
|
5
|
+
expect(normalizeTarget('api.example.com/v1/x')).toBe('api.example.com/v1/x');
|
|
6
|
+
});
|
|
7
|
+
it('strips https:// and http:// prefixes', () => {
|
|
8
|
+
expect(normalizeTarget('https://api.example.com/v1/x')).toBe('api.example.com/v1/x');
|
|
9
|
+
expect(normalizeTarget('http://api.example.com/v1/x')).toBe('api.example.com/v1/x');
|
|
10
|
+
});
|
|
11
|
+
it('matches the scheme case-insensitively', () => {
|
|
12
|
+
expect(normalizeTarget('HTTPS://api.example.com/v1/x')).toBe('api.example.com/v1/x');
|
|
13
|
+
expect(normalizeTarget('Http://api.example.com/v1/x')).toBe('api.example.com/v1/x');
|
|
14
|
+
});
|
|
15
|
+
it('strips leading slashes from bare form', () => {
|
|
16
|
+
expect(normalizeTarget('//api.example.com/v1')).toBe('api.example.com/v1');
|
|
17
|
+
});
|
|
18
|
+
it('preserves query strings', () => {
|
|
19
|
+
expect(normalizeTarget('api.example.com/x?a=1&b=2')).toBe('api.example.com/x?a=1&b=2');
|
|
20
|
+
});
|
|
21
|
+
it('rejects non-http(s) schemes', () => {
|
|
22
|
+
expect(() => normalizeTarget('ftp://api.example.com/x')).toThrow(/http\(s\)/);
|
|
23
|
+
expect(() => normalizeTarget('javascript://x')).toThrow(/http\(s\)/);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
describe('Proxy.fetch', () => {
|
|
27
|
+
it('throws when not signed in', async () => {
|
|
28
|
+
const auth = { token: null };
|
|
29
|
+
const proxy = new Proxy('weather', 'https://api.example.com', auth);
|
|
30
|
+
await expect(proxy.fetch('api.openweathermap.org/data/2.5/weather')).rejects.toThrow(/not signed in/);
|
|
31
|
+
});
|
|
32
|
+
it('builds the right URL and forwards Bearer auth', async () => {
|
|
33
|
+
const auth = { token: 'tok-123' };
|
|
34
|
+
const proxy = new Proxy('weather', 'https://api.example.com', auth);
|
|
35
|
+
const captured = {};
|
|
36
|
+
const origFetch = globalThis.fetch;
|
|
37
|
+
globalThis.fetch = vi.fn(async (url, init) => {
|
|
38
|
+
captured.url = String(url);
|
|
39
|
+
captured.init = init;
|
|
40
|
+
return new Response('ok');
|
|
41
|
+
});
|
|
42
|
+
try {
|
|
43
|
+
await proxy.fetch('api.openweathermap.org/data/2.5/weather?q=London');
|
|
44
|
+
expect(captured.url).toBe('https://api.example.com/v1/apps/weather/proxy/api.openweathermap.org/data/2.5/weather?q=London');
|
|
45
|
+
const h = new Headers(captured.init?.headers);
|
|
46
|
+
expect(h.get('authorization')).toBe('Bearer tok-123');
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
globalThis.fetch = origFetch;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
it('preserves caller-supplied headers and method', async () => {
|
|
53
|
+
const auth = { token: 'tok' };
|
|
54
|
+
const proxy = new Proxy('weather', 'https://api.example.com', auth);
|
|
55
|
+
const captured = {};
|
|
56
|
+
const origFetch = globalThis.fetch;
|
|
57
|
+
globalThis.fetch = vi.fn(async (_url, init) => {
|
|
58
|
+
captured.init = init;
|
|
59
|
+
return new Response('ok');
|
|
60
|
+
});
|
|
61
|
+
try {
|
|
62
|
+
await proxy.fetch('api.example.com/v1/x', {
|
|
63
|
+
method: 'POST',
|
|
64
|
+
headers: { 'Content-Type': 'application/json' },
|
|
65
|
+
body: '{}',
|
|
66
|
+
});
|
|
67
|
+
expect(captured.init?.method).toBe('POST');
|
|
68
|
+
const h = new Headers(captured.init?.headers);
|
|
69
|
+
expect(h.get('content-type')).toBe('application/json');
|
|
70
|
+
expect(h.get('authorization')).toBe('Bearer tok');
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
globalThis.fetch = origFetch;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
//# sourceMappingURL=proxy.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy.test.js","sourceRoot":"","sources":["../src/proxy.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rooms.test.d.ts","sourceRoot":"","sources":["../src/rooms.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { Rooms } from './rooms.js';
|
|
3
|
+
/**
|
|
4
|
+
* Minimal WebSocket stand-in. Tests poke at `created[i]` to drive open/
|
|
5
|
+
* message/error/close events imperatively.
|
|
6
|
+
*/
|
|
7
|
+
class FakeWebSocket {
|
|
8
|
+
static CONNECTING = 0;
|
|
9
|
+
static OPEN = 1;
|
|
10
|
+
static CLOSING = 2;
|
|
11
|
+
static CLOSED = 3;
|
|
12
|
+
readyState = FakeWebSocket.CONNECTING;
|
|
13
|
+
sent = [];
|
|
14
|
+
url;
|
|
15
|
+
listeners = new Map();
|
|
16
|
+
constructor(url) {
|
|
17
|
+
this.url = url.toString();
|
|
18
|
+
created.push(this);
|
|
19
|
+
}
|
|
20
|
+
addEventListener(type, fn) {
|
|
21
|
+
if (!this.listeners.has(type))
|
|
22
|
+
this.listeners.set(type, new Set());
|
|
23
|
+
this.listeners.get(type).add(fn);
|
|
24
|
+
}
|
|
25
|
+
send(data) {
|
|
26
|
+
this.sent.push(data);
|
|
27
|
+
}
|
|
28
|
+
close() {
|
|
29
|
+
if (this.readyState !== FakeWebSocket.CLOSED) {
|
|
30
|
+
this.readyState = FakeWebSocket.CLOSED;
|
|
31
|
+
this.fire('close', {});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
triggerOpen() {
|
|
35
|
+
this.readyState = FakeWebSocket.OPEN;
|
|
36
|
+
this.fire('open', {});
|
|
37
|
+
}
|
|
38
|
+
triggerMessage(data) {
|
|
39
|
+
this.fire('message', { data });
|
|
40
|
+
}
|
|
41
|
+
triggerError() {
|
|
42
|
+
this.fire('error', {});
|
|
43
|
+
}
|
|
44
|
+
triggerClose() {
|
|
45
|
+
this.readyState = FakeWebSocket.CLOSED;
|
|
46
|
+
this.fire('close', {});
|
|
47
|
+
}
|
|
48
|
+
fire(type, ev) {
|
|
49
|
+
this.listeners.get(type)?.forEach((fn) => fn(ev));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
let created = [];
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
created = [];
|
|
55
|
+
globalThis['WebSocket'] = FakeWebSocket;
|
|
56
|
+
});
|
|
57
|
+
afterEach(() => {
|
|
58
|
+
delete globalThis['WebSocket'];
|
|
59
|
+
vi.useRealTimers();
|
|
60
|
+
});
|
|
61
|
+
function fakeAuth(token) {
|
|
62
|
+
return { token };
|
|
63
|
+
}
|
|
64
|
+
function newRoom(token = 'tok') {
|
|
65
|
+
const rooms = new Rooms('demo', 'https://api.example', fakeAuth(token));
|
|
66
|
+
return rooms.join('lobby');
|
|
67
|
+
}
|
|
68
|
+
describe('Room.connect — initial state', () => {
|
|
69
|
+
it('starts in connecting state and creates a WebSocket immediately', () => {
|
|
70
|
+
const room = newRoom();
|
|
71
|
+
expect(created).toHaveLength(1);
|
|
72
|
+
expect(room.state).toBe('connecting');
|
|
73
|
+
expect(created[0].url).toMatch(/wss:\/\/api\.example\/v1\/apps\/demo\/rooms\/lobby/);
|
|
74
|
+
expect(created[0].url).toContain('token=tok');
|
|
75
|
+
});
|
|
76
|
+
it('encodes appId and roomId in the URL path', () => {
|
|
77
|
+
const rooms = new Rooms('app%name', 'https://api.example', fakeAuth('tok'));
|
|
78
|
+
rooms.join('room/with/slashes');
|
|
79
|
+
expect(created[0].url).toContain('/v1/apps/app%25name/rooms/room%2Fwith%2Fslashes');
|
|
80
|
+
});
|
|
81
|
+
it('with no auth token, sets state to closed and does NOT open a socket', () => {
|
|
82
|
+
const room = newRoom(null);
|
|
83
|
+
expect(created).toHaveLength(0);
|
|
84
|
+
expect(room.state).toBe('closed');
|
|
85
|
+
});
|
|
86
|
+
it('flips to open state when the socket opens', () => {
|
|
87
|
+
const room = newRoom();
|
|
88
|
+
created[0].triggerOpen();
|
|
89
|
+
expect(room.state).toBe('open');
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
describe('Room.onConnectionState', () => {
|
|
93
|
+
it('fires immediately with current state on subscribe', () => {
|
|
94
|
+
const room = newRoom();
|
|
95
|
+
const seen = [];
|
|
96
|
+
room.onConnectionState((s) => seen.push(s));
|
|
97
|
+
expect(seen).toEqual(['connecting']);
|
|
98
|
+
});
|
|
99
|
+
it('fires on every state transition', () => {
|
|
100
|
+
const room = newRoom();
|
|
101
|
+
const seen = [];
|
|
102
|
+
room.onConnectionState((s) => seen.push(s));
|
|
103
|
+
created[0].triggerOpen();
|
|
104
|
+
expect(seen).toEqual(['connecting', 'open']);
|
|
105
|
+
});
|
|
106
|
+
it('does not fire when state is set to the same value', () => {
|
|
107
|
+
const room = newRoom();
|
|
108
|
+
const seen = [];
|
|
109
|
+
room.onConnectionState((s) => seen.push(s));
|
|
110
|
+
// Trigger open twice — should only see one open transition.
|
|
111
|
+
created[0].triggerOpen();
|
|
112
|
+
created[0].triggerOpen();
|
|
113
|
+
expect(seen.filter((s) => s === 'open')).toHaveLength(1);
|
|
114
|
+
});
|
|
115
|
+
it('returns an unsubscribe function', () => {
|
|
116
|
+
const room = newRoom();
|
|
117
|
+
const seen = [];
|
|
118
|
+
const unsub = room.onConnectionState((s) => seen.push(s));
|
|
119
|
+
unsub();
|
|
120
|
+
created[0].triggerOpen(); // should NOT fire after unsub
|
|
121
|
+
expect(seen).toEqual(['connecting']);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
describe('Room.send', () => {
|
|
125
|
+
it('serializes data to a kind:msg envelope when socket is open', () => {
|
|
126
|
+
const room = newRoom();
|
|
127
|
+
created[0].triggerOpen();
|
|
128
|
+
room.send({ hello: 'world' });
|
|
129
|
+
expect(created[0].sent).toEqual([JSON.stringify({ kind: 'msg', data: { hello: 'world' } })]);
|
|
130
|
+
});
|
|
131
|
+
it('silently drops messages when socket is not yet open (regression: no throw)', () => {
|
|
132
|
+
const room = newRoom();
|
|
133
|
+
expect(() => room.send({ hello: 'world' })).not.toThrow();
|
|
134
|
+
expect(created[0].sent).toEqual([]);
|
|
135
|
+
});
|
|
136
|
+
it('silently drops messages after close', () => {
|
|
137
|
+
const room = newRoom();
|
|
138
|
+
created[0].triggerOpen();
|
|
139
|
+
room.close();
|
|
140
|
+
room.send({ hello: 'world' });
|
|
141
|
+
// After close, socket is null — no sent message.
|
|
142
|
+
expect(created[0].sent).toEqual([]);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
describe('Room.onMessage', () => {
|
|
146
|
+
it('receives kind:msg frames as RoomMessage', () => {
|
|
147
|
+
const room = newRoom();
|
|
148
|
+
created[0].triggerOpen();
|
|
149
|
+
const messages = [];
|
|
150
|
+
room.onMessage((m) => messages.push(m));
|
|
151
|
+
created[0].triggerMessage(JSON.stringify({
|
|
152
|
+
kind: 'msg',
|
|
153
|
+
from: { uid: 'gh:1', login: 'alice' },
|
|
154
|
+
data: { text: 'hi' },
|
|
155
|
+
at: 1700000000000,
|
|
156
|
+
}));
|
|
157
|
+
expect(messages).toHaveLength(1);
|
|
158
|
+
expect(messages[0].from).toEqual({ uid: 'gh:1', login: 'alice' });
|
|
159
|
+
expect(messages[0].data).toEqual({ text: 'hi' });
|
|
160
|
+
});
|
|
161
|
+
it('ignores malformed JSON frames without throwing', () => {
|
|
162
|
+
const room = newRoom();
|
|
163
|
+
created[0].triggerOpen();
|
|
164
|
+
const messages = [];
|
|
165
|
+
room.onMessage((m) => messages.push(m));
|
|
166
|
+
expect(() => created[0].triggerMessage('not-json{')).not.toThrow();
|
|
167
|
+
expect(messages).toEqual([]);
|
|
168
|
+
});
|
|
169
|
+
it('ignores frames with unknown `kind`', () => {
|
|
170
|
+
const room = newRoom();
|
|
171
|
+
created[0].triggerOpen();
|
|
172
|
+
const messages = [];
|
|
173
|
+
room.onMessage((m) => messages.push(m));
|
|
174
|
+
created[0].triggerMessage(JSON.stringify({ kind: 'something-else', data: {} }));
|
|
175
|
+
expect(messages).toEqual([]);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
describe('Room.onPeers', () => {
|
|
179
|
+
it('fires immediately with empty peers list on subscribe', () => {
|
|
180
|
+
const room = newRoom();
|
|
181
|
+
const calls = [];
|
|
182
|
+
room.onPeers((peers) => calls.push(peers));
|
|
183
|
+
expect(calls).toEqual([[]]);
|
|
184
|
+
});
|
|
185
|
+
it('updates on kind:peers frame', () => {
|
|
186
|
+
const room = newRoom();
|
|
187
|
+
created[0].triggerOpen();
|
|
188
|
+
const calls = [];
|
|
189
|
+
room.onPeers((peers) => calls.push(peers));
|
|
190
|
+
created[0].triggerMessage(JSON.stringify({
|
|
191
|
+
kind: 'peers',
|
|
192
|
+
peers: [{ uid: 'gh:1', login: 'alice' }, { uid: 'gh:2', login: 'bob' }],
|
|
193
|
+
}));
|
|
194
|
+
expect(calls).toHaveLength(2); // initial + the broadcast
|
|
195
|
+
expect(calls[1]).toEqual([
|
|
196
|
+
{ uid: 'gh:1', login: 'alice' },
|
|
197
|
+
{ uid: 'gh:2', login: 'bob' },
|
|
198
|
+
]);
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
describe('Room.close', () => {
|
|
202
|
+
it('stops reconnect, clears listeners, sets state to closed', () => {
|
|
203
|
+
const room = newRoom();
|
|
204
|
+
const stateSeen = [];
|
|
205
|
+
const messages = [];
|
|
206
|
+
room.onConnectionState((s) => stateSeen.push(s));
|
|
207
|
+
room.onMessage((m) => messages.push(m));
|
|
208
|
+
created[0].triggerOpen();
|
|
209
|
+
room.close();
|
|
210
|
+
expect(room.state).toBe('closed');
|
|
211
|
+
// Sending should have no effect (socket cleared).
|
|
212
|
+
room.send({ x: 1 });
|
|
213
|
+
expect(created[0].sent.filter((s) => s.includes('"x":1'))).toEqual([]);
|
|
214
|
+
});
|
|
215
|
+
it('cancels a pending reconnect timer', () => {
|
|
216
|
+
vi.useFakeTimers();
|
|
217
|
+
const room = newRoom();
|
|
218
|
+
created[0].triggerOpen();
|
|
219
|
+
created[0].triggerClose(); // unexpected close → schedules reconnect
|
|
220
|
+
expect(room.state).toBe('closed');
|
|
221
|
+
room.close(); // user-initiated; should cancel reconnect
|
|
222
|
+
vi.advanceTimersByTime(60_000); // jump past any backoff
|
|
223
|
+
expect(created).toHaveLength(1); // no second connect
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
describe('Room reconnect on unexpected close', () => {
|
|
227
|
+
it('opens a new WebSocket after backoff', () => {
|
|
228
|
+
vi.useFakeTimers();
|
|
229
|
+
const room = newRoom();
|
|
230
|
+
created[0].triggerOpen();
|
|
231
|
+
expect(created).toHaveLength(1);
|
|
232
|
+
created[0].triggerClose(); // unexpected — schedules reconnect
|
|
233
|
+
expect(room.state).toBe('closed');
|
|
234
|
+
// First reconnect attempt fires after RECONNECT_BASE_MS (1000ms) + jitter (up to 1000ms).
|
|
235
|
+
vi.advanceTimersByTime(2100);
|
|
236
|
+
expect(created.length).toBeGreaterThanOrEqual(2);
|
|
237
|
+
expect(room.state).toBe('connecting');
|
|
238
|
+
});
|
|
239
|
+
it('opens succeed → state goes back to open + reconnect counter resets', () => {
|
|
240
|
+
vi.useFakeTimers();
|
|
241
|
+
const room = newRoom();
|
|
242
|
+
created[0].triggerOpen();
|
|
243
|
+
created[0].triggerClose();
|
|
244
|
+
vi.advanceTimersByTime(2100);
|
|
245
|
+
created[1].triggerOpen();
|
|
246
|
+
expect(room.state).toBe('open');
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
describe('Room error handling', () => {
|
|
250
|
+
it('socket error → state goes to error', () => {
|
|
251
|
+
const room = newRoom();
|
|
252
|
+
created[0].triggerError();
|
|
253
|
+
expect(room.state).toBe('error');
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
//# sourceMappingURL=rooms.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rooms.test.js","sourceRoot":"","sources":["../src/rooms.test.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@freeappstore/sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Browser SDK for free apps on freeappstore.online — auth, per-user KV, light realtime rooms.",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Browser SDK for free apps on freeappstore.online — auth, per-user KV, light realtime rooms, secret-injecting proxy for third-party APIs.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.js",
|
|
@@ -12,14 +12,14 @@
|
|
|
12
12
|
"import": "./dist/index.js"
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
|
-
"homepage": "https://github.com/freeappstore-online/
|
|
15
|
+
"homepage": "https://github.com/freeappstore-online/platform#readme",
|
|
16
16
|
"repository": {
|
|
17
17
|
"type": "git",
|
|
18
|
-
"url": "git+https://github.com/freeappstore-online/
|
|
18
|
+
"url": "git+https://github.com/freeappstore-online/platform.git",
|
|
19
19
|
"directory": "packages/sdk"
|
|
20
20
|
},
|
|
21
21
|
"bugs": {
|
|
22
|
-
"url": "https://github.com/freeappstore-online/
|
|
22
|
+
"url": "https://github.com/freeappstore-online/platform/issues"
|
|
23
23
|
},
|
|
24
24
|
"keywords": [
|
|
25
25
|
"freeappstore",
|
|
@@ -29,18 +29,20 @@
|
|
|
29
29
|
"rooms",
|
|
30
30
|
"websocket",
|
|
31
31
|
"github-oauth",
|
|
32
|
-
"cloudflare-workers"
|
|
32
|
+
"cloudflare-workers",
|
|
33
|
+
"proxy",
|
|
34
|
+
"api-key"
|
|
33
35
|
],
|
|
34
36
|
"files": [
|
|
35
37
|
"dist",
|
|
36
38
|
"README.md"
|
|
37
39
|
],
|
|
38
|
-
"scripts": {
|
|
39
|
-
"build": "tsc",
|
|
40
|
-
"typecheck": "tsc --noEmit"
|
|
41
|
-
},
|
|
42
40
|
"devDependencies": {
|
|
43
41
|
"typescript": "^5.7.0"
|
|
44
42
|
},
|
|
45
|
-
"sideEffects": false
|
|
46
|
-
|
|
43
|
+
"sideEffects": false,
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsc",
|
|
46
|
+
"typecheck": "tsc --noEmit"
|
|
47
|
+
}
|
|
48
|
+
}
|