@arkeytyp/valu-api 1.0.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.
@@ -0,0 +1,5 @@
1
+ bug:
2
+ title: "^fix:.*"
3
+
4
+ enhancement:
5
+ title: "^feat:.*"
@@ -0,0 +1,16 @@
1
+ ## Purpose of this PR
2
+ <!-- Why do you submit this PR? -->
3
+
4
+ ## Testing status
5
+ <!-- Remove the options that do not apply -->
6
+ * No tests have been added.
7
+ * Includes unit tests.
8
+ * Includes performance tests.
9
+ * Includes integration tests.
10
+
11
+ ### Manual testing status
12
+ <!-- Describe how you tested your implementation/fix. Try to mention all the cases and flows. -->
13
+ <!-- It will help the reviewer to understand if you missed any cases or test steps as well as will tell more about your feature or fix. -->
14
+
15
+ ## Comments to reviewers
16
+ <!-- Notes for the reviewers you have assigned. Remove if not applicable-->
@@ -0,0 +1,12 @@
1
+ name-template: '$NEXT_PATCH_VERSION'
2
+ tag-template: '$NEXT_PATCH_VERSION'
3
+ categories:
4
+ - title: 'New'
5
+ labels:
6
+ - 'enhancement'
7
+ - title: 'Fixes'
8
+ labels:
9
+ - 'bug'
10
+ template: |
11
+ ## Changes
12
+ $CHANGES
@@ -0,0 +1,14 @@
1
+ name: Label PRs
2
+
3
+ on:
4
+ - pull_request
5
+
6
+ jobs:
7
+ build:
8
+
9
+ runs-on: ubuntu-latest
10
+
11
+ steps:
12
+ - uses: srvaroa/labeler@master
13
+ env:
14
+ GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
@@ -0,0 +1,18 @@
1
+ name: Release Drafter
2
+
3
+ on:
4
+ push:
5
+ # branches to consider in the event; optional, defaults to all
6
+ branches:
7
+ - master
8
+
9
+ jobs:
10
+ update_release_draft:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ # Drafts your next Release notes as Pull Requests are merged into "master"
14
+ - uses: release-drafter/release-drafter@v5
15
+ with:
16
+ config-name: release-drafter-config.yml
17
+ env:
18
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,28 @@
1
+ name: Publish to npm
2
+
3
+ # Controls when the action will run.
4
+ # In this case I'm saying on each release event when it's specifically a new release publish, the types: [published] is required here,
5
+ # since releases could also be updated or deleted, we only want to publish to npm when a new release is created (published).
6
+ on:
7
+ release:
8
+ types: [published, edited]
9
+
10
+ jobs:
11
+ build:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
15
+ - uses: actions/checkout@v2
16
+ #Install Node.js, with the version 12 and using the registry URL of npm, this could be changed to a custom registry or the GitHub registry.
17
+ - uses: actions/setup-node@v1
18
+ with:
19
+ node-version: 12
20
+ registry-url: https://registry.npmjs.org/
21
+
22
+ # Command to install the package dependencies
23
+ # - run: yarn install
24
+
25
+ # Publish to npm
26
+ - run: npm publish --access public
27
+ env:
28
+ NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
@@ -0,0 +1,13 @@
1
+ name: Assign PR to creator
2
+
3
+ on: [pull_request]
4
+
5
+ jobs:
6
+ automation:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - name: Assign PR to creator
10
+ uses: thomaseizinger/assign-pr-creator-action@v1.0.0
11
+ if: github.event_name == 'pull_request' && github.event.action == 'opened'
12
+ with:
13
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,15 @@
1
+ name: "PR Title Convention Validation"
2
+ on:
3
+ pull_request:
4
+ types:
5
+ - opened
6
+ - edited
7
+ - synchronize
8
+
9
+ jobs:
10
+ main:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: amannn/action-semantic-pull-request@v1.1.1
14
+ env:
15
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,117 @@
1
+ name: Draft Release Modifications
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+ branches: [master]
8
+ types: [closed]
9
+
10
+ env:
11
+ GITHUB_USER_EMAIL: noreply@stansassets.com
12
+ GITHUB_USER_NAME: Stan's Assets Automation
13
+ GITHUB_API_TOKEN: ${{ secrets.GH_API_TOKEN }}
14
+ GITHUB_API_CALLER: StansAssets
15
+ LATEST_RELEASE_TAG: 0.0.0
16
+ LATEST_RELEASE_DRAFT_STATUS: false
17
+
18
+ GET_LATEST_RELEASE_INFO: |
19
+ URL="https://api.github.com/repos/${{ github.repository }}/releases"
20
+ LATEST_RELEASE_TAG=$(curl -u $GITHUB_API_CALLER:$GITHUB_API_TOKEN -s GET $URL | jq '.[0]' | jq -r '.tag_name')
21
+ LATEST_RELEASE_DRAFT_STATUS=$(curl -u $GITHUB_API_CALLER:$GITHUB_API_TOKEN -s GET $URL | jq '.[0]' | jq -r '.draft')
22
+
23
+ CHANGE_RELEASE_TAG_BY_ONE: |
24
+ IFS='.' read -ra TAG_ARRAY <<< "$LATEST_RELEASE_TAG"
25
+ for ((idx=0; idx<${#TAG_ARRAY[@]}; ++idx)); do
26
+ if [[ $(($idx+1)) == ${#TAG_ARRAY[@]} ]]
27
+ then
28
+ RELEASE_VERSION+="$((${TAG_ARRAY[idx]}+1))"
29
+ else
30
+ RELEASE_VERSION+=${TAG_ARRAY[idx]}.
31
+ fi
32
+ done
33
+ LATEST_RELEASE_TAG=$RELEASE_VERSION
34
+
35
+ CREATE_NEW_DRAFT_RELEASE: |
36
+ URL="https://api.github.com/repos/${{ github.repository }}/releases"
37
+ API_JSON=$(echo "{\"tag_name\": \"$LATEST_RELEASE_TAG\",
38
+ \"target_commitish\": \"master\",
39
+ \"name\": \"$LATEST_RELEASE_TAG\",
40
+ \"body\": \"\",
41
+ \"draft\": true,
42
+ \"prerelease\": false}")
43
+ curl -u $GITHUB_API_CALLER:$GITHUB_API_TOKEN -s -d "$API_JSON" -X POST $URL
44
+
45
+ MODIFY_PACKAGE_JSON: |
46
+ git config --global user.email "${GITHUB_USER_EMAIL}"
47
+ git config --global user.name "${GITHUB_USER_NAME}"
48
+ jq ".version=\"$LATEST_RELEASE_TAG\"" package.json > "tmp" && mv "tmp" package.json
49
+ git add -A
50
+ git status
51
+ git commit -m "Version Changed"
52
+ git push origin HEAD:master
53
+
54
+ ADD_PULL_REQUEST_TITLE_CONTENTS: |
55
+ URL="https://api.github.com/repos/${{ github.repository }}/releases"
56
+ LATEST_RELEASE_BODY=$(curl -u $GITHUB_API_CALLER:$GITHUB_API_TOKEN -s GET $URL | jq '.[0]' | jq -r '.body' | sed -E ':a;N;$!ba;s/\r{0,1}\n/\\n/g')
57
+ LATEST_RELEASE_ID=$(curl -u $GITHUB_API_CALLER:$GITHUB_API_TOKEN -s GET $URL | jq '.[0]' | jq -r '.id')
58
+ ADDED_DELIMITER="### Added"
59
+ FIXED_DELIMITER="### Fixed"
60
+ if [[ "${{ github.event.pull_request.title }}" == *"feat:"* ]]; then
61
+ if [[ $LATEST_RELEASE_BODY == *$ADDED_DELIMITER* ]]; then
62
+ if [[ $LATEST_RELEASE_BODY == *$FIXED_DELIMITER* ]]; then
63
+ FIRST_PART="${LATEST_RELEASE_BODY%"$FIXED_DELIMITER"*}"
64
+ LAST_PART="${LATEST_RELEASE_BODY#*"$FIXED_DELIMITER"}"
65
+ BODY_CONTENTS="${FIRST_PART}* ${{ github.event.pull_request.title }}\n${FIXED_DELIMITER}${LAST_PART}"
66
+ else
67
+ BODY_CONTENTS="${LATEST_RELEASE_BODY}\n* ${{ github.event.pull_request.title }}"
68
+ fi
69
+ else
70
+ BODY_CONTENTS="${ADDED_DELIMITER}\n* ${{ github.event.pull_request.title }}\n${LATEST_RELEASE_BODY}"
71
+ fi
72
+ elif [[ "${{ github.event.pull_request.title }}" == *"fix:"* ]]; then
73
+ if [[ $LATEST_RELEASE_BODY == *$FIXED_DELIMITER* ]]; then
74
+ BODY_CONTENTS="${LATEST_RELEASE_BODY}\n* ${{ github.event.pull_request.title }}"
75
+ else
76
+ BODY_CONTENTS="${LATEST_RELEASE_BODY}\n${FIXED_DELIMITER}\n* ${{ github.event.pull_request.title }}"
77
+ fi
78
+ fi
79
+ URL="https://api.github.com/repos/${{ github.repository }}/releases/${LATEST_RELEASE_ID}"
80
+ API_JSON=$(echo "{\"tag_name\": \"$LATEST_RELEASE_TAG\",
81
+ \"target_commitish\": \"master\",
82
+ \"name\": \"$LATEST_RELEASE_TAG\",
83
+ \"body\": \"$BODY_CONTENTS\",
84
+ \"draft\": true,
85
+ \"prerelease\": false}")
86
+ curl -u $GITHUB_API_CALLER:$GITHUB_API_TOKEN -s -d "$API_JSON" -X POST $URL
87
+
88
+ jobs:
89
+ PushModifications:
90
+ name: Modifications on Push
91
+ if: ( github.event_name == 'push' )
92
+ runs-on: ubuntu-latest
93
+ steps:
94
+ - uses: actions/checkout@v2
95
+ - name: Get latest release info. Create and modify if needed
96
+ run: |
97
+ eval "$GET_LATEST_RELEASE_INFO"
98
+ if [[ $LATEST_RELEASE_DRAFT_STATUS == false ]]; then
99
+ eval "$CHANGE_RELEASE_TAG_BY_ONE"
100
+ eval "$CREATE_NEW_DRAFT_RELEASE"
101
+ eval "$MODIFY_PACKAGE_JSON"
102
+ fi
103
+ PullRequestModifications:
104
+ name: Modifications on Pull Request
105
+ if: ( github.event_name == 'pull_request' )
106
+ runs-on: ubuntu-latest
107
+ steps:
108
+ - uses: actions/checkout@v2
109
+ - name: Add Pull Request title contents to release
110
+ run: |
111
+ eval "$GET_LATEST_RELEASE_INFO"
112
+ if [[ $LATEST_RELEASE_DRAFT_STATUS == false ]]; then
113
+ eval "$CHANGE_RELEASE_TAG_BY_ONE"
114
+ eval "$CREATE_NEW_DRAFT_RELEASE"
115
+ eval "$MODIFY_PACKAGE_JSON"
116
+ fi
117
+ eval "$ADD_PULL_REQUEST_TITLE_CONTENTS"
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Arkeytyp
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,99 @@
1
+ The `valu-api` package enables developers to create custom iframe applications for Valu Social. It provides tools to invoke functions of registered Valu applications and subscribe to their events. With features like API versioning, event handling, and console command testing, developers can seamlessly integrate and extend functionality within the Valu Social ecosystem.
2
+
3
+ # Usage
4
+
5
+ ## 1. Initialize ValuApi
6
+ On your application startup, create an instance of ValuApi and subscribe to the API_READY event. This event will be triggered only when your application is launched as an iframe within the Valu Verse application.
7
+
8
+ ```javascript
9
+ import { ValuApi } from 'valu-api';
10
+
11
+ const valuApi = new ValuApi();
12
+ valuApi.current.addEventListener(ValuApi.API_READY, async (e) => {
13
+ console.log("API IS READY!!!");
14
+ });
15
+ ```
16
+
17
+ ## 2. Get an API Pointer
18
+ Once the API is ready, you can create an APIPointer instance by specifying the name and version of the API you want to use. The version parameter is optional, and if omitted, the latest version will be used.
19
+
20
+ To get a pointer to the app API version 1:
21
+
22
+ ```javascript
23
+ const appApi = valuApi.getApi('app', 1);
24
+ ```
25
+
26
+ To use the latest version of the API:
27
+
28
+ ```javascript
29
+ const appApi = valuApi.current.getApi('app');
30
+ ```
31
+
32
+ ## 3. Invoke API Commands
33
+ After obtaining the API pointer, you can invoke commands on the API. Here's an example of opening the text_chat on the app API:
34
+
35
+ ```javascript
36
+ await appApi.run('open', 'text_chat');
37
+ ```
38
+
39
+ For interacting with other APIs, like the chat API:
40
+
41
+ ```javascript
42
+ const chatApi = valuApi.current.getApi('chat');
43
+ await chatApi.run('open-channel', { roomId: 'room123', propId: 'prop456' });
44
+ ```
45
+
46
+ ## 4. Subscribe to Events
47
+ You can subscribe to events emitted by the API. For example, if you want to listen for the app-open event:
48
+
49
+ ```javascript
50
+ appApi.addEventListener('app-open', (event) => {
51
+ console.log(event);
52
+ });
53
+ ```
54
+
55
+ ## 5. Run Console Commands (For Testing)
56
+ You can use the runConsoleCommand method to execute commands directly in the console environment. This method processes the output and returns a resolved promise on success or an error message if the command fails.
57
+
58
+ To run a console command, use:
59
+
60
+ ```javascript
61
+ let command = 'app open text_chat';
62
+ let reply = await valuApi.current.runConsoleCommand(command);
63
+ console.log(reply);
64
+
65
+ command = 'chat open-channel roomId xz21wd31tx83kk propId 812t26xbq5424b';
66
+ reply = await valuApi.current.runConsoleCommand(command);
67
+ console.log(reply);
68
+ ```
69
+
70
+ ## Example Workflow
71
+ Here's an example of a simple workflow using valu-api:
72
+
73
+ ```javascript
74
+ import { ValuApi } from 'valu-api';
75
+
76
+ const valuApi = new ValuApi();
77
+
78
+ // Wait for the API to be ready
79
+ valuApi.current.addEventListener(ValuApi.API_READY, async () => {
80
+ console.log("API IS READY!!!");
81
+
82
+ // Get API pointer
83
+ const appApi = valuApi.current.getApi('app');
84
+
85
+ // Run a command on the app API
86
+ await appApi.run('open', 'text_chat');
87
+
88
+ // Subscribe to events
89
+ appApi.addEventListener('app-open', (event) => {
90
+ console.log('App opened:', event);
91
+ });
92
+
93
+ // Run console command for testing
94
+ let command = 'app open text_chat';
95
+ let reply = await valuApi.current.runConsoleCommand(command);
96
+ console.log(reply);
97
+ });
98
+ ```
99
+
package/package.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "@arkeytyp/valu-api",
3
+ "version": "1.0.0",
4
+ "description": "A package for developing iframe applications for Valu Social. Allows invoking functions of registered Valu applications and subscribing to events, as well as other features that enable developers creating own ifram apps for the value social",
5
+ "main": "ValuApi.js",
6
+ "scripts": {
7
+ "test": "echo \"No test specified\" && exit 1"
8
+ },
9
+ "keywords": ["valu", "api", "iframe", "valu social", "multiverse"],
10
+ "author": "Stanislav Osipov",
11
+ "license": "MIT",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/arkeytyp/valu-api.git"
15
+ }
16
+ }
@@ -0,0 +1,110 @@
1
+ import {EventEmitter} from "./EventEmitter.js";
2
+ import {guid4, nextId} from "./Utils.js";
3
+
4
+ /**
5
+ * An access point to a specified API based on the provided API name and version.
6
+ * It simplifies interaction with APIs by dynamically resolving the appropriate versioned endpoints or features.
7
+ */
8
+ export class APIPointer {
9
+
10
+ #apiName;
11
+ #version;
12
+ #guid;
13
+ #eventEmitter;
14
+ #runRequest
15
+
16
+ #apiCalls = new Map();
17
+
18
+
19
+ /**
20
+ * @private
21
+ * @description Unique identifier (GUID) for the API Pointer object.
22
+ * Used for communication between your and valu app.
23
+ * @type {string}
24
+ */
25
+ get guid () {
26
+ return this.#guid;
27
+ }
28
+
29
+ /**
30
+ * @private
31
+ * @description Stores the API name.
32
+ * @type {string}
33
+ */
34
+ get apiName () {
35
+ return this.#apiName;
36
+ }
37
+
38
+ /**
39
+ * @private
40
+ * @description Stores the version of the API.
41
+ * @type {string}
42
+ */
43
+ get version () {
44
+ return this.#version;
45
+ }
46
+
47
+ constructor(apiName, version, runRequest) {
48
+ this.#apiName = apiName
49
+ this.#version = version;
50
+ this.#eventEmitter = new EventEmitter();
51
+ this.#guid = guid4();
52
+ this.#runRequest = runRequest;
53
+ }
54
+
55
+ /**
56
+ * Use to subscribe to the API events.
57
+ */
58
+ addEventListener = (...parameters) => this.#eventEmitter.addEventListener(...parameters);
59
+
60
+ /**
61
+ * Removes API event subscription.
62
+ */
63
+ removeEventListener = (...parameters) => this.#eventEmitter.removeEventListener(...parameters);
64
+
65
+ /**
66
+ * Executes and api request.
67
+ *
68
+ * @param {string} functionName - The name of the function to execute.
69
+ * @param {Object} params - The parameters required for the function execution.
70
+ * @private
71
+ */
72
+ run(functionName, params) {
73
+ let deferredPromise = this.#createDeferred();
74
+ this.#apiCalls[deferredPromise.id] = deferredPromise;
75
+ this.#runRequest(functionName, params, deferredPromise.id, this);
76
+
77
+ return deferredPromise.promise;
78
+ }
79
+
80
+
81
+ postRunResult(requestId, result) {
82
+ let deferred = this.#apiCalls[requestId];
83
+ if(!deferred) {
84
+ console.error(`Failed to postRunResult for ${requestId}`);
85
+ return;
86
+ }
87
+
88
+ if(result?.error) {
89
+ deferred.reject(result.error);
90
+ } else {
91
+ deferred.resolve(result);
92
+ }
93
+
94
+ delete this.#apiCalls[requestId];
95
+ }
96
+
97
+ /**
98
+ * Creates a deferred object with a promise and its resolve/reject functions.
99
+ * @returns {object} A deferred object.
100
+ */
101
+ #createDeferred() {
102
+ let resolve, reject;
103
+ const promise = new Promise((res, rej) => {
104
+ resolve = res;
105
+ reject = rej;
106
+ });
107
+
108
+ return { id: nextId(), promise, resolve, reject };
109
+ }
110
+ }
@@ -0,0 +1,86 @@
1
+ /** @module EventEmitter */
2
+
3
+ /**
4
+ * @class EventEmitter
5
+ *
6
+ * @property {function(eventName: string, listener: Function, once?: boolean=false): EventEmitter} addEventListener
7
+ * @property {function(eventName: string, listener: Function): EventEmitter} removeEventListener
8
+ * @property {function(eventName: string): EventEmitter} emit
9
+ */
10
+ export class EventEmitter {
11
+ #events = [];
12
+
13
+ get events() {
14
+ return this.#events;
15
+ }
16
+
17
+ get addEventListener() {
18
+ return (eventName, callback, once = false) => {
19
+ const listeners = this.#events[eventName] || [];
20
+
21
+ listeners.push({
22
+ callback,
23
+ once
24
+ });
25
+ this.#events[eventName] = listeners;
26
+
27
+ return this;
28
+ };
29
+ }
30
+
31
+ get removeEventListener() {
32
+ return (eventName = false, callback = false) => {
33
+ if (!eventName) {
34
+ this.#events = {};
35
+ } else if (!callback) {
36
+ this.#events[eventName] = [];
37
+ } else {
38
+ const listeners = this.#events[eventName] || [];
39
+
40
+ for (let i = listeners.length - 1; i >= 0; i--) {
41
+ if (listeners[i].callback === callback) listeners.splice(i, 1);
42
+ }
43
+ }
44
+
45
+ return this;
46
+ };
47
+ }
48
+
49
+ get emit() {
50
+ return (eventName, ...parameters) => {
51
+ const listeners = this.#events[eventName] || [];
52
+ const onceListeners = [];
53
+ const specialEvent = ['__before__', '__after__'].indexOf(eventName) !== -1;
54
+
55
+ let returnValue, lastValue;
56
+
57
+ specialEvent || this.emit.apply(this, ['__before__', eventName].concat(parameters));
58
+
59
+ for (let i = 0, length = listeners.length; i < length; i++) {
60
+ const listener = listeners[i];
61
+
62
+ if (listener === undefined) {
63
+ console.error('Error: incorrect event behaviour!');
64
+ return;
65
+ }
66
+
67
+ if (listener.callback) {
68
+ lastValue = listener.callback.apply(this, parameters);
69
+ } else {
70
+ listener.once = true;
71
+ }
72
+
73
+ if (listener.once) onceListeners.push(i);
74
+ if (lastValue !== undefined) returnValue = lastValue;
75
+ }
76
+
77
+ for (let i = onceListeners.length - 1; i >= 0; i--) {
78
+ listeners.splice(onceListeners[i], 1);
79
+ }
80
+
81
+ specialEvent || this.emit.apply(this, ['__after__', eventName].concat(parameters));
82
+
83
+ return returnValue;
84
+ };
85
+ }
86
+ }
package/src/Utils.js ADDED
@@ -0,0 +1,13 @@
1
+ export const guid4 = () => crypto && crypto.randomUUID
2
+ ? ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
3
+ (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16))
4
+ : "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
5
+ (+c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> +c / 4).toString(16)
6
+ );
7
+
8
+
9
+ let lastId = 0;
10
+ export const nextId =() => {
11
+ lastId++;
12
+ return lastId;
13
+ }
package/src/ValuApi.js ADDED
@@ -0,0 +1,163 @@
1
+ import {EventEmitter} from "./EventEmitter.js";
2
+ import {APIPointer} from "./APIPointer.js";
3
+ import {guid4, nextId} from "@/valu-api/Utils.js";
4
+
5
+
6
+ /**
7
+ * Allows to invoke functions of a registered Valu application and subscribe to its events.
8
+ *
9
+ * More info:
10
+ * https://github.com/Roomful/valu-api
11
+ */
12
+ export class ValuApi {
13
+
14
+ static API_READY = 'api:ready'
15
+
16
+ #eventEmitter;
17
+ #valuApplication = {};
18
+ #apiRequests = {};
19
+ #consoleCommands = new Map();
20
+
21
+
22
+ constructor() {
23
+ globalThis.addEventListener('message', (event) => {
24
+ this.#handleParentMessage(event);
25
+ });
26
+ this.#eventEmitter = new EventEmitter();
27
+ }
28
+
29
+ addEventListener = (...parameters) => this.#eventEmitter.addEventListener(...parameters);
30
+ removeEventListener = (...parameters) => this.#eventEmitter.removeEventListener(...parameters);
31
+
32
+ /**
33
+ * Retrieves an APIPointer object for a specific API module.
34
+ * @param {string} apiName The name of the API module to retrieve.
35
+ * @param {string} [version] The optional version of the API module. If not provided, the APIPointer will be bound to the latest available version.
36
+ * @returns {APIPointer} An APIPointer object bound to the specified API version (or the latest version if no version is specified).
37
+ *
38
+ * The APIPointer object provides the ability to:
39
+ * - Run functions within the specified API module.
40
+ * - Subscribe to events associated with the module.
41
+ *
42
+ * This method enables interaction with a specific version of an API module, allowing users to access its functionality and listen to events.
43
+ */
44
+ getApi(apiName, version) {
45
+ const apiPointer = new APIPointer(apiName, version, (functionName, params, requestId, apiPointer) => {
46
+ this.#onApiRunRequest(functionName, params, requestId, apiPointer);
47
+ });
48
+ this.#registerApiPointer(apiPointer);
49
+ return apiPointer;
50
+ }
51
+
52
+ #registerApiPointer(apiPointer) {
53
+ this.#postToValuApp('api:create-pointer', {
54
+ guid: apiPointer.guid,
55
+ api: apiPointer.apiName,
56
+ version: apiPointer.version,
57
+ });
58
+ }
59
+
60
+ #postToValuApp(name, message) {
61
+ const data = { name: name, message: message};
62
+
63
+ console.log('Posting to Valu: ', name, ' ', message, ' source: ', this.#valuApplication.source);
64
+ this.#valuApplication.source.postMessage(data, this.#valuApplication.origin);
65
+ }
66
+
67
+ async #onApiRunRequest(functionName, params, requestId, apiPointer) {
68
+ console.log('onApiRunRequest', this);
69
+
70
+ this.#apiRequests[requestId] = apiPointer;
71
+
72
+ this.#postToValuApp('api:run', {
73
+ apiPointerId: apiPointer.guid,
74
+ requestId: requestId,
75
+ functionName: functionName,
76
+ params: params,
77
+ });
78
+ }
79
+
80
+ /**
81
+ * Executes a given console command and returns the result of an API function.
82
+ *
83
+ * This method accepts a string command, executes it in the console environment,
84
+ * and processes the output. If the execution is successful, it returns the result
85
+ * of the associated API function as a resolved promise. If the promise fails
86
+ * or an exception occurs during execution, it will return an error message string.
87
+ *
88
+ * @param {string} command - The console command to execute.
89
+ * Example: `/chat -h`.
90
+ * @returns {Promise<any|string>} A promise resolving to the API function result. If the promise
91
+ * fails or throws an exception, it resolves to an error message string.
92
+ */
93
+ async runConsoleCommand(command) {
94
+ let deferredPromise = this.#createDeferred();
95
+ this.#consoleCommands[deferredPromise.id] = deferredPromise;
96
+
97
+ this.#postToValuApp('api:run-console', {
98
+ requestId: deferredPromise.id,
99
+ command: command,
100
+ });
101
+
102
+ return deferredPromise.promise;
103
+ }
104
+
105
+ #createDeferred() {
106
+ let resolve, reject;
107
+ const promise = new Promise((res, rej) => {
108
+ resolve = res;
109
+ reject = rej;
110
+ });
111
+
112
+ return { id: nextId(), promise, resolve, reject };
113
+ }
114
+
115
+ #handleParentMessage(event) {
116
+ if(event.data?.target !== 'valuApi') {
117
+ //console.log('Skipped non valu event: ');
118
+ return;
119
+ }
120
+
121
+ const message = event.data.message;
122
+ console.log('Message From Valu: ', event.data.name, ' ', message);
123
+
124
+ switch (event.data.name) {
125
+ case 'api:ready': {
126
+ this.#valuApplication = {
127
+ id : message,
128
+ source: event.source,
129
+ origin: event.origin,
130
+ }
131
+
132
+ this.#eventEmitter.emit(ValuApi.API_READY);
133
+ break;
134
+ }
135
+
136
+ case 'api:run-console-completed': {
137
+ const requestId = event.data.requestId;
138
+ const deferred = this.#consoleCommands[requestId];
139
+ if(deferred) {
140
+ deferred.resolve(message);
141
+ } else {
142
+ console.log('Failed to locate console request with Id: ', requestId);
143
+ }
144
+
145
+ delete this.#consoleCommands[requestId];
146
+ break;
147
+ }
148
+
149
+ case 'api:run-completed': {
150
+ const requestId = event.data.requestId;
151
+ const apiPointer = this.#apiRequests[requestId];
152
+ if(!apiPointer) {
153
+ console.error(`Failed to find Api Pointer for requestId: ${requestId}`);
154
+ break
155
+ }
156
+
157
+ apiPointer.postRunResult(requestId, message);
158
+ delete this.#apiRequests[requestId];
159
+ break;
160
+ }
161
+ }
162
+ }
163
+ }