@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.
- package/.github/labeler.yml +5 -0
- package/.github/pull_request_template.md +16 -0
- package/.github/release-drafter-config.yml +12 -0
- package/.github/workflows/auto-pr-labels.yml +14 -0
- package/.github/workflows/draft-release.yml +18 -0
- package/.github/workflows/npm.yml +28 -0
- package/.github/workflows/pr-assign-creator.yml +13 -0
- package/.github/workflows/pr-title-convention-validation.yml +15 -0
- package/.github/workflows/release-mods-workflow.yml +117 -0
- package/LICENSE +21 -0
- package/README.md +99 -0
- package/package.json +16 -0
- package/src/APIPointer.js +110 -0
- package/src/EventEmitter.js +86 -0
- package/src/Utils.js +13 -0
- package/src/ValuApi.js +163 -0
|
@@ -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,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
|
+
}
|