@calimero-network/agent-skills 0.2.0 → 0.4.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/README.md +137 -17
- package/SKILL.md +31 -23
- package/package.json +2 -2
- package/scripts/install.js +3 -3
- package/scripts/test.js +6 -15
- package/skills/calimero-abi-codegen/SKILL.md +121 -22
- package/skills/calimero-abi-codegen/references/abi-format.md +3 -5
- package/skills/calimero-abi-codegen/references/generated-output.md +12 -4
- package/skills/calimero-abi-codegen/rules/schema-version.md +11 -4
- package/skills/calimero-abi-codegen/rules/unique-names.md +2 -6
- package/skills/calimero-client-js/SKILL.md +127 -22
- package/skills/calimero-client-js/references/auth.md +18 -10
- package/skills/calimero-client-js/references/rpc-calls.md +15 -21
- package/skills/calimero-client-js/references/sso.md +9 -9
- package/skills/calimero-client-js/references/websocket-events.md +73 -59
- package/skills/calimero-client-js/rules/camelcase-api.md +10 -7
- package/skills/calimero-client-js/rules/token-refresh.md +59 -21
- package/skills/calimero-client-py/SKILL.md +26 -10
- package/skills/calimero-client-py/references/api.md +41 -43
- package/skills/calimero-client-py/references/auth.md +7 -7
- package/skills/calimero-client-py/rules/async-usage.md +27 -31
- package/skills/calimero-client-py/rules/stable-node-name.md +7 -7
- package/skills/calimero-core/SKILL.md +135 -0
- package/skills/calimero-core/references/architecture.md +101 -0
- package/skills/calimero-core/references/jsonrpc-protocol.md +192 -0
- package/skills/calimero-core/references/namespaces-groups.md +94 -0
- package/skills/calimero-core/references/storage-types.md +118 -0
- package/skills/calimero-core/references/websocket-events.md +142 -0
- package/skills/calimero-core/rules/context-is-not-app.md +35 -0
- package/skills/calimero-core/rules/crdt-types-only.md +55 -0
- package/skills/calimero-desktop/SKILL.md +25 -14
- package/skills/calimero-desktop/references/sso-integration.md +49 -22
- package/skills/calimero-desktop/rules/sso-fallback.md +3 -2
- package/skills/calimero-merobox/SKILL.md +255 -28
- package/skills/calimero-merobox/references/ci-integration.md +3 -2
- package/skills/calimero-merobox/references/workflow-files.md +7 -5
- package/skills/calimero-merobox/rules/docker-required.md +7 -6
- package/skills/calimero-meroctl/SKILL.md +68 -0
- package/skills/calimero-meroctl/references/commands.md +177 -0
- package/skills/calimero-meroctl/references/scripting.md +80 -0
- package/skills/calimero-meroctl/rules/call-view-flag.md +28 -0
- package/skills/calimero-meroctl/rules/register-node-once.md +34 -0
- package/skills/calimero-merod/SKILL.md +49 -0
- package/skills/calimero-merod/references/health-endpoints.md +90 -0
- package/skills/calimero-merod/references/init-flags.md +84 -0
- package/skills/calimero-merod/rules/init-before-run.md +40 -0
- package/skills/calimero-merod/rules/port-assignments.md +33 -0
- package/skills/calimero-node/SKILL.md +52 -35
- package/skills/calimero-node/references/context-lifecycle.md +34 -17
- package/skills/calimero-node/references/meroctl-commands.md +89 -99
- package/skills/calimero-node/rules/app-vs-context.md +4 -4
- package/skills/calimero-registry/SKILL.md +110 -31
- package/skills/calimero-registry/references/bundle-and-push.md +99 -34
- package/skills/calimero-registry/references/manifest-format.md +56 -35
- package/skills/calimero-registry/references/mero-sign.md +10 -9
- package/skills/calimero-registry/rules/key-security.md +3 -2
- package/skills/calimero-registry/rules/sign-before-pack.md +5 -5
- package/skills/calimero-rust-sdk/SKILL.md +154 -44
- package/skills/calimero-rust-sdk/references/blob-api.md +119 -0
- package/skills/calimero-rust-sdk/references/event-handlers.md +122 -0
- package/skills/calimero-rust-sdk/references/events.md +2 -1
- package/skills/calimero-rust-sdk/references/examples.md +81 -29
- package/skills/calimero-rust-sdk/references/migrations.md +123 -0
- package/skills/calimero-rust-sdk/references/nested-crdts.md +113 -0
- package/skills/calimero-rust-sdk/references/private-storage.md +76 -34
- package/skills/calimero-rust-sdk/references/state-collections.md +106 -21
- package/skills/calimero-rust-sdk/references/user-and-frozen-storage.md +169 -0
- package/skills/calimero-rust-sdk/rules/app-macro-placement.md +5 -2
- package/skills/calimero-rust-sdk/rules/no-std-collections.md +5 -2
- package/skills/calimero-rust-sdk/rules/state-derives.md +45 -0
- package/skills/calimero-rust-sdk/rules/wasm-constraints.md +12 -10
- package/skills/calimero-sdk-js/SKILL.md +145 -0
- package/skills/calimero-sdk-js/references/build-pipeline.md +98 -0
- package/skills/calimero-sdk-js/references/collections.md +132 -0
- package/skills/calimero-sdk-js/references/events.md +63 -0
- package/skills/calimero-sdk-js/rules/crdt-only-state.md +47 -0
- package/skills/calimero-sdk-js/rules/no-console-log.md +38 -0
- package/skills/calimero-sdk-js/rules/view-decorator.md +48 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# WebSocket Events
|
|
2
|
+
|
|
3
|
+
The Calimero node pushes real-time events to subscribers over WebSocket.
|
|
4
|
+
|
|
5
|
+
## Connect and subscribe
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
ws://localhost:2428/ws
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
After connecting, send:
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{ "action": "subscribe", "contextIds": ["<context-id-1>", "<context-id-2>"] }
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
You can subscribe to multiple context IDs in one message.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Event types
|
|
22
|
+
|
|
23
|
+
| Type | When fired | Payload |
|
|
24
|
+
| ---------------- | ----------------------------------------------------- | ------------------------------ |
|
|
25
|
+
| `ExecutionEvent` | App called `app::emit!()` (Rust) or `env.emit()` (JS) | `{ events: ExecutionEvent[] }` |
|
|
26
|
+
| `StateMutation` | Any context member mutated shared state | `{ newRoot: string }` |
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## ExecutionEvent schema
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
interface WsMessage {
|
|
34
|
+
contextId: string;
|
|
35
|
+
type: 'ExecutionEvent';
|
|
36
|
+
data: {
|
|
37
|
+
events: Array<{
|
|
38
|
+
kind: string; // matches Rust enum variant name or JS event name
|
|
39
|
+
data: number[] | object; // byte array (UTF-8 JSON) or plain object
|
|
40
|
+
}>;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`data` is typically a UTF-8 JSON byte array when coming from a Rust app. Decode it:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
function decodeEventData(data: unknown): unknown {
|
|
49
|
+
if (Array.isArray(data) && data.every((n) => typeof n === 'number')) {
|
|
50
|
+
try {
|
|
51
|
+
return JSON.parse(new TextDecoder().decode(new Uint8Array(data as number[])));
|
|
52
|
+
} catch {
|
|
53
|
+
return data; // leave as-is if not valid JSON
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return data;
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Full consumption pattern:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
ws.onmessage = (msg) => {
|
|
64
|
+
const envelope = JSON.parse(msg.data);
|
|
65
|
+
if (envelope.type === 'ExecutionEvent') {
|
|
66
|
+
for (const e of envelope.data.events) {
|
|
67
|
+
const payload = decodeEventData(e.data);
|
|
68
|
+
switch (e.kind) {
|
|
69
|
+
case 'PostCreated':
|
|
70
|
+
handlePostCreated(payload);
|
|
71
|
+
break;
|
|
72
|
+
case 'PostDeleted':
|
|
73
|
+
handlePostDeleted(payload);
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (envelope.type === 'StateMutation') {
|
|
79
|
+
// A peer mutated shared state — refetch data if needed
|
|
80
|
+
refreshData();
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## StateMutation schema
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
interface WsMessage {
|
|
91
|
+
contextId: string;
|
|
92
|
+
type: 'StateMutation';
|
|
93
|
+
data: {
|
|
94
|
+
newRoot: string; // hex hash of the new CRDT state root after merge
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Use `StateMutation` to know when to refetch state from the node — it fires on every mutation from
|
|
100
|
+
any member, including the local node's own mutations.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Unsubscribe
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{ "action": "unsubscribe", "contextIds": ["<context-id>"] }
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Emitting events from Rust
|
|
113
|
+
|
|
114
|
+
```rust
|
|
115
|
+
#[app::event]
|
|
116
|
+
pub enum Event<'a> {
|
|
117
|
+
PostCreated { title: &'a str },
|
|
118
|
+
PostDeleted { id: u32 },
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
#[app::logic]
|
|
122
|
+
impl AppState {
|
|
123
|
+
pub fn create_post(&mut self, title: String) -> app::Result<()> {
|
|
124
|
+
// ...
|
|
125
|
+
app::emit!(Event::PostCreated { title: &title });
|
|
126
|
+
Ok(())
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
The `kind` field in the WebSocket message will be `"PostCreated"` or `"PostDeleted"`.
|
|
132
|
+
|
|
133
|
+
## Emitting events from TypeScript (SDK JS)
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
import * as env from '@calimero-network/calimero-sdk-js/env';
|
|
137
|
+
|
|
138
|
+
set(key: string, value: string): void {
|
|
139
|
+
this.items.set(key, value);
|
|
140
|
+
env.emit({ kind: 'ItemSet', data: { key, value } });
|
|
141
|
+
}
|
|
142
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Rule: Application ≠ Context
|
|
2
|
+
|
|
3
|
+
**An application is not a context. Installing an app does not create a context.**
|
|
4
|
+
|
|
5
|
+
## What this means
|
|
6
|
+
|
|
7
|
+
- `meroctl app install` → registers the WASM binary. Returns an `application-id`. No state exists
|
|
8
|
+
yet. No context exists yet.
|
|
9
|
+
- `meroctl context create --application-id <id>` → creates a context. Calls `init()`. State now
|
|
10
|
+
exists. The context has a `context-id`.
|
|
11
|
+
|
|
12
|
+
These are always **two separate commands**. You cannot skip the first step and jump straight to
|
|
13
|
+
calling methods.
|
|
14
|
+
|
|
15
|
+
## Why this matters
|
|
16
|
+
|
|
17
|
+
When a developer says "install and run my app", the correct sequence is:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
meroctl app install --path app.wasm # → application-id
|
|
21
|
+
meroctl context create --application-id <id> # → context-id
|
|
22
|
+
meroctl call <context-id> my_method --args '{}'
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Not:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
meroctl app install --path app.wasm
|
|
29
|
+
meroctl call ??? # WRONG — no context yet
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Analogy
|
|
33
|
+
|
|
34
|
+
Think of an Application as a class definition and a Context as an instance of that class. You can
|
|
35
|
+
have many contexts (instances) from one application (class), each with completely isolated state.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Rule: CRDT Types Only for Shared State
|
|
2
|
+
|
|
3
|
+
**Never use `std::collections` (Rust) or plain `Map`/`Array` (JS/TS) for shared application state.**
|
|
4
|
+
|
|
5
|
+
## Forbidden for state fields
|
|
6
|
+
|
|
7
|
+
```rust
|
|
8
|
+
// WRONG — will compile but cause data loss on concurrent writes:
|
|
9
|
+
struct AppState {
|
|
10
|
+
items: std::collections::HashMap<String, String>, // ❌
|
|
11
|
+
log: Vec<String>, // ❌
|
|
12
|
+
seen: std::collections::HashSet<String>, // ❌
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
// WRONG — same problem in TypeScript:
|
|
18
|
+
@State
|
|
19
|
+
class AppState {
|
|
20
|
+
items: Map<string, string> = new Map(); // ❌
|
|
21
|
+
log: string[] = []; // ❌
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Required: use CRDT collections
|
|
26
|
+
|
|
27
|
+
```rust
|
|
28
|
+
// CORRECT:
|
|
29
|
+
use calimero_storage::collections::{UnorderedMap, Vector, LwwRegister};
|
|
30
|
+
|
|
31
|
+
struct AppState {
|
|
32
|
+
items: UnorderedMap<String, LwwRegister<String>>, // ✓
|
|
33
|
+
log: Vector<String>, // ✓
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// CORRECT:
|
|
39
|
+
import { UnorderedMap, Vector } from '@calimero-network/calimero-sdk-js/collections';
|
|
40
|
+
|
|
41
|
+
@State
|
|
42
|
+
class AppState {
|
|
43
|
+
items: UnorderedMap<string, string> = new UnorderedMap(); // ✓
|
|
44
|
+
log: Vector<string> = new Vector(); // ✓
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Why
|
|
49
|
+
|
|
50
|
+
Standard collections have no merge semantics. When two context members both write to a `HashMap`
|
|
51
|
+
while offline, merging their states is undefined — the CRDT engine cannot resolve conflicts
|
|
52
|
+
automatically and data will be silently lost or overwritten.
|
|
53
|
+
|
|
54
|
+
CRDT collections are designed for exactly this scenario: they guarantee that all members converge to
|
|
55
|
+
the same state regardless of write order or network partitions.
|
|
@@ -1,24 +1,30 @@
|
|
|
1
1
|
# calimero-desktop — Agent Instructions
|
|
2
2
|
|
|
3
|
-
You are helping a developer integrate their app frontend with **Calimero Desktop SSO
|
|
3
|
+
You are helping a developer integrate their app frontend with **Calimero Desktop SSO** — the flow
|
|
4
|
+
where users open an app from the Desktop and are automatically logged in without a manual auth
|
|
5
|
+
screen.
|
|
6
|
+
|
|
7
|
+
> **NOT this skill** if the developer just needs to authenticate manually via the login form — that
|
|
8
|
+
> is handled entirely by `calimero-client-js`. This skill is specifically for reading SSO tokens
|
|
9
|
+
> that Calimero Desktop passes in the URL hash.
|
|
4
10
|
|
|
5
11
|
## What Desktop does
|
|
6
12
|
|
|
7
|
-
Calimero Desktop opens app frontends in a browser window and passes auth tokens via the
|
|
8
|
-
|
|
13
|
+
Calimero Desktop opens app frontends in a browser window and passes auth tokens via the URL hash —
|
|
14
|
+
so users are automatically logged in without going through the manual auth flow.
|
|
9
15
|
|
|
10
16
|
## Hash parameters passed by Desktop
|
|
11
17
|
|
|
12
|
-
| Parameter
|
|
13
|
-
|
|
|
14
|
-
| `access_token`
|
|
15
|
-
| `refresh_token`
|
|
16
|
-
| `node_url`
|
|
17
|
-
| `application_id` | string | The installed app's ID on this node
|
|
18
|
+
| Parameter | Type | Description |
|
|
19
|
+
| ---------------- | ------ | ----------------------------------------------- |
|
|
20
|
+
| `access_token` | string | JWT for authenticated node calls |
|
|
21
|
+
| `refresh_token` | string | Token to obtain a new access token |
|
|
22
|
+
| `node_url` | string | URL of the local node (`http://localhost:PORT`) |
|
|
23
|
+
| `application_id` | string | The installed app's ID on this node |
|
|
18
24
|
|
|
19
25
|
## Example URL
|
|
20
26
|
|
|
21
|
-
```
|
|
27
|
+
```text
|
|
22
28
|
https://your-app.com/#access_token=eyJ...&refresh_token=eyJ...&node_url=http://localhost:2428&application_id=abc123
|
|
23
29
|
```
|
|
24
30
|
|
|
@@ -38,10 +44,15 @@ if (accessToken && nodeUrl) {
|
|
|
38
44
|
|
|
39
45
|
## Critical rule
|
|
40
46
|
|
|
41
|
-
Always fall back to manual login when hash params are absent. The app must work when
|
|
42
|
-
|
|
47
|
+
Always fall back to manual login when hash params are absent. The app must work when opened in a
|
|
48
|
+
regular browser, not only from Desktop.
|
|
49
|
+
|
|
50
|
+
## Related skills
|
|
51
|
+
|
|
52
|
+
- **`calimero-client-js`** — mero-js / mero-react API for auth, RPC, and WebSocket subscriptions
|
|
53
|
+
- **`calimero-core`** — auth flow (JWT tokens, login, refresh) and JSON-RPC protocol detail
|
|
43
54
|
|
|
44
55
|
## References
|
|
45
56
|
|
|
46
|
-
See `references/` for full integration pattern and how Desktop discovers app URLs.
|
|
47
|
-
|
|
57
|
+
See `references/` for full integration pattern and how Desktop discovers app URLs. See `rules/` for
|
|
58
|
+
the fallback requirement.
|
|
@@ -2,28 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
## Full startup flow
|
|
4
4
|
|
|
5
|
+
Use the storage helpers from `@calimero-network/calimero-client` — do **not** write to
|
|
6
|
+
`localStorage` directly. The helpers ensure correct key names and extract contextId +
|
|
7
|
+
executorPublicKey from the JWT automatically.
|
|
8
|
+
|
|
5
9
|
```typescript
|
|
10
|
+
import {
|
|
11
|
+
setAppEndpointKey,
|
|
12
|
+
setAccessToken,
|
|
13
|
+
setRefreshToken,
|
|
14
|
+
setApplicationId,
|
|
15
|
+
setContextAndIdentityFromJWT,
|
|
16
|
+
getAuthConfig,
|
|
17
|
+
} from '@calimero-network/calimero-client';
|
|
18
|
+
|
|
6
19
|
interface SSOParams {
|
|
7
20
|
accessToken: string;
|
|
8
|
-
refreshToken: string;
|
|
21
|
+
refreshToken: string | null;
|
|
9
22
|
nodeUrl: string;
|
|
10
|
-
applicationId: string;
|
|
23
|
+
applicationId: string | null;
|
|
11
24
|
}
|
|
12
25
|
|
|
13
26
|
function readDesktopSSO(): SSOParams | null {
|
|
14
27
|
const hash = new URLSearchParams(window.location.hash.slice(1));
|
|
15
28
|
const accessToken = hash.get('access_token');
|
|
16
|
-
const refreshToken = hash.get('refresh_token');
|
|
17
29
|
const nodeUrl = hash.get('node_url');
|
|
18
|
-
const applicationId = hash.get('application_id');
|
|
19
30
|
|
|
20
31
|
if (!accessToken || !nodeUrl) return null;
|
|
21
32
|
|
|
22
33
|
return {
|
|
23
34
|
accessToken,
|
|
24
|
-
refreshToken:
|
|
35
|
+
refreshToken: hash.get('refresh_token'),
|
|
25
36
|
nodeUrl,
|
|
26
|
-
applicationId:
|
|
37
|
+
applicationId: hash.get('application_id'),
|
|
27
38
|
};
|
|
28
39
|
}
|
|
29
40
|
|
|
@@ -31,21 +42,29 @@ async function bootstrap() {
|
|
|
31
42
|
const sso = readDesktopSSO();
|
|
32
43
|
|
|
33
44
|
if (sso) {
|
|
45
|
+
// Store tokens via SDK helpers (sets localStorage keys correctly)
|
|
46
|
+
setAppEndpointKey(sso.nodeUrl);
|
|
47
|
+
setAccessToken(sso.accessToken);
|
|
48
|
+
if (sso.refreshToken) setRefreshToken(sso.refreshToken);
|
|
49
|
+
if (sso.applicationId) setApplicationId(sso.applicationId);
|
|
50
|
+
// Extracts contextId + executorPublicKey from JWT claims
|
|
51
|
+
setContextAndIdentityFromJWT(sso.accessToken);
|
|
52
|
+
|
|
34
53
|
// Clear hash from URL bar (tokens shouldn't sit in browser history)
|
|
35
54
|
history.replaceState(null, '', window.location.pathname + window.location.search);
|
|
36
55
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
} else {
|
|
47
|
-
renderLoginScreen();
|
|
56
|
+
renderAuthenticatedApp();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// No SSO hash — check if already authenticated from a prior session
|
|
61
|
+
const config = getAuthConfig();
|
|
62
|
+
if (config.error === null) {
|
|
63
|
+
renderAuthenticatedApp();
|
|
64
|
+
return;
|
|
48
65
|
}
|
|
66
|
+
|
|
67
|
+
renderLoginScreen();
|
|
49
68
|
}
|
|
50
69
|
|
|
51
70
|
document.addEventListener('DOMContentLoaded', bootstrap);
|
|
@@ -53,7 +72,8 @@ document.addEventListener('DOMContentLoaded', bootstrap);
|
|
|
53
72
|
|
|
54
73
|
## How Desktop discovers your app's frontend URL
|
|
55
74
|
|
|
56
|
-
Desktop reads the `links.frontend` field from your app's `manifest.json` inside the installed
|
|
75
|
+
Desktop reads the `links.frontend` field from your app's `manifest.json` inside the installed
|
|
76
|
+
bundle. Set this field so Desktop can open your app:
|
|
57
77
|
|
|
58
78
|
```json
|
|
59
79
|
{
|
|
@@ -70,18 +90,25 @@ Desktop opens this URL and appends the SSO hash params.
|
|
|
70
90
|
|
|
71
91
|
```typescript
|
|
72
92
|
// App.tsx
|
|
93
|
+
import { getAuthConfig, setAccessToken, setAppEndpointKey,
|
|
94
|
+
setRefreshToken, setApplicationId, setContextAndIdentityFromJWT } from '@calimero-network/calimero-client';
|
|
95
|
+
|
|
73
96
|
function App() {
|
|
74
97
|
const [authState, setAuthState] = useState<'loading' | 'authenticated' | 'unauthenticated'>('loading');
|
|
75
98
|
|
|
76
99
|
useEffect(() => {
|
|
77
100
|
const sso = readDesktopSSO();
|
|
78
101
|
if (sso) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
102
|
+
setAppEndpointKey(sso.nodeUrl);
|
|
103
|
+
setAccessToken(sso.accessToken);
|
|
104
|
+
if (sso.refreshToken) setRefreshToken(sso.refreshToken);
|
|
105
|
+
if (sso.applicationId) setApplicationId(sso.applicationId);
|
|
106
|
+
setContextAndIdentityFromJWT(sso.accessToken);
|
|
107
|
+
history.replaceState(null, '', window.location.pathname);
|
|
82
108
|
setAuthState('authenticated');
|
|
83
109
|
} else {
|
|
84
|
-
|
|
110
|
+
const config = getAuthConfig();
|
|
111
|
+
setAuthState(config.error === null ? 'authenticated' : 'unauthenticated');
|
|
85
112
|
}
|
|
86
113
|
}, []);
|
|
87
114
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Rule: Always fall back to manual login if hash params are absent
|
|
2
2
|
|
|
3
3
|
Your app must work in two scenarios:
|
|
4
|
+
|
|
4
5
|
1. Opened from Calimero Desktop — SSO hash params present
|
|
5
6
|
2. Opened directly in a browser — no hash params
|
|
6
7
|
|
|
@@ -29,5 +30,5 @@ if (token) {
|
|
|
29
30
|
|
|
30
31
|
## Why
|
|
31
32
|
|
|
32
|
-
Desktop is not the only way users access apps. Developers test in browsers directly.
|
|
33
|
-
|
|
33
|
+
Desktop is not the only way users access apps. Developers test in browsers directly. Organizations
|
|
34
|
+
may embed apps in other contexts. The app must be self-sufficient.
|