@rimori/client 2.2.0 → 2.3.0-next.1
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/.github/workflows/pre-release.yml +126 -0
- package/README.md +9 -13
- package/dist/cli/scripts/init/main.js +0 -0
- package/dist/cli/scripts/release/release.js +0 -0
- package/dist/controller/SettingsController.d.ts +6 -2
- package/dist/controller/TranslationController.d.ts +4 -2
- package/dist/controller/TranslationController.js +40 -18
- package/dist/fromRimori/EventBus.js +49 -29
- package/dist/fromRimori/PluginTypes.d.ts +6 -6
- package/dist/index.d.ts +2 -1
- package/dist/plugin/CommunicationHandler.d.ts +11 -0
- package/dist/plugin/CommunicationHandler.js +9 -7
- package/dist/plugin/Logger.d.ts +1 -0
- package/dist/plugin/Logger.js +15 -3
- package/dist/plugin/RimoriClient.d.ts +12 -0
- package/dist/plugin/RimoriClient.js +31 -8
- package/example/worker/vite.config.ts +3 -0
- package/package.json +7 -1
- package/src/controller/SettingsController.ts +7 -2
- package/src/controller/TranslationController.ts +51 -22
- package/src/fromRimori/EventBus.ts +105 -53
- package/src/fromRimori/PluginTypes.ts +28 -19
- package/src/index.ts +2 -1
- package/src/plugin/CommunicationHandler.ts +20 -7
- package/src/plugin/Logger.ts +15 -3
- package/src/plugin/RimoriClient.ts +31 -8
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
name: Pre-Release Rimori Client
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [dev]
|
|
6
|
+
paths:
|
|
7
|
+
- '**'
|
|
8
|
+
- '!.github/workflows/**'
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
pre-release:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
permissions:
|
|
14
|
+
contents: write
|
|
15
|
+
id-token: write
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- name: Checkout repository
|
|
19
|
+
uses: actions/checkout@v4
|
|
20
|
+
with:
|
|
21
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
22
|
+
fetch-depth: 0
|
|
23
|
+
|
|
24
|
+
- name: Setup Node.js
|
|
25
|
+
uses: actions/setup-node@v4
|
|
26
|
+
with:
|
|
27
|
+
node-version: '20'
|
|
28
|
+
registry-url: 'https://registry.npmjs.org'
|
|
29
|
+
cache: 'yarn'
|
|
30
|
+
cache-dependency-path: yarn.lock
|
|
31
|
+
|
|
32
|
+
- name: Update npm
|
|
33
|
+
run: npm install -g npm@latest
|
|
34
|
+
|
|
35
|
+
- name: Install dependencies
|
|
36
|
+
run: yarn install --frozen-lockfile
|
|
37
|
+
|
|
38
|
+
- name: Build rimori-client (TypeScript verification)
|
|
39
|
+
run: yarn build
|
|
40
|
+
|
|
41
|
+
- name: Calculate next pre-release version
|
|
42
|
+
id: version
|
|
43
|
+
run: |
|
|
44
|
+
# Read current version from package.json (may be base or pre-release)
|
|
45
|
+
CURRENT_VERSION=$(node -p "require('./package.json').version")
|
|
46
|
+
|
|
47
|
+
# Extract base version (strip any pre-release suffix)
|
|
48
|
+
# Examples: "2.2.0" -> "2.2.0", "2.2.0-next.5" -> "2.2.0"
|
|
49
|
+
if [[ "$CURRENT_VERSION" =~ ^([0-9]+\.[0-9]+\.[0-9]+) ]]; then
|
|
50
|
+
BASE_VERSION="${BASH_REMATCH[1]}"
|
|
51
|
+
else
|
|
52
|
+
BASE_VERSION="$CURRENT_VERSION"
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
# Try to get latest next version from npm
|
|
56
|
+
LATEST_NEXT=$(npm view @rimori/client@next version 2>/dev/null || echo "none")
|
|
57
|
+
|
|
58
|
+
if [ "$LATEST_NEXT" != "none" ]; then
|
|
59
|
+
# Extract base version and pre-release number from latest next version
|
|
60
|
+
# Example: "2.2.0-next.5" -> extract "2.2.0" and "5"
|
|
61
|
+
if [[ "$LATEST_NEXT" =~ ^([0-9]+\.[0-9]+\.[0-9]+)-next\.([0-9]+)$ ]]; then
|
|
62
|
+
LATEST_BASE="${BASH_REMATCH[1]}"
|
|
63
|
+
PRERELEASE_NUM="${BASH_REMATCH[2]}"
|
|
64
|
+
|
|
65
|
+
# If base version changed, reset to 1, otherwise increment
|
|
66
|
+
if [ "$LATEST_BASE" != "$BASE_VERSION" ]; then
|
|
67
|
+
NEW_NUM=1
|
|
68
|
+
else
|
|
69
|
+
NEW_NUM=$((PRERELEASE_NUM + 1))
|
|
70
|
+
fi
|
|
71
|
+
else
|
|
72
|
+
# Fallback: if format doesn't match, start at 1
|
|
73
|
+
NEW_NUM=1
|
|
74
|
+
fi
|
|
75
|
+
else
|
|
76
|
+
# First pre-release
|
|
77
|
+
NEW_NUM=1
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
NEW_VERSION="${BASE_VERSION}-next.${NEW_NUM}"
|
|
81
|
+
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
|
|
82
|
+
echo "Base version: $BASE_VERSION"
|
|
83
|
+
echo "Calculated next version: $NEW_VERSION"
|
|
84
|
+
|
|
85
|
+
- name: Update package.json version
|
|
86
|
+
run: |
|
|
87
|
+
# Use node to update version directly (yarn version creates git tags)
|
|
88
|
+
node -e "const fs = require('fs'); const pkg = JSON.parse(fs.readFileSync('package.json')); pkg.version = '${{ steps.version.outputs.new_version }}'; fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');"
|
|
89
|
+
|
|
90
|
+
- name: Publish to npm
|
|
91
|
+
run: npm publish --tag next --access public
|
|
92
|
+
# Uses OIDC token automatically (no NODE_AUTH_TOKEN needed)
|
|
93
|
+
# Requires npm 11.5.1+ and id-token: write permission (already set)
|
|
94
|
+
|
|
95
|
+
- name: Output published version
|
|
96
|
+
run: |
|
|
97
|
+
echo "✅ Published @rimori/client@${{ steps.version.outputs.new_version }} to npm with @next tag"
|
|
98
|
+
|
|
99
|
+
- name: Create git tag
|
|
100
|
+
run: |
|
|
101
|
+
git config --local user.email "action@github.com"
|
|
102
|
+
git config --local user.name "GitHub Action"
|
|
103
|
+
git tag "v${{ steps.version.outputs.new_version }}" -m "Pre-release v${{ steps.version.outputs.new_version }}"
|
|
104
|
+
git push origin "v${{ steps.version.outputs.new_version }}"
|
|
105
|
+
echo "🏷️ Created and pushed tag v${{ steps.version.outputs.new_version }}"
|
|
106
|
+
|
|
107
|
+
- name: Notify Slack
|
|
108
|
+
if: always()
|
|
109
|
+
uses: slackapi/slack-github-action@v1.24.0
|
|
110
|
+
with:
|
|
111
|
+
channel-id: ${{ secrets.SLACK_CHANNEL_ID }}
|
|
112
|
+
payload: |
|
|
113
|
+
{
|
|
114
|
+
"text": "Pre-Release Pipeline Status",
|
|
115
|
+
"blocks": [
|
|
116
|
+
{
|
|
117
|
+
"type": "section",
|
|
118
|
+
"text": {
|
|
119
|
+
"type": "mrkdwn",
|
|
120
|
+
"text": "📦 *@rimori/client Pre-Release*\n\n*Branch:* ${{ github.ref_name }}\n*Version:* ${{ steps.version.outputs.new_version }}\n*Author:* ${{ github.actor }}\n*Pipeline:* <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Run>\n\n${{ job.status == 'success' && '✅ Successfully published to npm with @next tag!' || '❌ Pipeline failed. Check the logs for details.' }}"
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
env:
|
|
126
|
+
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
|
package/README.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
The `@rimori/client` package is the framework-agnostic runtime and CLI that powers Rimori plugins. Use it inside plugin iframes, workers, and build scripts to access Rimori platform features such as database access, AI, shared content, and the event bus. All React-specific helpers and UI components are now published separately in `@rimori/react-client`.
|
|
4
4
|
|
|
5
5
|
## Table of Contents
|
|
6
|
+
|
|
6
7
|
- [Overview](#overview)
|
|
7
8
|
- [Installation](#installation)
|
|
8
9
|
- [Relationship to @rimori/react-client](#relationship-to-rimori-react-client)
|
|
@@ -24,6 +25,7 @@ The `@rimori/client` package is the framework-agnostic runtime and CLI that powe
|
|
|
24
25
|
## Overview
|
|
25
26
|
|
|
26
27
|
`@rimori/client` gives you direct, typed access to the Rimori platform:
|
|
28
|
+
|
|
27
29
|
- Bootstrap authenticated plugin sessions and fetch Rimori context.
|
|
28
30
|
- Run Supabase queries against your plugin's dedicated schema.
|
|
29
31
|
- Call AI services for text, structured data, or voice.
|
|
@@ -54,18 +56,15 @@ npm install @rimori/react-client
|
|
|
54
56
|
Instantiate the client once in your application entry point and reuse it everywhere:
|
|
55
57
|
|
|
56
58
|
```ts
|
|
57
|
-
import { RimoriClient } from
|
|
59
|
+
import { RimoriClient } from '@rimori/client';
|
|
58
60
|
|
|
59
61
|
async function bootstrap() {
|
|
60
|
-
const client = await RimoriClient.getInstance(
|
|
62
|
+
const client = await RimoriClient.getInstance('your-plugin-id');
|
|
61
63
|
|
|
62
64
|
const user = client.plugin.getUserInfo();
|
|
63
|
-
const { data } = await client.db
|
|
64
|
-
.from("notes")
|
|
65
|
-
.select("*")
|
|
66
|
-
.eq("user_id", user.profile_id);
|
|
65
|
+
const { data } = await client.db.from('notes').select('*').eq('user_id', user.profile_id);
|
|
67
66
|
|
|
68
|
-
console.log(
|
|
67
|
+
console.log('Loaded notes', data);
|
|
69
68
|
}
|
|
70
69
|
|
|
71
70
|
bootstrap().catch(console.error);
|
|
@@ -127,10 +126,7 @@ Access metadata and settings through `client.plugin`:
|
|
|
127
126
|
`client.db` wraps the Supabase client that is scoped to your plugin tables:
|
|
128
127
|
|
|
129
128
|
```ts
|
|
130
|
-
const { data, error } = await client.db
|
|
131
|
-
.from("study_sessions")
|
|
132
|
-
.select("*")
|
|
133
|
-
.order("completed_at", { ascending: false });
|
|
129
|
+
const { data, error } = await client.db.from('study_sessions').select('*').order('completed_at', { ascending: false });
|
|
134
130
|
```
|
|
135
131
|
|
|
136
132
|
Helpers:
|
|
@@ -196,7 +192,7 @@ Import additional helpers as needed:
|
|
|
196
192
|
All exports are fully typed. You can import the type definitions directly:
|
|
197
193
|
|
|
198
194
|
```ts
|
|
199
|
-
import type { Message, Tool, SharedContent, MacroAccomplishmentPayload } from
|
|
195
|
+
import type { Message, Tool, SharedContent, MacroAccomplishmentPayload } from '@rimori/client';
|
|
200
196
|
```
|
|
201
197
|
|
|
202
198
|
The generated declaration files cover every controller and helper to keep plugins strictly typed.
|
|
@@ -206,7 +202,7 @@ The generated declaration files cover every controller and helper to keep plugin
|
|
|
206
202
|
React users should install `@rimori/react-client` and wrap their app:
|
|
207
203
|
|
|
208
204
|
```tsx
|
|
209
|
-
import { PluginProvider, useRimori, useChat } from
|
|
205
|
+
import { PluginProvider, useRimori, useChat } from '@rimori/react-client';
|
|
210
206
|
|
|
211
207
|
function Dashboard() {
|
|
212
208
|
const client = useRimori();
|
|
File without changes
|
|
File without changes
|
|
@@ -16,6 +16,7 @@ export interface Language {
|
|
|
16
16
|
capitalized: string;
|
|
17
17
|
uppercase: string;
|
|
18
18
|
}
|
|
19
|
+
export type UserRole = 'user' | 'plugin_moderator' | 'lang_moderator' | 'admin';
|
|
19
20
|
export interface UserInfo {
|
|
20
21
|
skill_level_reading: LanguageLevel;
|
|
21
22
|
skill_level_writing: LanguageLevel;
|
|
@@ -29,8 +30,7 @@ export interface UserInfo {
|
|
|
29
30
|
story_genre: string;
|
|
30
31
|
study_duration: number;
|
|
31
32
|
/**
|
|
32
|
-
* The
|
|
33
|
-
* With the function getLanguageName, the language name can be retrieved.
|
|
33
|
+
* The language the user speaks natively.
|
|
34
34
|
*/
|
|
35
35
|
mother_tongue: Language;
|
|
36
36
|
/**
|
|
@@ -49,6 +49,10 @@ export interface UserInfo {
|
|
|
49
49
|
* Optional: nearest big city (>100,000) near user's location
|
|
50
50
|
*/
|
|
51
51
|
target_city?: string;
|
|
52
|
+
/**
|
|
53
|
+
* The user's role: 'user', 'plugin_moderator', 'lang_moderator', or 'admin'
|
|
54
|
+
*/
|
|
55
|
+
user_role: UserRole;
|
|
52
56
|
}
|
|
53
57
|
export declare class SettingsController {
|
|
54
58
|
private pluginId;
|
|
@@ -4,9 +4,11 @@ import { ThirdPartyModule, TOptions } from 'i18next';
|
|
|
4
4
|
*/
|
|
5
5
|
export declare class Translator {
|
|
6
6
|
private currentLanguage;
|
|
7
|
-
private
|
|
7
|
+
private initializationState;
|
|
8
|
+
private initializationPromise;
|
|
8
9
|
private i18n;
|
|
9
|
-
|
|
10
|
+
private translationUrl;
|
|
11
|
+
constructor(initialLanguage: string, translationUrl: string);
|
|
10
12
|
/**
|
|
11
13
|
* Initialize translator with user's language
|
|
12
14
|
* @param userLanguage - Language code from user info
|
|
@@ -12,9 +12,11 @@ import { createInstance } from 'i18next';
|
|
|
12
12
|
* Translator class for handling internationalization
|
|
13
13
|
*/
|
|
14
14
|
export class Translator {
|
|
15
|
-
constructor(initialLanguage) {
|
|
16
|
-
this.isInitialized = false;
|
|
15
|
+
constructor(initialLanguage, translationUrl) {
|
|
17
16
|
this.currentLanguage = initialLanguage;
|
|
17
|
+
this.initializationState = 'not-inited';
|
|
18
|
+
this.initializationPromise = null;
|
|
19
|
+
this.translationUrl = translationUrl;
|
|
18
20
|
}
|
|
19
21
|
/**
|
|
20
22
|
* Initialize translator with user's language
|
|
@@ -22,21 +24,41 @@ export class Translator {
|
|
|
22
24
|
*/
|
|
23
25
|
initialize() {
|
|
24
26
|
return __awaiter(this, void 0, void 0, function* () {
|
|
25
|
-
|
|
27
|
+
// If already finished, return immediately
|
|
28
|
+
if (this.initializationState === 'finished') {
|
|
26
29
|
return;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
30
|
+
}
|
|
31
|
+
// If currently initializing, wait for the existing initialization to complete
|
|
32
|
+
if (this.initializationState === 'initing' && this.initializationPromise) {
|
|
33
|
+
return this.initializationPromise;
|
|
34
|
+
}
|
|
35
|
+
// Start initialization
|
|
36
|
+
this.initializationState = 'initing';
|
|
37
|
+
// Create a promise that will be resolved when initialization completes
|
|
38
|
+
this.initializationPromise = (() => __awaiter(this, void 0, void 0, function* () {
|
|
39
|
+
try {
|
|
40
|
+
const translations = yield this.fetchTranslations(this.currentLanguage);
|
|
41
|
+
const instance = createInstance({
|
|
42
|
+
lng: this.currentLanguage,
|
|
43
|
+
resources: {
|
|
44
|
+
[this.currentLanguage]: {
|
|
45
|
+
translation: translations,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
debug: window.location.hostname === 'localhost',
|
|
49
|
+
});
|
|
50
|
+
yield instance.init();
|
|
51
|
+
this.i18n = instance;
|
|
52
|
+
this.initializationState = 'finished';
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
// Reset state on error so it can be retried
|
|
56
|
+
this.initializationState = 'not-inited';
|
|
57
|
+
this.initializationPromise = null;
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
}))();
|
|
61
|
+
return this.initializationPromise;
|
|
40
62
|
});
|
|
41
63
|
}
|
|
42
64
|
getTranslationUrl(language) {
|
|
@@ -45,7 +67,7 @@ export class Translator {
|
|
|
45
67
|
const filename = language !== 'en' ? `local-${language}` : language;
|
|
46
68
|
return `${window.location.origin}/locales/${filename}.json`;
|
|
47
69
|
}
|
|
48
|
-
return
|
|
70
|
+
return `${this.translationUrl}/locales/${language}.json`;
|
|
49
71
|
}
|
|
50
72
|
usePlugin(plugin) {
|
|
51
73
|
if (!this.i18n) {
|
|
@@ -101,6 +123,6 @@ export class Translator {
|
|
|
101
123
|
* Check if translator is initialized
|
|
102
124
|
*/
|
|
103
125
|
isReady() {
|
|
104
|
-
return this.
|
|
126
|
+
return this.initializationState === 'finished';
|
|
105
127
|
}
|
|
106
128
|
}
|
|
@@ -12,18 +12,18 @@ export class EventBusHandler {
|
|
|
12
12
|
this.listeners = new Map();
|
|
13
13
|
this.responseResolvers = new Map();
|
|
14
14
|
this.debugEnabled = false;
|
|
15
|
-
this.evName =
|
|
15
|
+
this.evName = '';
|
|
16
16
|
//private constructor
|
|
17
17
|
}
|
|
18
18
|
static getInstance(name) {
|
|
19
19
|
if (!EventBusHandler.instance) {
|
|
20
20
|
EventBusHandler.instance = new EventBusHandler();
|
|
21
|
-
EventBusHandler.instance.on(
|
|
21
|
+
EventBusHandler.instance.on('global.system.requestDebug', () => {
|
|
22
22
|
EventBusHandler.instance.debugEnabled = true;
|
|
23
23
|
console.log(`[${EventBusHandler.instance.evName}] Debug mode enabled. Make sure debugging messages are enabled in the browser console.`);
|
|
24
24
|
});
|
|
25
25
|
}
|
|
26
|
-
if (name && EventBusHandler.instance.evName ===
|
|
26
|
+
if (name && EventBusHandler.instance.evName === '') {
|
|
27
27
|
EventBusHandler.instance.evName = name;
|
|
28
28
|
}
|
|
29
29
|
return EventBusHandler.instance;
|
|
@@ -67,7 +67,7 @@ export class EventBusHandler {
|
|
|
67
67
|
}
|
|
68
68
|
const event = this.createEvent(sender, topic, data, eventId);
|
|
69
69
|
const handlers = this.getMatchingHandlers(event.topic);
|
|
70
|
-
handlers.forEach(handler => {
|
|
70
|
+
handlers.forEach((handler) => {
|
|
71
71
|
if (handler.ignoreSender && handler.ignoreSender.includes(sender)) {
|
|
72
72
|
// console.log("ignore event as its in the ignoreSender list", { event, ignoreList: handler.ignoreSender });
|
|
73
73
|
return;
|
|
@@ -93,7 +93,7 @@ export class EventBusHandler {
|
|
|
93
93
|
* @returns An EventListener object containing an off() method to unsubscribe the listeners.
|
|
94
94
|
*/
|
|
95
95
|
on(topics, handler, ignoreSender = []) {
|
|
96
|
-
const ids = this.toArray(topics).map(topic => {
|
|
96
|
+
const ids = this.toArray(topics).map((topic) => {
|
|
97
97
|
this.logIfDebug(`Subscribing to ` + topic, { ignoreSender });
|
|
98
98
|
if (!this.validateTopic(topic)) {
|
|
99
99
|
this.logAndThrowError(true, `Invalid topic: ` + topic);
|
|
@@ -102,14 +102,31 @@ export class EventBusHandler {
|
|
|
102
102
|
this.listeners.set(topic, new Set());
|
|
103
103
|
}
|
|
104
104
|
const id = Math.floor(Math.random() * 10000000000);
|
|
105
|
-
//
|
|
106
|
-
const
|
|
105
|
+
// To prevent infinite loops and processing the same eventId multiple times
|
|
106
|
+
const blackListedEventIds = [];
|
|
107
|
+
const eventHandler = (data) => {
|
|
108
|
+
if (blackListedEventIds.some((item) => item.eventId === data.eventId && item.sender === data.sender)) {
|
|
109
|
+
console.log('BLACKLISTED EVENT ID', data.eventId, data);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
blackListedEventIds.push({
|
|
113
|
+
eventId: data.eventId,
|
|
114
|
+
sender: data.sender,
|
|
115
|
+
});
|
|
116
|
+
if (blackListedEventIds.length > 100) {
|
|
117
|
+
blackListedEventIds.shift();
|
|
118
|
+
}
|
|
119
|
+
return handler(data);
|
|
120
|
+
};
|
|
107
121
|
this.listeners.get(topic).add({ id, handler: eventHandler, ignoreSender });
|
|
108
|
-
this.logIfDebug(`Subscribed to ` + topic, {
|
|
122
|
+
this.logIfDebug(`Subscribed to ` + topic, {
|
|
123
|
+
listenerId: id,
|
|
124
|
+
ignoreSender,
|
|
125
|
+
});
|
|
109
126
|
return btoa(JSON.stringify({ topic, id }));
|
|
110
127
|
});
|
|
111
128
|
return {
|
|
112
|
-
off: () => this.off(ids)
|
|
129
|
+
off: () => this.off(ids),
|
|
113
130
|
};
|
|
114
131
|
}
|
|
115
132
|
/**
|
|
@@ -121,29 +138,29 @@ export class EventBusHandler {
|
|
|
121
138
|
*/
|
|
122
139
|
respond(sender, topic, handler) {
|
|
123
140
|
const topics = Array.isArray(topic) ? topic : [topic];
|
|
124
|
-
const listeners = topics.map(topic => {
|
|
141
|
+
const listeners = topics.map((topic) => {
|
|
125
142
|
const blackListedEventIds = [];
|
|
126
143
|
//To allow event communication inside the same plugin the sender needs to be ignored but the events still need to be checked for the same event just reaching the subscriber to prevent infinite loops
|
|
127
|
-
const finalIgnoreSender = !topic.startsWith(
|
|
144
|
+
const finalIgnoreSender = !topic.startsWith('self.') ? [sender] : [];
|
|
128
145
|
const listener = this.on(topic, (data) => __awaiter(this, void 0, void 0, function* () {
|
|
129
146
|
if (blackListedEventIds.includes(data.eventId)) {
|
|
130
147
|
// console.log("BLACKLISTED EVENT ID", data.eventId);
|
|
131
148
|
return;
|
|
132
149
|
}
|
|
133
150
|
blackListedEventIds.push(data.eventId);
|
|
134
|
-
if (blackListedEventIds.length >
|
|
151
|
+
if (blackListedEventIds.length > 100) {
|
|
135
152
|
blackListedEventIds.shift();
|
|
136
153
|
}
|
|
137
|
-
const response = typeof handler ===
|
|
154
|
+
const response = typeof handler === 'function' ? yield handler(data) : handler;
|
|
138
155
|
this.emit(sender, topic, response, data.eventId);
|
|
139
156
|
}), finalIgnoreSender);
|
|
140
|
-
this.logIfDebug(`Added respond listener ` + sender +
|
|
157
|
+
this.logIfDebug(`Added respond listener ` + sender + ' to topic ' + topic, { listener, sender });
|
|
141
158
|
return {
|
|
142
|
-
off: () => listener.off()
|
|
159
|
+
off: () => listener.off(),
|
|
143
160
|
};
|
|
144
161
|
});
|
|
145
162
|
return {
|
|
146
|
-
off: () => listeners.forEach(listener => listener.off())
|
|
163
|
+
off: () => listeners.forEach((listener) => listener.off()),
|
|
147
164
|
};
|
|
148
165
|
}
|
|
149
166
|
/**
|
|
@@ -156,7 +173,7 @@ export class EventBusHandler {
|
|
|
156
173
|
this.logAndThrowError(false, `Invalid topic: ` + topic);
|
|
157
174
|
return;
|
|
158
175
|
}
|
|
159
|
-
let listener;
|
|
176
|
+
let listener = undefined;
|
|
160
177
|
const wrapper = (event) => {
|
|
161
178
|
handler(event);
|
|
162
179
|
listener === null || listener === void 0 ? void 0 : listener.off();
|
|
@@ -169,13 +186,16 @@ export class EventBusHandler {
|
|
|
169
186
|
* @param listenerIds - The ids of the listeners to unsubscribe from.
|
|
170
187
|
*/
|
|
171
188
|
off(listenerIds) {
|
|
172
|
-
this.toArray(listenerIds).forEach(fullId => {
|
|
189
|
+
this.toArray(listenerIds).forEach((fullId) => {
|
|
173
190
|
const { topic, id } = JSON.parse(atob(fullId));
|
|
174
191
|
const listeners = this.listeners.get(topic) || new Set();
|
|
175
|
-
listeners.forEach(listener => {
|
|
192
|
+
listeners.forEach((listener) => {
|
|
176
193
|
if (listener.id === Number(id)) {
|
|
177
194
|
listeners.delete(listener);
|
|
178
|
-
this.logIfDebug(`Removed listener ` + fullId, {
|
|
195
|
+
this.logIfDebug(`Removed listener ` + fullId, {
|
|
196
|
+
topic,
|
|
197
|
+
listenerId: id,
|
|
198
|
+
});
|
|
179
199
|
}
|
|
180
200
|
});
|
|
181
201
|
});
|
|
@@ -197,7 +217,7 @@ export class EventBusHandler {
|
|
|
197
217
|
}
|
|
198
218
|
const event = this.createEvent(sender, topic, data || {});
|
|
199
219
|
this.logIfDebug(`Requesting data from ` + topic, { event });
|
|
200
|
-
return new Promise(resolve => {
|
|
220
|
+
return new Promise((resolve) => {
|
|
201
221
|
this.responseResolvers.set(event.eventId, (value) => resolve(value));
|
|
202
222
|
this.emitInternal(sender, topic, data || {}, event.eventId, true);
|
|
203
223
|
});
|
|
@@ -212,7 +232,7 @@ export class EventBusHandler {
|
|
|
212
232
|
const exact = this.listeners.get(topic) || new Set();
|
|
213
233
|
// Find wildcard matches
|
|
214
234
|
const wildcard = [...this.listeners.entries()]
|
|
215
|
-
.filter(([key]) => key.endsWith(
|
|
235
|
+
.filter(([key]) => key.endsWith('*') && topic.startsWith(key.slice(0, -1)))
|
|
216
236
|
.flatMap(([_, handlers]) => [...handlers]);
|
|
217
237
|
return new Set([...exact, ...wildcard]);
|
|
218
238
|
}
|
|
@@ -223,27 +243,27 @@ export class EventBusHandler {
|
|
|
223
243
|
*/
|
|
224
244
|
validateTopic(topic) {
|
|
225
245
|
// Split event type into parts
|
|
226
|
-
const parts = topic.split(
|
|
246
|
+
const parts = topic.split('.');
|
|
227
247
|
const [plugin, area, action] = parts;
|
|
228
248
|
if (parts.length !== 3) {
|
|
229
|
-
if (parts.length === 1 && plugin ===
|
|
249
|
+
if (parts.length === 1 && plugin === '*') {
|
|
230
250
|
return true;
|
|
231
251
|
}
|
|
232
|
-
if (parts.length === 2 && plugin !==
|
|
252
|
+
if (parts.length === 2 && plugin !== '*' && area === '*') {
|
|
233
253
|
return true;
|
|
234
254
|
}
|
|
235
255
|
this.logAndThrowError(false, `Event type must have 3 parts separated by dots. Received: ` + topic);
|
|
236
256
|
return false;
|
|
237
257
|
}
|
|
238
|
-
if (action ===
|
|
258
|
+
if (action === '*') {
|
|
239
259
|
return true;
|
|
240
260
|
}
|
|
241
261
|
// Validate action part
|
|
242
|
-
const validActions = [
|
|
243
|
-
if (validActions.some(a => action.startsWith(a))) {
|
|
262
|
+
const validActions = ['request', 'create', 'update', 'delete', 'trigger'];
|
|
263
|
+
if (validActions.some((a) => action.startsWith(a))) {
|
|
244
264
|
return true;
|
|
245
265
|
}
|
|
246
|
-
this.logAndThrowError(false, `Invalid event topic name. The action: ` + action +
|
|
266
|
+
this.logAndThrowError(false, `Invalid event topic name. The action: ` + action + '. Must be or start with one of: ' + validActions.join(', '));
|
|
247
267
|
return false;
|
|
248
268
|
}
|
|
249
269
|
logIfDebug(...args) {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export type Plugin<T extends
|
|
1
|
+
export type Plugin<T extends object = object> = Omit<RimoriPluginConfig<T>, 'context_menu_actions'> & {
|
|
2
2
|
version: string;
|
|
3
3
|
endpoint: string;
|
|
4
4
|
assetEndpoint: string;
|
|
5
5
|
context_menu_actions: MenuEntry[];
|
|
6
|
-
release_channel:
|
|
6
|
+
release_channel: 'alpha' | 'beta' | 'stable';
|
|
7
7
|
};
|
|
8
8
|
export type ActivePlugin = Plugin<{
|
|
9
9
|
active?: boolean;
|
|
@@ -14,7 +14,7 @@ export interface PluginPage {
|
|
|
14
14
|
url: string;
|
|
15
15
|
show: boolean;
|
|
16
16
|
description: string;
|
|
17
|
-
root:
|
|
17
|
+
root: 'vocabulary' | 'grammar' | 'reading' | 'listening' | 'watching' | 'writing' | 'speaking' | 'other' | 'community';
|
|
18
18
|
action?: {
|
|
19
19
|
key: string;
|
|
20
20
|
parameters: ObjectTool;
|
|
@@ -46,7 +46,7 @@ export interface ContextMenuAction {
|
|
|
46
46
|
* Rimori plugin structure representing the complete configuration
|
|
47
47
|
* of a Rimori plugin with all metadata and configuration options.
|
|
48
48
|
*/
|
|
49
|
-
export interface RimoriPluginConfig<T extends
|
|
49
|
+
export interface RimoriPluginConfig<T extends object = object> {
|
|
50
50
|
id: string;
|
|
51
51
|
/**
|
|
52
52
|
* Basic information about the plugin including branding and core details.
|
|
@@ -79,7 +79,7 @@ export interface RimoriPluginConfig<T extends {} = {}> {
|
|
|
79
79
|
/**
|
|
80
80
|
* Context menu actions that the plugin registers to appear in right-click menus throughout the application.
|
|
81
81
|
*/
|
|
82
|
-
context_menu_actions: Omit<MenuEntry,
|
|
82
|
+
context_menu_actions: Omit<MenuEntry, 'plugin_id'>[];
|
|
83
83
|
/**
|
|
84
84
|
* Documentation paths for different types of plugin documentation.
|
|
85
85
|
*/
|
|
@@ -107,7 +107,7 @@ export interface Tool {
|
|
|
107
107
|
parameters: {
|
|
108
108
|
name: string;
|
|
109
109
|
description: string;
|
|
110
|
-
type:
|
|
110
|
+
type: 'string' | 'number' | 'boolean';
|
|
111
111
|
}[];
|
|
112
112
|
execute?: (args: Record<string, any>) => Promise<unknown> | unknown | void;
|
|
113
113
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -11,7 +11,8 @@ export { Translator } from './controller/TranslationController';
|
|
|
11
11
|
export type { TOptions } from 'i18next';
|
|
12
12
|
export type { SharedContent, SharedContentObjectRequest } from './controller/SharedContentController';
|
|
13
13
|
export type { Exercise } from './controller/ExerciseController';
|
|
14
|
-
export type { UserInfo, Language } from './controller/SettingsController';
|
|
14
|
+
export type { UserInfo, Language, UserRole } from './controller/SettingsController';
|
|
15
15
|
export type { Message, ToolInvocation } from './controller/AIController';
|
|
16
16
|
export type { TriggerAction } from './controller/ExerciseController';
|
|
17
17
|
export type { MacroAccomplishmentPayload, MicroAccomplishmentPayload } from './controller/AccomplishmentController';
|
|
18
|
+
export type { EventBusMessage } from './fromRimori/EventBus';
|
|
@@ -27,6 +27,17 @@ export interface RimoriInfo {
|
|
|
27
27
|
mainPanelPlugin?: ActivePlugin;
|
|
28
28
|
sidePanelPlugin?: ActivePlugin;
|
|
29
29
|
interfaceLanguage: string;
|
|
30
|
+
/**
|
|
31
|
+
* The release channel of the plugin installation.
|
|
32
|
+
*/
|
|
33
|
+
releaseChannel: 'alpha' | 'beta' | 'stable';
|
|
34
|
+
/**
|
|
35
|
+
* The database schema to use for plugin tables.
|
|
36
|
+
* Determined by rimori-main based on release channel:
|
|
37
|
+
* - 'plugins_alpha' for alpha release channel
|
|
38
|
+
* - 'plugins' for beta and stable release channels
|
|
39
|
+
*/
|
|
40
|
+
dbSchema: 'plugins' | 'plugins_alpha';
|
|
30
41
|
}
|
|
31
42
|
export declare class RimoriCommunicationHandler {
|
|
32
43
|
private port;
|
|
@@ -26,15 +26,17 @@ export class RimoriCommunicationHandler {
|
|
|
26
26
|
}
|
|
27
27
|
initMessageChannel(worker = false) {
|
|
28
28
|
const listener = (event) => {
|
|
29
|
-
console.log('[PluginController] window message', { origin: event.origin, data: event.data });
|
|
29
|
+
// console.log('[PluginController] window message', { origin: event.origin, data: event.data });
|
|
30
30
|
const { type, pluginId, queryParams, rimoriInfo } = event.data || {};
|
|
31
31
|
const [transferredPort] = event.ports || [];
|
|
32
32
|
if (type !== 'rimori:init' || !transferredPort || pluginId !== this.pluginId) {
|
|
33
|
-
console.log('[PluginController] message ignored (not init or wrong plugin)', {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
// console.log('[PluginController] message ignored (not init or wrong plugin)', {
|
|
34
|
+
// type,
|
|
35
|
+
// pluginId,
|
|
36
|
+
// currentPluginId: this.pluginId,
|
|
37
|
+
// hasPortProperty: !!transferredPort,
|
|
38
|
+
// event
|
|
39
|
+
// });
|
|
38
40
|
return;
|
|
39
41
|
}
|
|
40
42
|
this.queryParams = queryParams || {};
|
|
@@ -182,7 +184,7 @@ export class RimoriCommunicationHandler {
|
|
|
182
184
|
else {
|
|
183
185
|
// In main thread context, use EventBus
|
|
184
186
|
const { data } = yield EventBus.request(this.pluginId, 'global.supabase.requestAccess');
|
|
185
|
-
console.log({ data });
|
|
187
|
+
// console.log({ data });
|
|
186
188
|
this.rimoriInfo = data;
|
|
187
189
|
this.supabase = createClient(this.rimoriInfo.url, this.rimoriInfo.key, {
|
|
188
190
|
accessToken: () => Promise.resolve(this.getToken()),
|
package/dist/plugin/Logger.d.ts
CHANGED
|
@@ -54,6 +54,7 @@ export declare class Logger {
|
|
|
54
54
|
private getBrowserInfo;
|
|
55
55
|
/**
|
|
56
56
|
* Capture a screenshot of the current page.
|
|
57
|
+
* Dynamically imports html2canvas only in browser environments.
|
|
57
58
|
* @returns Promise resolving to base64 screenshot or null if failed
|
|
58
59
|
*/
|
|
59
60
|
private captureScreenshot;
|