@rool-dev/sdk 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.
- package/README.md +1070 -0
- package/dist/apps.d.ts +29 -0
- package/dist/apps.d.ts.map +1 -0
- package/dist/apps.js +88 -0
- package/dist/apps.js.map +1 -0
- package/dist/auth-browser.d.ts +80 -0
- package/dist/auth-browser.d.ts.map +1 -0
- package/dist/auth-browser.js +370 -0
- package/dist/auth-browser.js.map +1 -0
- package/dist/auth-node.d.ts +46 -0
- package/dist/auth-node.d.ts.map +1 -0
- package/dist/auth-node.js +316 -0
- package/dist/auth-node.js.map +1 -0
- package/dist/auth.d.ts +56 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +96 -0
- package/dist/auth.js.map +1 -0
- package/dist/client.d.ts +202 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +472 -0
- package/dist/client.js.map +1 -0
- package/dist/event-emitter.d.ts +38 -0
- package/dist/event-emitter.d.ts.map +1 -0
- package/dist/event-emitter.js +80 -0
- package/dist/event-emitter.js.map +1 -0
- package/dist/graphql.d.ts +71 -0
- package/dist/graphql.d.ts.map +1 -0
- package/dist/graphql.js +487 -0
- package/dist/graphql.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/jsonld.d.ts +47 -0
- package/dist/jsonld.d.ts.map +1 -0
- package/dist/jsonld.js +137 -0
- package/dist/jsonld.js.map +1 -0
- package/dist/media.d.ts +52 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +173 -0
- package/dist/media.js.map +1 -0
- package/dist/space.d.ts +358 -0
- package/dist/space.d.ts.map +1 -0
- package/dist/space.js +1121 -0
- package/dist/space.js.map +1 -0
- package/dist/subscription.d.ts +57 -0
- package/dist/subscription.d.ts.map +1 -0
- package/dist/subscription.js +296 -0
- package/dist/subscription.js.map +1 -0
- package/dist/types.d.ts +409 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/package.json +65 -0
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { EventEmitter } from './event-emitter.js';
|
|
2
|
+
import { RoolSpace } from './space.js';
|
|
3
|
+
import type { RoolClientConfig, RoolClientEvents, RoolSpaceInfo, CurrentUser, UserResult, AuthUser, PublishedAppInfo, PublishAppOptions } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Rool Client - Manages authentication, space lifecycle, and shared infrastructure.
|
|
6
|
+
*
|
|
7
|
+
* The client is lightweight - most operations happen on Space instances
|
|
8
|
+
*
|
|
9
|
+
* Features:
|
|
10
|
+
* - Authentication (login, logout, token management)
|
|
11
|
+
* - Spaces lifecycle (list, open, create, delete)
|
|
12
|
+
* - Client-level subscription for lifecycle events
|
|
13
|
+
* - Media operations
|
|
14
|
+
* - AI operations (prompt, image generation)
|
|
15
|
+
*/
|
|
16
|
+
export declare class RoolClient extends EventEmitter<RoolClientEvents> {
|
|
17
|
+
private urls;
|
|
18
|
+
private authManager;
|
|
19
|
+
private graphqlClient;
|
|
20
|
+
private subscriptionManager;
|
|
21
|
+
private openSpaces;
|
|
22
|
+
private _storageCache;
|
|
23
|
+
constructor(config: RoolClientConfig);
|
|
24
|
+
/**
|
|
25
|
+
* Initialize the client - should be called on app startup.
|
|
26
|
+
* Processes any auth callback in the URL, sets up auto-refresh,
|
|
27
|
+
* and starts real-time event subscription if authenticated.
|
|
28
|
+
* @returns true if an auth callback was processed
|
|
29
|
+
*/
|
|
30
|
+
initialize(): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Clean up resources - call when destroying the client.
|
|
33
|
+
*/
|
|
34
|
+
destroy(): void;
|
|
35
|
+
/**
|
|
36
|
+
* Initiate login by redirecting to auth page.
|
|
37
|
+
* @param appName - The name of the application requesting login (displayed on auth page)
|
|
38
|
+
*/
|
|
39
|
+
login(appName: string): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Logout - clear all tokens and state.
|
|
42
|
+
*/
|
|
43
|
+
logout(): void;
|
|
44
|
+
/**
|
|
45
|
+
* Process auth callback from URL fragment.
|
|
46
|
+
* Called automatically by initialize(), but can be called manually.
|
|
47
|
+
*/
|
|
48
|
+
processAuthCallback(): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Check if user is currently authenticated (validates token is usable).
|
|
51
|
+
*/
|
|
52
|
+
isAuthenticated(): Promise<boolean>;
|
|
53
|
+
/**
|
|
54
|
+
* Get current access token.
|
|
55
|
+
* Returns undefined if not authenticated.
|
|
56
|
+
*/
|
|
57
|
+
getToken(): Promise<string | undefined>;
|
|
58
|
+
/**
|
|
59
|
+
* Get auth identity decoded from JWT token.
|
|
60
|
+
* For the Rool user (with server-assigned id), use getCurrentUser().
|
|
61
|
+
*/
|
|
62
|
+
getAuthUser(): AuthUser;
|
|
63
|
+
/**
|
|
64
|
+
* List all spaces accessible to the user.
|
|
65
|
+
*/
|
|
66
|
+
listSpaces(): Promise<RoolSpaceInfo[]>;
|
|
67
|
+
/**
|
|
68
|
+
* Open an existing space.
|
|
69
|
+
* Loads the space data from the server and returns a Space instance.
|
|
70
|
+
* The space manages its own real-time subscription.
|
|
71
|
+
*
|
|
72
|
+
* @param spaceId - The ID of the space to open
|
|
73
|
+
* @param options.conversationId - Optional conversation ID for AI context continuity. If not provided, a new conversation is created.
|
|
74
|
+
*/
|
|
75
|
+
openSpace(spaceId: string, options?: {
|
|
76
|
+
conversationId?: string;
|
|
77
|
+
}): Promise<RoolSpace>;
|
|
78
|
+
/**
|
|
79
|
+
* Create a new space.
|
|
80
|
+
* Creates on server and returns a Space instance.
|
|
81
|
+
* The space manages its own real-time subscription.
|
|
82
|
+
*
|
|
83
|
+
* @param name - Optional name for the space
|
|
84
|
+
* @param options.conversationId - Optional conversation ID for AI context continuity. If not provided, a new conversation is created.
|
|
85
|
+
*/
|
|
86
|
+
createSpace(name?: string, options?: {
|
|
87
|
+
conversationId?: string;
|
|
88
|
+
}): Promise<RoolSpace>;
|
|
89
|
+
/**
|
|
90
|
+
* Delete a space.
|
|
91
|
+
* Note: This does not affect any open Space instances - they become stale.
|
|
92
|
+
*/
|
|
93
|
+
deleteSpace(spaceId: string): Promise<void>;
|
|
94
|
+
/**
|
|
95
|
+
* Get the current Rool user from the server.
|
|
96
|
+
* Returns the user's server-assigned id, email, plan, and credits.
|
|
97
|
+
*/
|
|
98
|
+
getCurrentUser(): Promise<CurrentUser>;
|
|
99
|
+
/**
|
|
100
|
+
* Search for a user by email.
|
|
101
|
+
*/
|
|
102
|
+
searchUser(email: string): Promise<UserResult | null>;
|
|
103
|
+
/**
|
|
104
|
+
* Set the current user's slug (used in app publishing URLs).
|
|
105
|
+
* Slug must be 3-32 characters, start with a letter, and contain only
|
|
106
|
+
* lowercase letters, numbers, hyphens, and underscores.
|
|
107
|
+
* Cannot be changed if the user has published apps.
|
|
108
|
+
*/
|
|
109
|
+
setSlug(slug: string): Promise<void>;
|
|
110
|
+
/**
|
|
111
|
+
* Publish an app. The app will be accessible at:
|
|
112
|
+
* https://rool.app/{user_slug}/{appId}/
|
|
113
|
+
*
|
|
114
|
+
* @param appId - URL-safe identifier (alphanumeric, hyphens, underscores)
|
|
115
|
+
* @param options - App name, bundle (zip file), and optional SPA flag
|
|
116
|
+
*/
|
|
117
|
+
publishApp(appId: string, options: PublishAppOptions): Promise<PublishedAppInfo>;
|
|
118
|
+
/**
|
|
119
|
+
* Unpublish an app.
|
|
120
|
+
*/
|
|
121
|
+
unpublishApp(appId: string): Promise<void>;
|
|
122
|
+
/**
|
|
123
|
+
* List all published apps for the current user.
|
|
124
|
+
*/
|
|
125
|
+
listApps(): Promise<PublishedAppInfo[]>;
|
|
126
|
+
/**
|
|
127
|
+
* Get info for a specific published app.
|
|
128
|
+
* Returns null if the app doesn't exist.
|
|
129
|
+
*/
|
|
130
|
+
getAppInfo(appId: string): Promise<PublishedAppInfo | null>;
|
|
131
|
+
/**
|
|
132
|
+
* Get a value from user storage (sync read from local cache).
|
|
133
|
+
* Returns undefined if key doesn't exist.
|
|
134
|
+
*/
|
|
135
|
+
getUserStorage<T = unknown>(key: string): T | undefined;
|
|
136
|
+
/**
|
|
137
|
+
* Set a value in user storage.
|
|
138
|
+
* Updates local cache immediately, then syncs to server.
|
|
139
|
+
* Pass undefined/null to delete the key.
|
|
140
|
+
* Storage is limited to 10MB total.
|
|
141
|
+
*/
|
|
142
|
+
setUserStorage(key: string, value: unknown): void;
|
|
143
|
+
/**
|
|
144
|
+
* Get all user storage data (sync read from local cache).
|
|
145
|
+
*/
|
|
146
|
+
getAllUserStorage(): Record<string, unknown>;
|
|
147
|
+
private get mediaClient();
|
|
148
|
+
private get appsClient();
|
|
149
|
+
/**
|
|
150
|
+
* Ensure the client-level event subscription is active.
|
|
151
|
+
* Called automatically when opening spaces.
|
|
152
|
+
* Also fetches and caches the current user ID.
|
|
153
|
+
* @internal
|
|
154
|
+
*/
|
|
155
|
+
private ensureSubscribed;
|
|
156
|
+
/**
|
|
157
|
+
* Disconnect from real-time events.
|
|
158
|
+
* @internal
|
|
159
|
+
*/
|
|
160
|
+
private unsubscribe;
|
|
161
|
+
/**
|
|
162
|
+
* Generate a unique entity ID.
|
|
163
|
+
* 6-character alphanumeric string (62^6 = 56.8 billion possible values).
|
|
164
|
+
* Also available as top-level generateEntityId() export.
|
|
165
|
+
*/
|
|
166
|
+
static generateId(): string;
|
|
167
|
+
/**
|
|
168
|
+
* Execute an arbitrary GraphQL query or mutation.
|
|
169
|
+
* Use this escape hatch for app-specific operations not covered by the typed API.
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* const result = await client.graphql<{ lastMessages: Message[] }>(
|
|
173
|
+
* `query trace($spaceId: String!) { trace(spaceId: $spaceId) }`,
|
|
174
|
+
* { spaceId: 'abc123' }
|
|
175
|
+
* );
|
|
176
|
+
*/
|
|
177
|
+
graphql<T>(query: string, variables?: Record<string, unknown>): Promise<T>;
|
|
178
|
+
private registerSpace;
|
|
179
|
+
private unregisterSpace;
|
|
180
|
+
/**
|
|
181
|
+
* Handle a client-level event from the subscription.
|
|
182
|
+
* @internal
|
|
183
|
+
*/
|
|
184
|
+
private handleClientEvent;
|
|
185
|
+
/**
|
|
186
|
+
* Load storage cache from auth provider.
|
|
187
|
+
* @internal
|
|
188
|
+
*/
|
|
189
|
+
private loadStorageCache;
|
|
190
|
+
/**
|
|
191
|
+
* Save storage cache via auth provider.
|
|
192
|
+
* @internal
|
|
193
|
+
*/
|
|
194
|
+
private saveStorageCache;
|
|
195
|
+
/**
|
|
196
|
+
* Handle a user storage change from SSE (remote update).
|
|
197
|
+
* Updates cache and emits event if value actually changed.
|
|
198
|
+
* @internal
|
|
199
|
+
*/
|
|
200
|
+
private handleUserStorageChanged;
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Rool Client
|
|
3
|
+
// =============================================================================
|
|
4
|
+
import { EventEmitter } from './event-emitter.js';
|
|
5
|
+
import { AuthManager } from './auth.js';
|
|
6
|
+
import { GraphQLClient } from './graphql.js';
|
|
7
|
+
import { ClientSubscriptionManager } from './subscription.js';
|
|
8
|
+
import { MediaClient } from './media.js';
|
|
9
|
+
import { AppsClient } from './apps.js';
|
|
10
|
+
import { RoolSpace, generateEntityId } from './space.js';
|
|
11
|
+
/**
|
|
12
|
+
* Rool Client - Manages authentication, space lifecycle, and shared infrastructure.
|
|
13
|
+
*
|
|
14
|
+
* The client is lightweight - most operations happen on Space instances
|
|
15
|
+
*
|
|
16
|
+
* Features:
|
|
17
|
+
* - Authentication (login, logout, token management)
|
|
18
|
+
* - Spaces lifecycle (list, open, create, delete)
|
|
19
|
+
* - Client-level subscription for lifecycle events
|
|
20
|
+
* - Media operations
|
|
21
|
+
* - AI operations (prompt, image generation)
|
|
22
|
+
*/
|
|
23
|
+
export class RoolClient extends EventEmitter {
|
|
24
|
+
urls;
|
|
25
|
+
authManager;
|
|
26
|
+
graphqlClient;
|
|
27
|
+
subscriptionManager = null;
|
|
28
|
+
// Registry of open spaces (for cleanup on logout/destroy)
|
|
29
|
+
openSpaces = new Map();
|
|
30
|
+
// User storage cache (synced to localStorage)
|
|
31
|
+
_storageCache = {};
|
|
32
|
+
constructor(config) {
|
|
33
|
+
super();
|
|
34
|
+
const baseUrl = config.baseUrl.replace(/\/+$/, ''); // Remove trailing slashes
|
|
35
|
+
this.urls = {
|
|
36
|
+
graphql: config.graphqlUrl ?? `${baseUrl}/graphql`,
|
|
37
|
+
media: config.mediaUrl ?? `${baseUrl}/media`,
|
|
38
|
+
auth: config.authUrl ?? `${baseUrl}/auth`,
|
|
39
|
+
apps: `${baseUrl}/apps`,
|
|
40
|
+
};
|
|
41
|
+
this.authManager = new AuthManager({
|
|
42
|
+
authUrl: this.urls.auth,
|
|
43
|
+
authProvider: config.authProvider,
|
|
44
|
+
onAuthStateChanged: (authenticated) => {
|
|
45
|
+
this.emit('authStateChanged', authenticated);
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
this.graphqlClient = new GraphQLClient({
|
|
49
|
+
graphqlUrl: this.urls.graphql,
|
|
50
|
+
authManager: this.authManager,
|
|
51
|
+
});
|
|
52
|
+
// Load storage cache from localStorage
|
|
53
|
+
this.loadStorageCache();
|
|
54
|
+
}
|
|
55
|
+
// ===========================================================================
|
|
56
|
+
// Initialization
|
|
57
|
+
// ===========================================================================
|
|
58
|
+
/**
|
|
59
|
+
* Initialize the client - should be called on app startup.
|
|
60
|
+
* Processes any auth callback in the URL, sets up auto-refresh,
|
|
61
|
+
* and starts real-time event subscription if authenticated.
|
|
62
|
+
* @returns true if an auth callback was processed
|
|
63
|
+
*/
|
|
64
|
+
initialize() {
|
|
65
|
+
const result = this.authManager.initialize();
|
|
66
|
+
// Auto-subscribe to real-time events if authenticated (fire-and-forget)
|
|
67
|
+
void this.authManager.isAuthenticated().then(authenticated => {
|
|
68
|
+
if (authenticated) {
|
|
69
|
+
void this.ensureSubscribed();
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Clean up resources - call when destroying the client.
|
|
76
|
+
*/
|
|
77
|
+
destroy() {
|
|
78
|
+
this.authManager.destroy();
|
|
79
|
+
this.subscriptionManager?.destroy();
|
|
80
|
+
// Close all open spaces
|
|
81
|
+
for (const space of this.openSpaces.values()) {
|
|
82
|
+
space.close();
|
|
83
|
+
}
|
|
84
|
+
this.openSpaces.clear();
|
|
85
|
+
this.removeAllListeners();
|
|
86
|
+
}
|
|
87
|
+
// ===========================================================================
|
|
88
|
+
// Authentication
|
|
89
|
+
// ===========================================================================
|
|
90
|
+
/**
|
|
91
|
+
* Initiate login by redirecting to auth page.
|
|
92
|
+
* @param appName - The name of the application requesting login (displayed on auth page)
|
|
93
|
+
*/
|
|
94
|
+
async login(appName) {
|
|
95
|
+
return this.authManager.login(appName);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Logout - clear all tokens and state.
|
|
99
|
+
*/
|
|
100
|
+
logout() {
|
|
101
|
+
this.authManager.logout();
|
|
102
|
+
this.unsubscribe();
|
|
103
|
+
// Close all open spaces
|
|
104
|
+
for (const space of this.openSpaces.values()) {
|
|
105
|
+
space.close();
|
|
106
|
+
}
|
|
107
|
+
this.openSpaces.clear();
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Process auth callback from URL fragment.
|
|
111
|
+
* Called automatically by initialize(), but can be called manually.
|
|
112
|
+
*/
|
|
113
|
+
processAuthCallback() {
|
|
114
|
+
return this.authManager.processCallback();
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Check if user is currently authenticated (validates token is usable).
|
|
118
|
+
*/
|
|
119
|
+
async isAuthenticated() {
|
|
120
|
+
return this.authManager.isAuthenticated();
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Get current access token.
|
|
124
|
+
* Returns undefined if not authenticated.
|
|
125
|
+
*/
|
|
126
|
+
async getToken() {
|
|
127
|
+
return this.authManager.getToken();
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Get auth identity decoded from JWT token.
|
|
131
|
+
* For the Rool user (with server-assigned id), use getCurrentUser().
|
|
132
|
+
*/
|
|
133
|
+
getAuthUser() {
|
|
134
|
+
return this.authManager.getAuthUser();
|
|
135
|
+
}
|
|
136
|
+
// ===========================================================================
|
|
137
|
+
// Space Lifecycle
|
|
138
|
+
// ===========================================================================
|
|
139
|
+
/**
|
|
140
|
+
* List all spaces accessible to the user.
|
|
141
|
+
*/
|
|
142
|
+
async listSpaces() {
|
|
143
|
+
return this.graphqlClient.listSpaces();
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Open an existing space.
|
|
147
|
+
* Loads the space data from the server and returns a Space instance.
|
|
148
|
+
* The space manages its own real-time subscription.
|
|
149
|
+
*
|
|
150
|
+
* @param spaceId - The ID of the space to open
|
|
151
|
+
* @param options.conversationId - Optional conversation ID for AI context continuity. If not provided, a new conversation is created.
|
|
152
|
+
*/
|
|
153
|
+
async openSpace(spaceId, options) {
|
|
154
|
+
// Ensure client subscription is active (for lifecycle events)
|
|
155
|
+
void this.ensureSubscribed();
|
|
156
|
+
const { data, name, role, userId } = await this.graphqlClient.getSpace(spaceId);
|
|
157
|
+
const space = new RoolSpace({
|
|
158
|
+
id: spaceId,
|
|
159
|
+
name,
|
|
160
|
+
role: role,
|
|
161
|
+
userId,
|
|
162
|
+
initialData: data,
|
|
163
|
+
conversationId: options?.conversationId,
|
|
164
|
+
graphqlClient: this.graphqlClient,
|
|
165
|
+
mediaClient: this.mediaClient,
|
|
166
|
+
graphqlUrl: this.urls.graphql,
|
|
167
|
+
authManager: this.authManager,
|
|
168
|
+
onClose: (id) => this.unregisterSpace(id),
|
|
169
|
+
});
|
|
170
|
+
// Register for cleanup
|
|
171
|
+
this.registerSpace(spaceId, space);
|
|
172
|
+
return space;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Create a new space.
|
|
176
|
+
* Creates on server and returns a Space instance.
|
|
177
|
+
* The space manages its own real-time subscription.
|
|
178
|
+
*
|
|
179
|
+
* @param name - Optional name for the space
|
|
180
|
+
* @param options.conversationId - Optional conversation ID for AI context continuity. If not provided, a new conversation is created.
|
|
181
|
+
*/
|
|
182
|
+
async createSpace(name, options) {
|
|
183
|
+
// Ensure client subscription is active (for lifecycle events)
|
|
184
|
+
void this.ensureSubscribed();
|
|
185
|
+
const spaceId = generateEntityId();
|
|
186
|
+
const spaceName = name ?? spaceId;
|
|
187
|
+
// Create on server with name (use a temporary conversationId for lifecycle ops)
|
|
188
|
+
await this.graphqlClient.createSpace(spaceId, spaceName, generateEntityId());
|
|
189
|
+
// Fetch the created space to get userId (server assigns it)
|
|
190
|
+
const { data, userId } = await this.graphqlClient.getSpace(spaceId);
|
|
191
|
+
const space = new RoolSpace({
|
|
192
|
+
id: spaceId,
|
|
193
|
+
name: spaceName,
|
|
194
|
+
role: 'owner',
|
|
195
|
+
userId,
|
|
196
|
+
initialData: data,
|
|
197
|
+
conversationId: options?.conversationId,
|
|
198
|
+
graphqlClient: this.graphqlClient,
|
|
199
|
+
mediaClient: this.mediaClient,
|
|
200
|
+
graphqlUrl: this.urls.graphql,
|
|
201
|
+
authManager: this.authManager,
|
|
202
|
+
onClose: (id) => this.unregisterSpace(id),
|
|
203
|
+
});
|
|
204
|
+
// Register for cleanup
|
|
205
|
+
this.registerSpace(spaceId, space);
|
|
206
|
+
return space;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Delete a space.
|
|
210
|
+
* Note: This does not affect any open Space instances - they become stale.
|
|
211
|
+
*/
|
|
212
|
+
async deleteSpace(spaceId) {
|
|
213
|
+
// Use a temporary conversationId for lifecycle ops
|
|
214
|
+
await this.graphqlClient.deleteSpace(spaceId, generateEntityId());
|
|
215
|
+
// Client-level event will be emitted via SSE subscription
|
|
216
|
+
}
|
|
217
|
+
// ===========================================================================
|
|
218
|
+
// User Operations
|
|
219
|
+
// ===========================================================================
|
|
220
|
+
/**
|
|
221
|
+
* Get the current Rool user from the server.
|
|
222
|
+
* Returns the user's server-assigned id, email, plan, and credits.
|
|
223
|
+
*/
|
|
224
|
+
async getCurrentUser() {
|
|
225
|
+
return this.graphqlClient.getAccount();
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Search for a user by email.
|
|
229
|
+
*/
|
|
230
|
+
async searchUser(email) {
|
|
231
|
+
return this.graphqlClient.searchUser(email);
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Set the current user's slug (used in app publishing URLs).
|
|
235
|
+
* Slug must be 3-32 characters, start with a letter, and contain only
|
|
236
|
+
* lowercase letters, numbers, hyphens, and underscores.
|
|
237
|
+
* Cannot be changed if the user has published apps.
|
|
238
|
+
*/
|
|
239
|
+
async setSlug(slug) {
|
|
240
|
+
return this.graphqlClient.setSlug(slug);
|
|
241
|
+
}
|
|
242
|
+
// ===========================================================================
|
|
243
|
+
// App Publishing
|
|
244
|
+
// ===========================================================================
|
|
245
|
+
/**
|
|
246
|
+
* Publish an app. The app will be accessible at:
|
|
247
|
+
* https://rool.app/{user_slug}/{appId}/
|
|
248
|
+
*
|
|
249
|
+
* @param appId - URL-safe identifier (alphanumeric, hyphens, underscores)
|
|
250
|
+
* @param options - App name, bundle (zip file), and optional SPA flag
|
|
251
|
+
*/
|
|
252
|
+
async publishApp(appId, options) {
|
|
253
|
+
return this.appsClient.publish(appId, options);
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Unpublish an app.
|
|
257
|
+
*/
|
|
258
|
+
async unpublishApp(appId) {
|
|
259
|
+
return this.appsClient.unpublish(appId);
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* List all published apps for the current user.
|
|
263
|
+
*/
|
|
264
|
+
async listApps() {
|
|
265
|
+
return this.appsClient.list();
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Get info for a specific published app.
|
|
269
|
+
* Returns null if the app doesn't exist.
|
|
270
|
+
*/
|
|
271
|
+
async getAppInfo(appId) {
|
|
272
|
+
return this.appsClient.get(appId);
|
|
273
|
+
}
|
|
274
|
+
// ===========================================================================
|
|
275
|
+
// User Storage (server-side localStorage equivalent)
|
|
276
|
+
// ===========================================================================
|
|
277
|
+
/**
|
|
278
|
+
* Get a value from user storage (sync read from local cache).
|
|
279
|
+
* Returns undefined if key doesn't exist.
|
|
280
|
+
*/
|
|
281
|
+
getUserStorage(key) {
|
|
282
|
+
return this._storageCache[key];
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Set a value in user storage.
|
|
286
|
+
* Updates local cache immediately, then syncs to server.
|
|
287
|
+
* Pass undefined/null to delete the key.
|
|
288
|
+
* Storage is limited to 10MB total.
|
|
289
|
+
*/
|
|
290
|
+
setUserStorage(key, value) {
|
|
291
|
+
// Update local cache
|
|
292
|
+
if (value === null || value === undefined) {
|
|
293
|
+
delete this._storageCache[key];
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
this._storageCache[key] = value;
|
|
297
|
+
}
|
|
298
|
+
// Persist to localStorage
|
|
299
|
+
this.saveStorageCache();
|
|
300
|
+
// Emit event (local source)
|
|
301
|
+
this.emit('userStorageChanged', { key, value: value ?? null, source: 'local' });
|
|
302
|
+
// Fire-and-forget server sync
|
|
303
|
+
this.graphqlClient.setUserStorage(key, value).catch((error) => {
|
|
304
|
+
console.error('[RoolClient] Failed to sync user storage:', error);
|
|
305
|
+
this.emit('error', error instanceof Error ? error : new Error(String(error)), 'userStorage');
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Get all user storage data (sync read from local cache).
|
|
310
|
+
*/
|
|
311
|
+
getAllUserStorage() {
|
|
312
|
+
return { ...this._storageCache };
|
|
313
|
+
}
|
|
314
|
+
// ===========================================================================
|
|
315
|
+
// Media Client (used internally by Space instances)
|
|
316
|
+
// ===========================================================================
|
|
317
|
+
get mediaClient() {
|
|
318
|
+
return new MediaClient({
|
|
319
|
+
mediaUrl: this.urls.media,
|
|
320
|
+
backendOrigin: new URL(this.urls.media).origin,
|
|
321
|
+
authManager: this.authManager,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
// ===========================================================================
|
|
325
|
+
// Apps Client (used for app publishing)
|
|
326
|
+
// ===========================================================================
|
|
327
|
+
get appsClient() {
|
|
328
|
+
return new AppsClient({
|
|
329
|
+
appsUrl: this.urls.apps,
|
|
330
|
+
authManager: this.authManager,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
// ===========================================================================
|
|
334
|
+
// Subscriptions (internal - auto-managed)
|
|
335
|
+
// ===========================================================================
|
|
336
|
+
/**
|
|
337
|
+
* Ensure the client-level event subscription is active.
|
|
338
|
+
* Called automatically when opening spaces.
|
|
339
|
+
* Also fetches and caches the current user ID.
|
|
340
|
+
* @internal
|
|
341
|
+
*/
|
|
342
|
+
async ensureSubscribed() {
|
|
343
|
+
if (this.subscriptionManager)
|
|
344
|
+
return;
|
|
345
|
+
this.subscriptionManager = new ClientSubscriptionManager({
|
|
346
|
+
graphqlUrl: this.urls.graphql,
|
|
347
|
+
authManager: this.authManager,
|
|
348
|
+
onEvent: (event) => this.handleClientEvent(event),
|
|
349
|
+
onConnectionStateChanged: (state) => {
|
|
350
|
+
this.emit('connectionStateChanged', state);
|
|
351
|
+
},
|
|
352
|
+
onError: (error) => {
|
|
353
|
+
this.emit('error', error, 'subscription');
|
|
354
|
+
},
|
|
355
|
+
});
|
|
356
|
+
await this.subscriptionManager.subscribe();
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Disconnect from real-time events.
|
|
360
|
+
* @internal
|
|
361
|
+
*/
|
|
362
|
+
unsubscribe() {
|
|
363
|
+
if (this.subscriptionManager) {
|
|
364
|
+
this.subscriptionManager.destroy();
|
|
365
|
+
this.subscriptionManager = null;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
// ===========================================================================
|
|
369
|
+
// Utilities
|
|
370
|
+
// ===========================================================================
|
|
371
|
+
/**
|
|
372
|
+
* Generate a unique entity ID.
|
|
373
|
+
* 6-character alphanumeric string (62^6 = 56.8 billion possible values).
|
|
374
|
+
* Also available as top-level generateEntityId() export.
|
|
375
|
+
*/
|
|
376
|
+
static generateId() {
|
|
377
|
+
return generateEntityId();
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Execute an arbitrary GraphQL query or mutation.
|
|
381
|
+
* Use this escape hatch for app-specific operations not covered by the typed API.
|
|
382
|
+
*
|
|
383
|
+
* @example
|
|
384
|
+
* const result = await client.graphql<{ lastMessages: Message[] }>(
|
|
385
|
+
* `query trace($spaceId: String!) { trace(spaceId: $spaceId) }`,
|
|
386
|
+
* { spaceId: 'abc123' }
|
|
387
|
+
* );
|
|
388
|
+
*/
|
|
389
|
+
async graphql(query, variables) {
|
|
390
|
+
return this.graphqlClient.query(query, variables);
|
|
391
|
+
}
|
|
392
|
+
// ===========================================================================
|
|
393
|
+
// Private Methods - Space Registry
|
|
394
|
+
// ===========================================================================
|
|
395
|
+
registerSpace(spaceId, space) {
|
|
396
|
+
this.openSpaces.set(spaceId, space);
|
|
397
|
+
}
|
|
398
|
+
unregisterSpace(spaceId) {
|
|
399
|
+
this.openSpaces.delete(spaceId);
|
|
400
|
+
}
|
|
401
|
+
// ===========================================================================
|
|
402
|
+
// Private Methods - Event Handling
|
|
403
|
+
// ===========================================================================
|
|
404
|
+
/**
|
|
405
|
+
* Handle a client-level event from the subscription.
|
|
406
|
+
* @internal
|
|
407
|
+
*/
|
|
408
|
+
handleClientEvent(event) {
|
|
409
|
+
switch (event.type) {
|
|
410
|
+
case 'space_created':
|
|
411
|
+
this.emit('spaceCreated', {
|
|
412
|
+
id: event.spaceId,
|
|
413
|
+
name: event.name ?? event.spaceId,
|
|
414
|
+
role: event.role ?? 'owner',
|
|
415
|
+
ownerId: event.ownerId ?? '',
|
|
416
|
+
size: event.size ?? 0,
|
|
417
|
+
createdAt: event.createdAt ?? new Date().toISOString(),
|
|
418
|
+
updatedAt: event.updatedAt ?? new Date().toISOString(),
|
|
419
|
+
});
|
|
420
|
+
break;
|
|
421
|
+
case 'space_deleted':
|
|
422
|
+
this.emit('spaceDeleted', event.spaceId);
|
|
423
|
+
break;
|
|
424
|
+
case 'space_renamed':
|
|
425
|
+
this.emit('spaceRenamed', event.spaceId, event.name ?? event.spaceId);
|
|
426
|
+
break;
|
|
427
|
+
case 'user_storage_changed':
|
|
428
|
+
this.handleUserStorageChanged(event.key, event.value);
|
|
429
|
+
break;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
// ===========================================================================
|
|
433
|
+
// Private Methods - User Storage Cache
|
|
434
|
+
// ===========================================================================
|
|
435
|
+
/**
|
|
436
|
+
* Load storage cache from auth provider.
|
|
437
|
+
* @internal
|
|
438
|
+
*/
|
|
439
|
+
loadStorageCache() {
|
|
440
|
+
const cached = this.authManager.getStorage();
|
|
441
|
+
if (cached) {
|
|
442
|
+
this._storageCache = cached;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Save storage cache via auth provider.
|
|
447
|
+
* @internal
|
|
448
|
+
*/
|
|
449
|
+
saveStorageCache() {
|
|
450
|
+
this.authManager.setStorage(this._storageCache);
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Handle a user storage change from SSE (remote update).
|
|
454
|
+
* Updates cache and emits event if value actually changed.
|
|
455
|
+
* @internal
|
|
456
|
+
*/
|
|
457
|
+
handleUserStorageChanged(key, value) {
|
|
458
|
+
const currentValue = this._storageCache[key];
|
|
459
|
+
// Only update and emit if value actually changed
|
|
460
|
+
if (JSON.stringify(currentValue) !== JSON.stringify(value)) {
|
|
461
|
+
if (value === null || value === undefined) {
|
|
462
|
+
delete this._storageCache[key];
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
this._storageCache[key] = value;
|
|
466
|
+
}
|
|
467
|
+
this.saveStorageCache();
|
|
468
|
+
this.emit('userStorageChanged', { key, value: value ?? null, source: 'remote' });
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
//# sourceMappingURL=client.js.map
|