@stopnetic/js 0.0.6-pilot → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +48 -48
  2. package/dist/client-guide.md +6 -3
  3. package/dist/server.d.ts +17 -17
  4. package/dist/server.js +125 -123
  5. package/dist/setup.js +126 -125
  6. package/examples/frontend/angular/app/app.config.ts +8 -8
  7. package/examples/frontend/angular/app/app.routes.ts +20 -20
  8. package/examples/frontend/angular/app/app.ts +21 -21
  9. package/examples/frontend/angular/app/members.page.ts +21 -21
  10. package/examples/frontend/angular/app/stopnetic-access.service.ts +27 -27
  11. package/examples/frontend/angular/main.ts +4 -4
  12. package/examples/frontend/load-private-data.d.ts +1 -1
  13. package/examples/frontend/load-private-data.js +9 -9
  14. package/examples/frontend/react/App.tsx +48 -48
  15. package/examples/frontend/react/main.tsx +3 -3
  16. package/examples/frontend/solid/App.tsx +54 -54
  17. package/examples/frontend/solid/main.tsx +3 -3
  18. package/examples/frontend/svelte/App.svelte +56 -56
  19. package/examples/frontend/svelte/main.js +4 -4
  20. package/examples/frontend/vanilla/main.js +46 -46
  21. package/examples/frontend/vue/App.vue +17 -17
  22. package/examples/frontend/vue/Members.vue +14 -14
  23. package/examples/frontend/vue/main.js +4 -4
  24. package/examples/frontend/vue/router.js +40 -40
  25. package/examples/ssr/angular/app.routes.server.ts +5 -5
  26. package/examples/ssr/angular/server.ts +12 -12
  27. package/examples/ssr/astro/middleware.js +11 -11
  28. package/examples/ssr/astro/page.astro +4 -4
  29. package/examples/ssr/node/fetch.mjs +9 -9
  30. package/examples/ssr/node/install.mjs +9 -9
  31. package/examples/ssr/nuxt/middleware.mjs +10 -10
  32. package/examples/ssr/react/install.mjs +10 -10
  33. package/examples/ssr/react-router/install.mjs +22 -22
  34. package/examples/ssr/solidstart/middleware-v1.js +19 -19
  35. package/examples/ssr/solidstart/middleware-v2.js +25 -25
  36. package/examples/ssr/sveltekit/hooks.server.js +16 -16
  37. package/examples/ssr/sveltekit/page.server.js +3 -3
  38. package/examples/ssr/vue/install.mjs +10 -10
  39. package/package.json +35 -9
@@ -1,54 +1,54 @@
1
- import { createEffect, createSignal, onCleanup, Show, type ParentComponent } from 'solid-js';
2
- import { A, Route, Router, useLocation } from '@solidjs/router';
3
- import { checkAccess } from '@stopnetic/js/browser';
4
- import { loadPrivateData } from '../load-private-data.js';
5
-
6
- const GuardedLayout: ParentComponent = props => {
7
- const location = useLocation();
8
- const path = () => location.pathname + location.search;
9
- const [result, setResult] = createSignal({ path: '', allowed: false, pending: true });
10
- createEffect(() => {
11
- const target = path();
12
- const controller = new AbortController();
13
- setResult({ path: target, allowed: false, pending: true });
14
- checkAccess(target, { signal: controller.signal }).then(value => {
15
- if (controller.signal.aborted) return;
16
- if (!value.allowed && value.reason === 'verification_required' && value.verificationUrl) locationRedirect(value.verificationUrl);
17
- setResult({ path: target, allowed: value.allowed, pending: false });
18
- }).catch(() => {
19
- if (!controller.signal.aborted) setResult({ path: target, allowed: false, pending: false });
20
- });
21
- onCleanup(() => controller.abort());
22
- });
23
- return <Show when={result().allowed && result().path === path() ? result() : undefined}
24
- fallback={<Show when={!result().pending && result().path === path()}>
25
- <p role="alert">Access unavailable.</p></Show>}>
26
- {_checked => <>
27
- <header><nav><A href="/">Home</A>{' '}<A href="/members">Members</A>{' '}
28
- <A href="/slow">Slow</A>{' '}<A href="/blocked">Blocked</A>{' '}<A href="/offline">Offline</A>
29
- </nav></header>
30
- {props.children}
31
- <footer>Example site</footer>
32
- </>}
33
- </Show>;
34
- };
35
- const locationRedirect = (url: string) => window.location.assign(url);
36
-
37
- function Members() {
38
- const [data, setData] = createSignal('Loading data...');
39
- const controller = new AbortController();
40
- loadPrivateData(controller.signal)
41
- .then(value => { if (!controller.signal.aborted) setData(JSON.stringify(value) ?? 'No data.'); })
42
- .catch(() => { if (!controller.signal.aborted) setData('API access unavailable.'); });
43
- onCleanup(() => controller.abort());
44
- return <main><h1>Members</h1><p>{data()}</p></main>;
45
- }
46
- export default function App() {
47
- return <Router root={GuardedLayout}>
48
- <Route path="/" component={() => <main><h1>Home</h1></main>}/>
49
- <Route path="/members" component={Members}/>
50
- <Route path="/slow" component={() => <main><h1>Slow</h1></main>}/>
51
- <Route path="/blocked" component={() => <main><h1>Blocked</h1></main>}/>
52
- <Route path="/offline" component={() => <main><h1>Offline</h1></main>}/>
53
- </Router>;
54
- }
1
+ import { createEffect, createSignal, onCleanup, Show, type ParentComponent } from 'solid-js';
2
+ import { A, Route, Router, useLocation } from '@solidjs/router';
3
+ import { checkAccess } from '@stopnetic/js/browser';
4
+ import { loadPrivateData } from '../load-private-data.js';
5
+
6
+ const GuardedLayout: ParentComponent = props => {
7
+ const location = useLocation();
8
+ const path = () => location.pathname + location.search;
9
+ const [result, setResult] = createSignal({ path: '', allowed: false, pending: true });
10
+ createEffect(() => {
11
+ const target = path();
12
+ const controller = new AbortController();
13
+ setResult({ path: target, allowed: false, pending: true });
14
+ checkAccess(target, { signal: controller.signal }).then(value => {
15
+ if (controller.signal.aborted) return;
16
+ if (!value.allowed && value.reason === 'verification_required' && value.verificationUrl) locationRedirect(value.verificationUrl);
17
+ setResult({ path: target, allowed: value.allowed, pending: false });
18
+ }).catch(() => {
19
+ if (!controller.signal.aborted) setResult({ path: target, allowed: false, pending: false });
20
+ });
21
+ onCleanup(() => controller.abort());
22
+ });
23
+ return <Show when={result().allowed && result().path === path() ? result() : undefined}
24
+ fallback={<Show when={!result().pending && result().path === path()}>
25
+ <p role="alert">Access unavailable.</p></Show>}>
26
+ {_checked => <>
27
+ <header><nav><A href="/">Home</A>{' '}<A href="/members">Members</A>{' '}
28
+ <A href="/slow">Slow</A>{' '}<A href="/blocked">Blocked</A>{' '}<A href="/offline">Offline</A>
29
+ </nav></header>
30
+ {props.children}
31
+ <footer>Example site</footer>
32
+ </>}
33
+ </Show>;
34
+ };
35
+ const locationRedirect = (url: string) => window.location.assign(url);
36
+
37
+ function Members() {
38
+ const [data, setData] = createSignal('Loading data...');
39
+ const controller = new AbortController();
40
+ loadPrivateData(controller.signal)
41
+ .then(value => { if (!controller.signal.aborted) setData(JSON.stringify(value) ?? 'No data.'); })
42
+ .catch(() => { if (!controller.signal.aborted) setData('API access unavailable.'); });
43
+ onCleanup(() => controller.abort());
44
+ return <main><h1>Members</h1><p>{data()}</p></main>;
45
+ }
46
+ export default function App() {
47
+ return <Router root={GuardedLayout}>
48
+ <Route path="/" component={() => <main><h1>Home</h1></main>}/>
49
+ <Route path="/members" component={Members}/>
50
+ <Route path="/slow" component={() => <main><h1>Slow</h1></main>}/>
51
+ <Route path="/blocked" component={() => <main><h1>Blocked</h1></main>}/>
52
+ <Route path="/offline" component={() => <main><h1>Offline</h1></main>}/>
53
+ </Router>;
54
+ }
@@ -1,3 +1,3 @@
1
- import { render } from 'solid-js/web';
2
- import App from './App';
3
- render(() => <App/>, document.getElementById('app')!);
1
+ import { render } from 'solid-js/web';
2
+ import App from './App';
3
+ render(() => <App/>, document.getElementById('app')!);
@@ -1,56 +1,56 @@
1
- <script>
2
- import { onMount } from 'svelte';
3
- import { checkAccess } from '@stopnetic/js/browser';
4
- import { loadPrivateData } from '../load-private-data.js';
5
-
6
- let path = '';
7
- let state = 'checking';
8
- let data = '';
9
- let current;
10
- async function showPage() {
11
- current?.abort();
12
- const controller = new AbortController();
13
- current = controller;
14
- path = location.pathname;
15
- state = 'checking'; data = '';
16
- try {
17
- const result = await checkAccess(path + location.search, { signal: controller.signal });
18
- if (controller.signal.aborted) return;
19
- if (!result.allowed) {
20
- if (result.reason === 'verification_required' && result.verificationUrl) location.assign(result.verificationUrl);
21
- state = 'denied'; return;
22
- }
23
- state = 'allowed';
24
- if (path === '/members') {
25
- data = 'Loading data...';
26
- try {
27
- const value = await loadPrivateData(controller.signal);
28
- if (!controller.signal.aborted) data = JSON.stringify(value);
29
- } catch { if (!controller.signal.aborted) data = 'API access unavailable.'; }
30
- }
31
- } catch { if (!controller.signal.aborted) state = 'denied'; }
32
- }
33
- function navigate(event) {
34
- if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
35
- event.preventDefault();
36
- history.pushState(null, '', event.currentTarget.href);
37
- void showPage();
38
- }
39
- onMount(() => {
40
- void showPage();
41
- addEventListener('popstate', showPage);
42
- return () => { current?.abort(); removeEventListener('popstate', showPage); };
43
- });
44
- </script>
45
-
46
- {#if state === 'allowed'}
47
- <header><nav>
48
- <a href="/" onclick={navigate}>Home</a> <a href="/members" onclick={navigate}>Members</a>
49
- <a href="/slow" onclick={navigate}>Slow</a> <a href="/blocked" onclick={navigate}>Blocked</a>
50
- <a href="/offline" onclick={navigate}>Offline</a>
51
- </nav></header>
52
- <main><h1>{path === '/members' ? 'Members' : path === '/' ? 'Home' : path === '/slow' ? 'Slow' : 'Not found'}</h1><p>{data}</p></main>
53
- <footer>Example site</footer>
54
- {:else if state === 'denied'}
55
- <p role="alert">Access unavailable.</p>
56
- {/if}
1
+ <script>
2
+ import { onMount } from 'svelte';
3
+ import { checkAccess } from '@stopnetic/js/browser';
4
+ import { loadPrivateData } from '../load-private-data.js';
5
+
6
+ let path = '';
7
+ let state = 'checking';
8
+ let data = '';
9
+ let current;
10
+ async function showPage() {
11
+ current?.abort();
12
+ const controller = new AbortController();
13
+ current = controller;
14
+ path = location.pathname;
15
+ state = 'checking'; data = '';
16
+ try {
17
+ const result = await checkAccess(path + location.search, { signal: controller.signal });
18
+ if (controller.signal.aborted) return;
19
+ if (!result.allowed) {
20
+ if (result.reason === 'verification_required' && result.verificationUrl) location.assign(result.verificationUrl);
21
+ state = 'denied'; return;
22
+ }
23
+ state = 'allowed';
24
+ if (path === '/members') {
25
+ data = 'Loading data...';
26
+ try {
27
+ const value = await loadPrivateData(controller.signal);
28
+ if (!controller.signal.aborted) data = JSON.stringify(value);
29
+ } catch { if (!controller.signal.aborted) data = 'API access unavailable.'; }
30
+ }
31
+ } catch { if (!controller.signal.aborted) state = 'denied'; }
32
+ }
33
+ function navigate(event) {
34
+ if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
35
+ event.preventDefault();
36
+ history.pushState(null, '', event.currentTarget.href);
37
+ void showPage();
38
+ }
39
+ onMount(() => {
40
+ void showPage();
41
+ addEventListener('popstate', showPage);
42
+ return () => { current?.abort(); removeEventListener('popstate', showPage); };
43
+ });
44
+ </script>
45
+
46
+ {#if state === 'allowed'}
47
+ <header><nav>
48
+ <a href="/" onclick={navigate}>Home</a> <a href="/members" onclick={navigate}>Members</a>
49
+ <a href="/slow" onclick={navigate}>Slow</a> <a href="/blocked" onclick={navigate}>Blocked</a>
50
+ <a href="/offline" onclick={navigate}>Offline</a>
51
+ </nav></header>
52
+ <main><h1>{path === '/members' ? 'Members' : path === '/' ? 'Home' : path === '/slow' ? 'Slow' : 'Not found'}</h1><p>{data}</p></main>
53
+ <footer>Example site</footer>
54
+ {:else if state === 'denied'}
55
+ <p role="alert">Access unavailable.</p>
56
+ {/if}
@@ -1,4 +1,4 @@
1
- // Svelte 5 entry. Svelte 4 apps retain their existing `new App(...)` entry.
2
- import { mount } from 'svelte';
3
- import App from './App.svelte';
4
- mount(App, { target: document.getElementById('app') });
1
+ // Svelte 5 entry. Svelte 4 apps retain their existing `new App(...)` entry.
2
+ import { mount } from 'svelte';
3
+ import App from './App.svelte';
4
+ mount(App, { target: document.getElementById('app') });
@@ -1,46 +1,46 @@
1
- import { checkAccess } from '@stopnetic/js/browser';
2
- import { loadPrivateData } from '../load-private-data.js';
3
- const root = document.getElementById('app');
4
- let current;
5
- async function showPage() {
6
- current?.abort();
7
- const controller = new AbortController(); current = controller;
8
- root.replaceChildren();
9
- try {
10
- const path = location.pathname;
11
- const result = await checkAccess(path + location.search, { signal: controller.signal });
12
- if (controller.signal.aborted) return;
13
- if (!result.allowed) {
14
- if (result.reason === 'verification_required' && result.verificationUrl) location.assign(result.verificationUrl);
15
- const alert = document.createElement('p'); alert.setAttribute('role', 'alert');
16
- alert.textContent = 'Access unavailable.'; root.replaceChildren(alert); return;
17
- }
18
- const header = document.createElement('header');
19
- const nav = document.createElement('nav'); header.append(nav);
20
- for (const [url, name] of [['/', 'Home'], ['/members', 'Members'], ['/slow', 'Slow'], ['/blocked', 'Blocked'], ['/offline', 'Offline']]) {
21
- const link = document.createElement('a'); link.href = url; link.textContent = name;
22
- link.addEventListener('click', event => {
23
- if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
24
- event.preventDefault(); history.pushState(null, '', url); void showPage();
25
- });
26
- nav.append(link, document.createTextNode(' '));
27
- }
28
- const main = document.createElement('main'); const heading = document.createElement('h1');
29
- heading.textContent = path === '/members' ? 'Members' : path === '/' ? 'Home' : path === '/slow' ? 'Slow' : 'Not found';
30
- const data = document.createElement('p'); main.append(heading, data);
31
- const footer = document.createElement('footer'); footer.textContent = 'Example site';
32
- root.replaceChildren(header, main, footer);
33
- if (path === '/members') {
34
- data.textContent = 'Loading data...';
35
- try {
36
- const value = await loadPrivateData(controller.signal);
37
- if (!controller.signal.aborted) data.textContent = JSON.stringify(value);
38
- } catch { if (!controller.signal.aborted) data.textContent = 'API access unavailable.'; }
39
- }
40
- } catch {
41
- if (!controller.signal.aborted) root.replaceChildren(document.createTextNode('Access unavailable.'));
42
- }
43
- }
44
- addEventListener('popstate', showPage);
45
- addEventListener('pagehide', () => current?.abort());
46
- void showPage();
1
+ import { checkAccess } from '@stopnetic/js/browser';
2
+ import { loadPrivateData } from '../load-private-data.js';
3
+ const root = document.getElementById('app');
4
+ let current;
5
+ async function showPage() {
6
+ current?.abort();
7
+ const controller = new AbortController(); current = controller;
8
+ root.replaceChildren();
9
+ try {
10
+ const path = location.pathname;
11
+ const result = await checkAccess(path + location.search, { signal: controller.signal });
12
+ if (controller.signal.aborted) return;
13
+ if (!result.allowed) {
14
+ if (result.reason === 'verification_required' && result.verificationUrl) location.assign(result.verificationUrl);
15
+ const alert = document.createElement('p'); alert.setAttribute('role', 'alert');
16
+ alert.textContent = 'Access unavailable.'; root.replaceChildren(alert); return;
17
+ }
18
+ const header = document.createElement('header');
19
+ const nav = document.createElement('nav'); header.append(nav);
20
+ for (const [url, name] of [['/', 'Home'], ['/members', 'Members'], ['/slow', 'Slow'], ['/blocked', 'Blocked'], ['/offline', 'Offline']]) {
21
+ const link = document.createElement('a'); link.href = url; link.textContent = name;
22
+ link.addEventListener('click', event => {
23
+ if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
24
+ event.preventDefault(); history.pushState(null, '', url); void showPage();
25
+ });
26
+ nav.append(link, document.createTextNode(' '));
27
+ }
28
+ const main = document.createElement('main'); const heading = document.createElement('h1');
29
+ heading.textContent = path === '/members' ? 'Members' : path === '/' ? 'Home' : path === '/slow' ? 'Slow' : 'Not found';
30
+ const data = document.createElement('p'); main.append(heading, data);
31
+ const footer = document.createElement('footer'); footer.textContent = 'Example site';
32
+ root.replaceChildren(header, main, footer);
33
+ if (path === '/members') {
34
+ data.textContent = 'Loading data...';
35
+ try {
36
+ const value = await loadPrivateData(controller.signal);
37
+ if (!controller.signal.aborted) data.textContent = JSON.stringify(value);
38
+ } catch { if (!controller.signal.aborted) data.textContent = 'API access unavailable.'; }
39
+ }
40
+ } catch {
41
+ if (!controller.signal.aborted) root.replaceChildren(document.createTextNode('Access unavailable.'));
42
+ }
43
+ }
44
+ addEventListener('popstate', showPage);
45
+ addEventListener('pagehide', () => current?.abort());
46
+ void showPage();
@@ -1,17 +1,17 @@
1
- <script setup>
2
- import { RouterLink, RouterView } from 'vue-router';
3
- import { accessState } from './router.js';
4
- </script>
5
-
6
- <template>
7
- <template v-if="accessState === 'allowed'">
8
- <header><nav>
9
- <RouterLink to="/">Home</RouterLink> <RouterLink to="/members">Members</RouterLink>
10
- <RouterLink to="/slow">Slow</RouterLink> <RouterLink to="/blocked">Blocked</RouterLink>
11
- <RouterLink to="/offline">Offline</RouterLink>
12
- </nav></header>
13
- <RouterView/>
14
- <footer>Example site</footer>
15
- </template>
16
- <p v-else-if="accessState === 'denied'" role="alert">Access unavailable.</p>
17
- </template>
1
+ <script setup>
2
+ import { RouterLink, RouterView } from 'vue-router';
3
+ import { accessState } from './router.js';
4
+ </script>
5
+
6
+ <template>
7
+ <template v-if="accessState === 'allowed'">
8
+ <header><nav>
9
+ <RouterLink to="/">Home</RouterLink> <RouterLink to="/members">Members</RouterLink>
10
+ <RouterLink to="/slow">Slow</RouterLink> <RouterLink to="/blocked">Blocked</RouterLink>
11
+ <RouterLink to="/offline">Offline</RouterLink>
12
+ </nav></header>
13
+ <RouterView/>
14
+ <footer>Example site</footer>
15
+ </template>
16
+ <p v-else-if="accessState === 'denied'" role="alert">Access unavailable.</p>
17
+ </template>
@@ -1,14 +1,14 @@
1
- <script setup>
2
- import { onMounted, onUnmounted, ref } from 'vue';
3
- import { loadPrivateData } from '../load-private-data.js';
4
- const data = ref('Loading data...');
5
- const controller = new AbortController();
6
- onMounted(() => {
7
- loadPrivateData(controller.signal)
8
- .then(value => { if (!controller.signal.aborted) data.value = JSON.stringify(value); })
9
- .catch(() => { if (!controller.signal.aborted) data.value = 'API access unavailable.'; });
10
- });
11
- onUnmounted(() => controller.abort());
12
- </script>
13
-
14
- <template><main><h1>Members</h1><p>{{ data }}</p></main></template>
1
+ <script setup>
2
+ import { onMounted, onUnmounted, ref } from 'vue';
3
+ import { loadPrivateData } from '../load-private-data.js';
4
+ const data = ref('Loading data...');
5
+ const controller = new AbortController();
6
+ onMounted(() => {
7
+ loadPrivateData(controller.signal)
8
+ .then(value => { if (!controller.signal.aborted) data.value = JSON.stringify(value); })
9
+ .catch(() => { if (!controller.signal.aborted) data.value = 'API access unavailable.'; });
10
+ });
11
+ onUnmounted(() => controller.abort());
12
+ </script>
13
+
14
+ <template><main><h1>Members</h1><p>{{ data }}</p></main></template>
@@ -1,4 +1,4 @@
1
- import { createApp } from 'vue';
2
- import App from './App.vue';
3
- import { router } from './router.js';
4
- createApp(App).use(router).mount('#app');
1
+ import { createApp } from 'vue';
2
+ import App from './App.vue';
3
+ import { router } from './router.js';
4
+ createApp(App).use(router).mount('#app');
@@ -1,40 +1,40 @@
1
- import { h, ref } from 'vue';
2
- import { createRouter, createWebHistory } from 'vue-router';
3
- import { checkAccess } from '@stopnetic/js/browser';
4
- import Members from './Members.vue';
5
-
6
- export const accessState = ref('checking');
7
- const page = title => ({ render: () => h('main', h('h1', title)) });
8
- export const router = createRouter({
9
- history: createWebHistory(),
10
- routes: [
11
- { path: '/', component: page('Home') },
12
- { path: '/members', component: Members },
13
- { path: '/slow', component: page('Slow') },
14
- { path: '/blocked', component: page('Blocked') },
15
- { path: '/offline', component: page('Offline') },
16
- ],
17
- });
18
-
19
- let current;
20
- let approvedPath = '';
21
- router.beforeEach(async to => {
22
- current?.abort();
23
- const controller = new AbortController();
24
- current = controller;
25
- approvedPath = '';
26
- accessState.value = 'checking';
27
- try {
28
- const result = await checkAccess(to.fullPath.split('#')[0], { signal: controller.signal });
29
- if (controller.signal.aborted) return false;
30
- if (result.allowed) { approvedPath = to.fullPath; return true; }
31
- if (result.reason === 'verification_required' && result.verificationUrl) {
32
- location.assign(result.verificationUrl);
33
- }
34
- accessState.value = 'denied';
35
- } catch { if (!controller.signal.aborted) accessState.value = 'denied'; }
36
- return false;
37
- });
38
- router.afterEach((to, _from, failure) => {
39
- if (!failure && approvedPath === to.fullPath) accessState.value = 'allowed';
40
- });
1
+ import { h, ref } from 'vue';
2
+ import { createRouter, createWebHistory } from 'vue-router';
3
+ import { checkAccess } from '@stopnetic/js/browser';
4
+ import Members from './Members.vue';
5
+
6
+ export const accessState = ref('checking');
7
+ const page = title => ({ render: () => h('main', h('h1', title)) });
8
+ export const router = createRouter({
9
+ history: createWebHistory(),
10
+ routes: [
11
+ { path: '/', component: page('Home') },
12
+ { path: '/members', component: Members },
13
+ { path: '/slow', component: page('Slow') },
14
+ { path: '/blocked', component: page('Blocked') },
15
+ { path: '/offline', component: page('Offline') },
16
+ ],
17
+ });
18
+
19
+ let current;
20
+ let approvedPath = '';
21
+ router.beforeEach(async to => {
22
+ current?.abort();
23
+ const controller = new AbortController();
24
+ current = controller;
25
+ approvedPath = '';
26
+ accessState.value = 'checking';
27
+ try {
28
+ const result = await checkAccess(to.fullPath.split('#')[0], { signal: controller.signal });
29
+ if (controller.signal.aborted) return false;
30
+ if (result.allowed) { approvedPath = to.fullPath; return true; }
31
+ if (result.reason === 'verification_required' && result.verificationUrl) {
32
+ location.assign(result.verificationUrl);
33
+ }
34
+ accessState.value = 'denied';
35
+ } catch { if (!controller.signal.aborted) accessState.value = 'denied'; }
36
+ return false;
37
+ });
38
+ router.afterEach((to, _from, failure) => {
39
+ if (!failure && approvedPath === to.fullPath) accessState.value = 'allowed';
40
+ });
@@ -1,5 +1,5 @@
1
- import { RenderMode, ServerRoute } from '@angular/ssr';
2
- export const serverRoutes: ServerRoute[] = [
3
- // Demo only: merge with existing hybrid modes rather than replacing them.
4
- { path: '**', renderMode: RenderMode.Server },
5
- ];
1
+ import { RenderMode, ServerRoute } from '@angular/ssr';
2
+ export const serverRoutes: ServerRoute[] = [
3
+ // Demo only: merge with existing hybrid modes rather than replacing them.
4
+ { path: '**', renderMode: RenderMode.Server },
5
+ ];
@@ -1,12 +1,12 @@
1
- // Merge into your existing Node SSR entry; retain host settings and context.
2
- import { AngularAppEngine, createRequestHandler } from '@angular/ssr';
3
- import { createStopneticAppHandler } from '@stopnetic/js/server';
4
-
5
- const engine = new AngularAppEngine();
6
- let guarded: ReturnType<typeof createStopneticAppHandler> | undefined;
7
- async function handle(request: Request): Promise<Response> {
8
- guarded ??= createStopneticAppHandler(async checked =>
9
- await engine.handle(checked) ?? new Response(null, { status: 404 }));
10
- return guarded(request);
11
- }
12
- export const reqHandler = createRequestHandler(handle);
1
+ // Merge into your existing Node SSR entry; retain host settings and context.
2
+ import { AngularAppEngine, createRequestHandler } from '@angular/ssr';
3
+ import { createStopneticAppHandler } from '@stopnetic/js/server';
4
+
5
+ const engine = new AngularAppEngine();
6
+ let guarded: ReturnType<typeof createStopneticAppHandler> | undefined;
7
+ async function handle(request: Request): Promise<Response> {
8
+ guarded ??= createStopneticAppHandler(async checked =>
9
+ await engine.handle(checked) ?? new Response(null, { status: 404 }));
10
+ return guarded(request);
11
+ }
12
+ export const reqHandler = createRequestHandler(handle);
@@ -1,11 +1,11 @@
1
- // src/middleware.js. Keep the existing Node deployment adapter.
2
- import { defineMiddleware } from 'astro:middleware';
3
- import { createStopneticAppHandler } from '@stopnetic/js/server';
4
-
5
- let guarded;
6
- export const onRequest = defineMiddleware(async (context, next) => {
7
- // Public prerender builds only; protected pages must export prerender=false.
8
- if (context.isPrerendered) return next();
9
- guarded ??= createStopneticAppHandler((_request, run) => run());
10
- return guarded(context.request, () => next());
11
- });
1
+ // src/middleware.js. Keep the existing Node deployment adapter.
2
+ import { defineMiddleware } from 'astro:middleware';
3
+ import { createStopneticAppHandler } from '@stopnetic/js/server';
4
+
5
+ let guarded;
6
+ export const onRequest = defineMiddleware(async (context, next) => {
7
+ // Public prerender builds only; protected pages must export prerender=false.
8
+ if (context.isPrerendered) return next();
9
+ guarded ??= createStopneticAppHandler((_request, run) => run());
10
+ return guarded(context.request, () => next());
11
+ });
@@ -1,4 +1,4 @@
1
- ---
2
- // Merge this flag into a protected page, preserving its existing content.
3
- export const prerender = false;
4
- ---
1
+ ---
2
+ // Merge this flag into a protected page, preserving its existing content.
3
+ export const prerender = false;
4
+ ---
@@ -1,9 +1,9 @@
1
- // Wrap your existing Node Request -> Response renderer; retain all context.
2
- import { createStopneticAppHandler } from '@stopnetic/js/server';
3
- export function protectSSR(renderApplication) {
4
- let guarded;
5
- return (request, ...context) => {
6
- guarded ??= createStopneticAppHandler(renderApplication);
7
- return guarded(request, ...context);
8
- };
9
- }
1
+ // Wrap your existing Node Request -> Response renderer; retain all context.
2
+ import { createStopneticAppHandler } from '@stopnetic/js/server';
3
+ export function protectSSR(renderApplication) {
4
+ let guarded;
5
+ return (request, ...context) => {
6
+ guarded ??= createStopneticAppHandler(renderApplication);
7
+ return guarded(request, ...context);
8
+ };
9
+ }
@@ -1,9 +1,9 @@
1
- // Call before parsers, static files, APIs and your existing Node SSR renderer.
2
- import { createStopneticServer } from '@stopnetic/js/server';
3
- export function installStopneticSSR(app) {
4
- let server;
5
- app.use((req, res, next) => {
6
- server ??= createStopneticServer();
7
- return server.access(req, res, () => server.guard(req, res, next));
8
- });
9
- }
1
+ // Call before parsers, static files, APIs and your existing Node SSR renderer.
2
+ import { createStopneticServer } from '@stopnetic/js/server';
3
+ export function installStopneticSSR(app) {
4
+ let server;
5
+ app.use((req, res, next) => {
6
+ server ??= createStopneticServer();
7
+ return server.access(req, res, () => server.guard(req, res, next));
8
+ });
9
+ }
@@ -1,10 +1,10 @@
1
- // server/middleware/00-stopnetic.mjs. Nuxt 3/4, Nitro 2, H3 1, NODE only.
2
- // Use server middleware, not app/middleware or defineNuxtRouteMiddleware.
3
- import { fromNodeMiddleware } from 'h3';
4
- import { createStopneticServer } from '@stopnetic/js/server';
5
-
6
- let server;
7
- export default fromNodeMiddleware((req, res, next) => {
8
- server ??= createStopneticServer();
9
- return server.access(req, res, () => server.guard(req, res, next));
10
- });
1
+ // server/middleware/00-stopnetic.mjs. Nuxt 3/4, Nitro 2, H3 1, NODE only.
2
+ // Use server middleware, not app/middleware or defineNuxtRouteMiddleware.
3
+ import { fromNodeMiddleware } from 'h3';
4
+ import { createStopneticServer } from '@stopnetic/js/server';
5
+
6
+ let server;
7
+ export default fromNodeMiddleware((req, res, next) => {
8
+ server ??= createStopneticServer();
9
+ return server.access(req, res, () => server.guard(req, res, next));
10
+ });