Merge branch 'v3.0.x-release' into use-who-is-talking
This commit is contained in:
commit
f4a2af7dc1
30
bbb-export-annotations/package-lock.json
generated
30
bbb-export-annotations/package-lock.json
generated
@ -9,7 +9,7 @@
|
||||
"version": "2.0",
|
||||
"dependencies": {
|
||||
"@svgdotjs/svg.js": "^3.2.0",
|
||||
"axios": "^1.6.5",
|
||||
"axios": "^1.7.2",
|
||||
"form-data": "^4.0.0",
|
||||
"opentype.js": "^1.3.4",
|
||||
"perfect-freehand": "^1.0.16",
|
||||
@ -258,11 +258,11 @@
|
||||
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.6.5",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz",
|
||||
"integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==",
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz",
|
||||
"integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.4",
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
@ -692,9 +692,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.5",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
|
||||
"integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
@ -1633,11 +1633,11 @@
|
||||
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
|
||||
},
|
||||
"axios": {
|
||||
"version": "1.6.5",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz",
|
||||
"integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==",
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz",
|
||||
"integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.15.4",
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
@ -1960,9 +1960,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.15.5",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
|
||||
"integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw=="
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA=="
|
||||
},
|
||||
"fontkit": {
|
||||
"version": "2.0.2",
|
||||
|
@ -9,7 +9,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@svgdotjs/svg.js": "^3.2.0",
|
||||
"axios": "^1.6.5",
|
||||
"axios": "^1.7.2",
|
||||
"form-data": "^4.0.0",
|
||||
"opentype.js": "^1.3.4",
|
||||
"perfect-freehand": "^1.0.16",
|
||||
|
95
bbb-graphql-actions/package-lock.json
generated
95
bbb-graphql-actions/package-lock.json
generated
@ -12,7 +12,7 @@
|
||||
"@types/express": "^4.17.18",
|
||||
"@types/node": "^20.7.0",
|
||||
"@types/redis": "^4.0.11",
|
||||
"axios": "^0.21.1",
|
||||
"axios": "^0.28.0",
|
||||
"express": "^4.19.2",
|
||||
"redis": "^4.6.10"
|
||||
},
|
||||
@ -291,12 +291,19 @@
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "0.21.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
|
||||
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.28.0.tgz",
|
||||
"integrity": "sha512-Tu7NYoGY4Yoc7I+Npf9HhUMtEEpV7ZiLH9yndTCoNhcpBH0kwcvFbzYN9/u5QKI5A6uefjsNNWaz5olJVYS62Q==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.14.0"
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
@ -348,12 +355,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fill-range": "^7.0.1"
|
||||
"fill-range": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@ -420,6 +427,17 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
@ -488,6 +506,14 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
@ -601,9 +627,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
@ -630,9 +656,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz",
|
||||
"integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==",
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
@ -648,6 +674,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
@ -875,18 +914,6 @@
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/make-error": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
||||
@ -1092,6 +1119,11 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"node_modules/pstree.remy": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
|
||||
@ -1184,13 +1216,10 @@
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
||||
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
|
||||
"version": "7.6.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
|
||||
"integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
|
@ -27,7 +27,7 @@
|
||||
"@types/express": "^4.17.18",
|
||||
"@types/node": "^20.7.0",
|
||||
"@types/redis": "^4.0.11",
|
||||
"axios": "^0.21.1",
|
||||
"axios": "^0.28.0",
|
||||
"express": "^4.19.2",
|
||||
"redis": "^4.6.10"
|
||||
},
|
||||
|
1431
bbb-graphql-client-test/package-lock.json
generated
1431
bbb-graphql-client-test/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -9,7 +9,7 @@
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"fast-json-patch": "^3.1.1",
|
||||
"graphql": "^16.6.0",
|
||||
"graphql": "^16.8.1",
|
||||
"graphql-ws": "^5.11.2",
|
||||
"lodash": "^4.17.21",
|
||||
"react": "^18.2.0",
|
||||
|
@ -45,11 +45,14 @@ func main() {
|
||||
log.Infof("Json Patch Disabled!")
|
||||
}
|
||||
|
||||
if rawDataCacheStorageMode := os.Getenv("BBB_GRAPHQL_MIDDLEWARE_RAW_DATA_CACHE_STORAGE_MODE"); rawDataCacheStorageMode == "memory" {
|
||||
msgpatch.RawDataCacheStorageMode = "memory"
|
||||
} else {
|
||||
msgpatch.RawDataCacheStorageMode = "file"
|
||||
}
|
||||
//if rawDataCacheStorageMode := os.Getenv("BBB_GRAPHQL_MIDDLEWARE_RAW_DATA_CACHE_STORAGE_MODE"); rawDataCacheStorageMode == "file" {
|
||||
// msgpatch.RawDataCacheStorageMode = "file"
|
||||
//} else {
|
||||
// msgpatch.RawDataCacheStorageMode = "memory"
|
||||
//}
|
||||
//Force memory cache for now
|
||||
msgpatch.RawDataCacheStorageMode = "memory"
|
||||
|
||||
log.Infof("Raw Data Cache Storage Mode: %s", msgpatch.RawDataCacheStorageMode)
|
||||
|
||||
// Websocket listener
|
||||
|
@ -7,17 +7,17 @@ import (
|
||||
var GlobalCacheLocks = NewCacheLocks()
|
||||
|
||||
type CacheLocks struct {
|
||||
locks map[string]*sync.Mutex
|
||||
locks map[uint32]*sync.Mutex
|
||||
mutex sync.Mutex // Protects the 'locks' map
|
||||
}
|
||||
|
||||
func NewCacheLocks() *CacheLocks {
|
||||
return &CacheLocks{
|
||||
locks: make(map[string]*sync.Mutex),
|
||||
locks: make(map[uint32]*sync.Mutex),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CacheLocks) Lock(id string) {
|
||||
func (c *CacheLocks) Lock(id uint32) {
|
||||
c.mutex.Lock()
|
||||
if _, exists := c.locks[id]; !exists {
|
||||
c.locks[id] = &sync.Mutex{}
|
||||
@ -28,7 +28,7 @@ func (c *CacheLocks) Lock(id string) {
|
||||
mtx.Lock() // Lock the specific ID mutex
|
||||
}
|
||||
|
||||
func (c *CacheLocks) Unlock(id string) {
|
||||
func (c *CacheLocks) Unlock(id uint32) {
|
||||
c.mutex.Lock()
|
||||
if mtx, exists := c.locks[id]; exists {
|
||||
mtx.Unlock()
|
||||
|
@ -1,6 +1,7 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
evanphxjsonpatch "github.com/evanphx/json-patch"
|
||||
@ -8,46 +9,51 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func ValidateIfShouldUseCustomJsonPatch(original []byte, modified []byte, idFieldName string) bool {
|
||||
func ValidateIfShouldUseCustomJsonPatch(original []byte, modified []byte, idFieldName string) (bool, []byte) {
|
||||
//Temporarily use CustomPatch only for UserList (testing feature)
|
||||
if !bytes.Contains(modified, []byte("\"__typename\":\"user\"}]")) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
//Test Original Data
|
||||
originalMap := GetMapFromByte(original)
|
||||
if originalMap == nil {
|
||||
return false
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if len(originalMap) <= 1 {
|
||||
return false
|
||||
return false, nil
|
||||
}
|
||||
|
||||
firstItem := originalMap[0]
|
||||
if _, existsIdField := firstItem[idFieldName].(string); !existsIdField {
|
||||
return false
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if hasDuplicatedId(originalMap, idFieldName) {
|
||||
return false
|
||||
return false, nil
|
||||
}
|
||||
|
||||
//Test Modified Data
|
||||
modifiedMap := GetMapFromByte(modified)
|
||||
if modifiedMap == nil {
|
||||
return false
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if len(modifiedMap) <= 1 {
|
||||
return false
|
||||
return false, nil
|
||||
}
|
||||
|
||||
firstItem = modifiedMap[0]
|
||||
if _, existsIdField := firstItem[idFieldName].(string); !existsIdField {
|
||||
return false
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if hasDuplicatedId(modifiedMap, idFieldName) {
|
||||
return false
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true
|
||||
return true, CreateJsonPatchFromMaps(originalMap, modifiedMap, modified, "userId")
|
||||
}
|
||||
|
||||
func hasDuplicatedId(items []map[string]interface{}, idFieldName string) bool {
|
||||
@ -67,12 +73,10 @@ func CreateJsonPatch(original []byte, modified []byte, idFieldName string) []byt
|
||||
originalMap := GetMapFromByte(original)
|
||||
modifiedMap := GetMapFromByte(modified)
|
||||
|
||||
return CreateJsonPatchFromMaps(originalMap, modifiedMap, idFieldName)
|
||||
return CreateJsonPatchFromMaps(originalMap, modifiedMap, modified, idFieldName)
|
||||
}
|
||||
|
||||
func CreateJsonPatchFromMaps(original []map[string]interface{}, modified []map[string]interface{}, idFieldName string) []byte {
|
||||
modifiedJson, _ := json.Marshal(modified)
|
||||
|
||||
func CreateJsonPatchFromMaps(original []map[string]interface{}, modified []map[string]interface{}, modifiedJson []byte, idFieldName string) []byte {
|
||||
//CREATE PATCHES FOR OPERATION "REPLACE"
|
||||
replacesPatches, originalWithReplaces := CreateReplacePatches(original, modified, idFieldName)
|
||||
|
||||
|
@ -16,31 +16,93 @@ func GetUniqueID() string {
|
||||
return uniqueID
|
||||
}
|
||||
|
||||
var JsonPatchCache = make(map[string][]byte)
|
||||
var JsonPatchCacheMutex sync.RWMutex
|
||||
var PatchedMessageCache = make(map[uint32][]byte)
|
||||
var PatchedMessageCacheMutex sync.RWMutex
|
||||
|
||||
func GetJsonPatchCache(cacheKey string) ([]byte, bool) {
|
||||
JsonPatchCacheMutex.RLock()
|
||||
defer JsonPatchCacheMutex.RUnlock()
|
||||
func GetPatchedMessageCache(cacheKey uint32) ([]byte, bool) {
|
||||
PatchedMessageCacheMutex.RLock()
|
||||
defer PatchedMessageCacheMutex.RUnlock()
|
||||
|
||||
jsonDiffPatch, jsonDiffPatchExists := JsonPatchCache[cacheKey]
|
||||
jsonDiffPatch, jsonDiffPatchExists := PatchedMessageCache[cacheKey]
|
||||
return jsonDiffPatch, jsonDiffPatchExists
|
||||
}
|
||||
|
||||
func StoreJsonPatchCache(cacheKey string, data []byte) {
|
||||
JsonPatchCacheMutex.Lock()
|
||||
defer JsonPatchCacheMutex.Unlock()
|
||||
func StorePatchedMessageCache(cacheKey uint32, data []byte) {
|
||||
PatchedMessageCacheMutex.Lock()
|
||||
defer PatchedMessageCacheMutex.Unlock()
|
||||
|
||||
JsonPatchCache[cacheKey] = data
|
||||
PatchedMessageCache[cacheKey] = data
|
||||
|
||||
//Remove the cache after 30 seconds
|
||||
go RemoveJsonPatchCache(cacheKey, 30)
|
||||
go RemovePatchedMessageCache(cacheKey, 30)
|
||||
}
|
||||
|
||||
func RemoveJsonPatchCache(cacheKey string, delayInSecs time.Duration) {
|
||||
func RemovePatchedMessageCache(cacheKey uint32, delayInSecs time.Duration) {
|
||||
time.Sleep(delayInSecs * time.Second)
|
||||
|
||||
JsonPatchCacheMutex.Lock()
|
||||
defer JsonPatchCacheMutex.Unlock()
|
||||
delete(JsonPatchCache, cacheKey)
|
||||
PatchedMessageCacheMutex.Lock()
|
||||
defer PatchedMessageCacheMutex.Unlock()
|
||||
delete(PatchedMessageCache, cacheKey)
|
||||
}
|
||||
|
||||
var HasuraMessageCache = make(map[uint32]HasuraMessage)
|
||||
var HasuraMessageKeyCache = make(map[uint32]string)
|
||||
var HasuraMessageCacheMutex sync.RWMutex
|
||||
|
||||
func GetHasuraMessageCache(cacheKey uint32) (string, HasuraMessage, bool) {
|
||||
HasuraMessageCacheMutex.RLock()
|
||||
defer HasuraMessageCacheMutex.RUnlock()
|
||||
|
||||
hasuraMessageDataKey, _ := HasuraMessageKeyCache[cacheKey]
|
||||
hasuraMessage, hasuraMessageExists := HasuraMessageCache[cacheKey]
|
||||
return hasuraMessageDataKey, hasuraMessage, hasuraMessageExists
|
||||
}
|
||||
|
||||
func StoreHasuraMessageCache(cacheKey uint32, dataKey string, hasuraMessage HasuraMessage) {
|
||||
HasuraMessageCacheMutex.Lock()
|
||||
defer HasuraMessageCacheMutex.Unlock()
|
||||
|
||||
HasuraMessageKeyCache[cacheKey] = dataKey
|
||||
HasuraMessageCache[cacheKey] = hasuraMessage
|
||||
|
||||
//Remove the cache after 30 seconds
|
||||
go RemoveHasuraMessageCache(cacheKey, 30)
|
||||
}
|
||||
|
||||
func RemoveHasuraMessageCache(cacheKey uint32, delayInSecs time.Duration) {
|
||||
time.Sleep(delayInSecs * time.Second)
|
||||
|
||||
HasuraMessageCacheMutex.Lock()
|
||||
defer HasuraMessageCacheMutex.Unlock()
|
||||
delete(HasuraMessageKeyCache, cacheKey)
|
||||
delete(HasuraMessageCache, cacheKey)
|
||||
}
|
||||
|
||||
var StreamCursorValueCache = make(map[uint32]interface{})
|
||||
var StreamCursorValueCacheMutex sync.RWMutex
|
||||
|
||||
func GetStreamCursorValueCache(cacheKey uint32) (interface{}, bool) {
|
||||
StreamCursorValueCacheMutex.RLock()
|
||||
defer StreamCursorValueCacheMutex.RUnlock()
|
||||
|
||||
streamCursorValue, streamCursorValueExists := StreamCursorValueCache[cacheKey]
|
||||
return streamCursorValue, streamCursorValueExists
|
||||
}
|
||||
|
||||
func StoreStreamCursorValueCache(cacheKey uint32, streamCursorValue interface{}) {
|
||||
StreamCursorValueCacheMutex.Lock()
|
||||
defer StreamCursorValueCacheMutex.Unlock()
|
||||
|
||||
StreamCursorValueCache[cacheKey] = streamCursorValue
|
||||
|
||||
//Remove the cache after 30 seconds
|
||||
go RemoveStreamCursorValueCache(cacheKey, 30)
|
||||
}
|
||||
|
||||
func RemoveStreamCursorValueCache(cacheKey uint32, delayInSecs time.Duration) {
|
||||
time.Sleep(delayInSecs * time.Second)
|
||||
|
||||
StreamCursorValueCacheMutex.Lock()
|
||||
defer StreamCursorValueCacheMutex.Unlock()
|
||||
delete(StreamCursorValueCache, cacheKey)
|
||||
}
|
||||
|
73
bbb-graphql-middleware/internal/common/SafeChannelByte.go
Normal file
73
bbb-graphql-middleware/internal/common/SafeChannelByte.go
Normal file
@ -0,0 +1,73 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
type SafeChannelByte struct {
|
||||
ch chan []byte
|
||||
closed bool
|
||||
mux sync.Mutex
|
||||
freezeFlag bool
|
||||
}
|
||||
|
||||
func NewSafeChannelByte(size int) *SafeChannelByte {
|
||||
return &SafeChannelByte{
|
||||
ch: make(chan []byte, size),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SafeChannelByte) Send(value []byte) bool {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
if s.closed {
|
||||
return false
|
||||
}
|
||||
s.ch <- value
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *SafeChannelByte) Receive() ([]byte, bool) {
|
||||
val, ok := <-s.ch
|
||||
return val, ok
|
||||
}
|
||||
|
||||
func (s *SafeChannelByte) ReceiveChannel() <-chan []byte {
|
||||
return s.ch
|
||||
}
|
||||
|
||||
func (s *SafeChannelByte) Closed() bool {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
return s.closed
|
||||
}
|
||||
|
||||
func (s *SafeChannelByte) Close() {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
if !s.closed {
|
||||
close(s.ch)
|
||||
s.closed = true
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SafeChannelByte) Frozen() bool {
|
||||
return s.freezeFlag
|
||||
}
|
||||
|
||||
func (s *SafeChannelByte) FreezeChannel() {
|
||||
if !s.freezeFlag {
|
||||
s.mux.Lock()
|
||||
s.freezeFlag = true
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SafeChannelByte) UnfreezeChannel() {
|
||||
if s.freezeFlag {
|
||||
s.mux.Unlock()
|
||||
s.freezeFlag = false
|
||||
}
|
||||
}
|
@ -1,27 +1,28 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"hash/crc32"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetStreamCursorPropsFromQuery(payload map[string]interface{}, query string) (string, string, interface{}) {
|
||||
func GetStreamCursorPropsFromBrowserMessage(browserMessage BrowserSubscribeMessage) (string, string, interface{}) {
|
||||
streamCursorField := ""
|
||||
streamCursorVariableName := ""
|
||||
var streamCursorInitialValue interface{}
|
||||
|
||||
cursorInitialValueRePattern := regexp.MustCompile(`cursor:\s*\{\s*initial_value\s*:\s*\{\s*([^:]+):\s*([^}]+)\s*}\s*}`)
|
||||
matches := cursorInitialValueRePattern.FindStringSubmatch(query)
|
||||
matches := cursorInitialValueRePattern.FindStringSubmatch(browserMessage.Payload.Query)
|
||||
if matches != nil {
|
||||
streamCursorField = matches[1]
|
||||
if strings.HasPrefix(matches[2], "$") {
|
||||
streamCursorVariableName, _ = strings.CutPrefix(matches[2], "$")
|
||||
if variables, okVariables := payload["variables"].(map[string]interface{}); okVariables {
|
||||
if targetVariableValue, okTargetVariableValue := variables[streamCursorVariableName]; okTargetVariableValue {
|
||||
streamCursorInitialValue = targetVariableValue
|
||||
}
|
||||
if targetVariableValue, okTargetVariableValue := browserMessage.Payload.Variables[streamCursorVariableName]; okTargetVariableValue {
|
||||
streamCursorInitialValue = targetVariableValue
|
||||
}
|
||||
} else {
|
||||
streamCursorInitialValue = matches[2]
|
||||
@ -31,9 +32,28 @@ func GetStreamCursorPropsFromQuery(payload map[string]interface{}, query string)
|
||||
return streamCursorField, streamCursorVariableName, streamCursorInitialValue
|
||||
}
|
||||
|
||||
func GetLastStreamCursorValueFromReceivedMessage(messageAsMap map[string]interface{}, streamCursorField string) interface{} {
|
||||
func GetLastStreamCursorValueFromReceivedMessage(message []byte, streamCursorField string) interface{} {
|
||||
dataChecksum := crc32.ChecksumIEEE(message)
|
||||
GlobalCacheLocks.Lock(dataChecksum)
|
||||
|
||||
if streamCursorValueCache, streamCursorValueCacheExists := GetStreamCursorValueCache(dataChecksum); streamCursorValueCacheExists {
|
||||
//Unlock immediately once the cache was already created by other routine
|
||||
GlobalCacheLocks.Unlock(dataChecksum)
|
||||
return streamCursorValueCache
|
||||
} else {
|
||||
//It will create the cache and then Unlock (others will wait to benefit from this cache)
|
||||
defer GlobalCacheLocks.Unlock(dataChecksum)
|
||||
}
|
||||
|
||||
var lastStreamCursorValue interface{}
|
||||
|
||||
var messageAsMap map[string]interface{}
|
||||
err := json.Unmarshal(message, &messageAsMap)
|
||||
if err != nil {
|
||||
log.Errorf("failed to unmarshal message: %v", err)
|
||||
//return
|
||||
}
|
||||
|
||||
if payload, okPayload := messageAsMap["payload"].(map[string]interface{}); okPayload {
|
||||
if data, okData := payload["data"].(map[string]interface{}); okData {
|
||||
//Data will have only one prop, `range` because its name is unknown
|
||||
@ -52,6 +72,7 @@ func GetLastStreamCursorValueFromReceivedMessage(messageAsMap map[string]interfa
|
||||
}
|
||||
}
|
||||
|
||||
StoreStreamCursorValueCache(dataChecksum, lastStreamCursorValue)
|
||||
return lastStreamCursorValue
|
||||
}
|
||||
|
||||
@ -60,76 +81,84 @@ func PatchQueryIncludingCursorField(originalQuery string, cursorField string) st
|
||||
return originalQuery
|
||||
}
|
||||
|
||||
lastIndex := strings.LastIndex(originalQuery, "{")
|
||||
if lastIndex == -1 {
|
||||
lastIndexOfTypename := LastButOneIndex(originalQuery, "}")
|
||||
if lastIndexOfTypename == -1 {
|
||||
return originalQuery
|
||||
}
|
||||
|
||||
// It will include the cursorField at the beginning of the list of fields
|
||||
// It will include the cursorField at the end of the list of fields
|
||||
// It's not a problem if the field be duplicated in the list, Hasura just ignore the second occurrence
|
||||
return originalQuery[:lastIndex+1] + "\n " + cursorField + originalQuery[lastIndex+1:]
|
||||
return originalQuery[:lastIndexOfTypename] + " " + cursorField + "\n " + originalQuery[lastIndexOfTypename:]
|
||||
}
|
||||
|
||||
func PatchQuerySettingLastCursorValue(subscription GraphQlSubscription) interface{} {
|
||||
message := subscription.Message
|
||||
payload, okPayload := message["payload"].(map[string]interface{})
|
||||
|
||||
if okPayload {
|
||||
if subscription.StreamCursorVariableName != "" {
|
||||
/**** This stream has its cursor value set through variables ****/
|
||||
if variables, okVariables := payload["variables"].(map[string]interface{}); okVariables {
|
||||
if variables[subscription.StreamCursorVariableName] != subscription.StreamCursorCurrValue {
|
||||
variables[subscription.StreamCursorVariableName] = subscription.StreamCursorCurrValue
|
||||
payload["variables"] = variables
|
||||
message["payload"] = payload
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/**** This stream has its cursor value set through inline value (not variables) ****/
|
||||
query, okQuery := payload["query"].(string)
|
||||
if okQuery {
|
||||
cursorInitialValueRePattern := regexp.MustCompile(`cursor:\s*\{\s*initial_value\s*:\s*\{\s*([^:]+:\s*[^}]+)\s*}\s*}`)
|
||||
newValue := ""
|
||||
|
||||
replaceInitialValueFunc := func(match string) string {
|
||||
switch v := subscription.StreamCursorCurrValue.(type) {
|
||||
case string:
|
||||
newValue = v
|
||||
|
||||
//Append quotes if it is missing, it will be necessary when appending to the query
|
||||
if !strings.HasPrefix(v, "\"") {
|
||||
newValue = "\"" + newValue
|
||||
}
|
||||
if !strings.HasSuffix(v, "\"") {
|
||||
newValue = newValue + "\""
|
||||
}
|
||||
case int:
|
||||
newValue = strconv.Itoa(v)
|
||||
case float32:
|
||||
myFloat64 := float64(v)
|
||||
newValue = strconv.FormatFloat(myFloat64, 'f', -1, 32)
|
||||
case float64:
|
||||
newValue = strconv.FormatFloat(v, 'f', -1, 64)
|
||||
default:
|
||||
newValue = ""
|
||||
}
|
||||
|
||||
if newValue != "" {
|
||||
replacement := subscription.StreamCursorField + ": " + newValue
|
||||
return fmt.Sprintf("cursor: {initial_value: {%s}}", replacement)
|
||||
} else {
|
||||
return match
|
||||
}
|
||||
}
|
||||
|
||||
newQuery := cursorInitialValueRePattern.ReplaceAllStringFunc(query, replaceInitialValueFunc)
|
||||
if query != newQuery {
|
||||
payload["query"] = newQuery
|
||||
message["payload"] = payload
|
||||
}
|
||||
}
|
||||
}
|
||||
func PatchQuerySettingLastCursorValue(subscription GraphQlSubscription) []byte {
|
||||
var browserMessage BrowserSubscribeMessage
|
||||
err := json.Unmarshal(subscription.Message, &browserMessage)
|
||||
if err != nil {
|
||||
log.Errorf("failed to unmarshal message: %v", err)
|
||||
return subscription.Message
|
||||
}
|
||||
|
||||
return message
|
||||
if subscription.StreamCursorVariableName != "" {
|
||||
/**** This stream has its cursor value set through variables ****/
|
||||
if browserMessage.Payload.Variables[subscription.StreamCursorVariableName] == subscription.StreamCursorCurrValue {
|
||||
return subscription.Message
|
||||
}
|
||||
browserMessage.Payload.Variables[subscription.StreamCursorVariableName] = subscription.StreamCursorCurrValue
|
||||
} else {
|
||||
/**** This stream has its cursor value set through inline value (not variables) ****/
|
||||
cursorInitialValueRePattern := regexp.MustCompile(`cursor:\s*\{\s*initial_value\s*:\s*\{\s*([^:]+:\s*[^}]+)\s*}\s*}`)
|
||||
newValue := ""
|
||||
|
||||
replaceInitialValueFunc := func(match string) string {
|
||||
switch v := subscription.StreamCursorCurrValue.(type) {
|
||||
case string:
|
||||
newValue = v
|
||||
|
||||
//Append quotes if it is missing, it will be necessary when appending to the query
|
||||
if !strings.HasPrefix(v, "\"") {
|
||||
newValue = "\"" + newValue
|
||||
}
|
||||
if !strings.HasSuffix(v, "\"") {
|
||||
newValue = newValue + "\""
|
||||
}
|
||||
case int:
|
||||
newValue = strconv.Itoa(v)
|
||||
case float32:
|
||||
myFloat64 := float64(v)
|
||||
newValue = strconv.FormatFloat(myFloat64, 'f', -1, 32)
|
||||
case float64:
|
||||
newValue = strconv.FormatFloat(v, 'f', -1, 64)
|
||||
default:
|
||||
newValue = ""
|
||||
}
|
||||
|
||||
if newValue != "" {
|
||||
replacement := subscription.StreamCursorField + ": " + newValue
|
||||
return fmt.Sprintf("cursor: {initial_value: {%s}}", replacement)
|
||||
} else {
|
||||
return match
|
||||
}
|
||||
}
|
||||
|
||||
newQuery := cursorInitialValueRePattern.ReplaceAllStringFunc(browserMessage.Payload.Query, replaceInitialValueFunc)
|
||||
if browserMessage.Payload.Query == newQuery {
|
||||
return subscription.Message
|
||||
}
|
||||
|
||||
browserMessage.Payload.Query = newQuery
|
||||
}
|
||||
|
||||
newMessageJson, _ := json.Marshal(browserMessage)
|
||||
|
||||
return newMessageJson
|
||||
}
|
||||
|
||||
func LastButOneIndex(s, substr string) int {
|
||||
last := strings.LastIndex(s, substr)
|
||||
if last == -1 {
|
||||
return -1
|
||||
}
|
||||
|
||||
return strings.LastIndex(s[:last], substr)
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
@ -20,13 +21,13 @@ const (
|
||||
|
||||
type GraphQlSubscription struct {
|
||||
Id string
|
||||
Message map[string]interface{}
|
||||
Message []byte
|
||||
Type QueryType
|
||||
OperationName string
|
||||
StreamCursorField string
|
||||
StreamCursorVariableName string
|
||||
StreamCursorCurrValue interface{}
|
||||
LastReceivedData []byte
|
||||
LastReceivedData HasuraMessage
|
||||
LastReceivedDataChecksum uint32
|
||||
JsonPatchSupported bool // indicate if client support Json Patch for this subscription
|
||||
LastSeenOnHasuraConnection string // id of the hasura connection that this query was active
|
||||
@ -45,7 +46,7 @@ type BrowserConnection struct {
|
||||
BrowserRequestCookies []*http.Cookie
|
||||
ActiveSubscriptions map[string]GraphQlSubscription // active subscriptions of this connection (start, but no stop)
|
||||
ActiveSubscriptionsMutex sync.RWMutex // mutex to control the map usage
|
||||
ConnectionInitMessage map[string]interface{} // init message received in this connection (to be used on hasura reconnect)
|
||||
ConnectionInitMessage []byte // init message received in this connection (to be used on hasura reconnect)
|
||||
HasuraConnection *HasuraConnection // associated hasura connection
|
||||
Disconnected bool // indicate if the connection is gone
|
||||
ConnAckSentToBrowser bool // indicate if `connection_ack` msg was already sent to the browser
|
||||
@ -62,3 +63,22 @@ type HasuraConnection struct {
|
||||
ContextCancelFunc context.CancelFunc // function to cancel the hasura context (and so, the hasura connection)
|
||||
FreezeMsgFromBrowserChan *SafeChannel // indicate that it's waiting for the return of mutations before closing connection
|
||||
}
|
||||
|
||||
type HasuraMessage struct {
|
||||
Type string `json:"type"`
|
||||
ID string `json:"id"`
|
||||
Payload struct {
|
||||
Data map[string]json.RawMessage `json:"data"`
|
||||
} `json:"payload"`
|
||||
}
|
||||
|
||||
type BrowserSubscribeMessage struct {
|
||||
Type string `json:"type"`
|
||||
ID string `json:"id"`
|
||||
Payload struct {
|
||||
Extensions map[string]interface{} `json:"extensions"`
|
||||
OperationName string `json:"operationName"`
|
||||
Query string `json:"query"`
|
||||
Variables map[string]interface{} `json:"variables"`
|
||||
} `json:"payload"`
|
||||
}
|
||||
|
@ -17,8 +17,8 @@ var graphqlActionsUrl = os.Getenv("BBB_GRAPHQL_MIDDLEWARE_GRAPHQL_ACTIONS_URL")
|
||||
|
||||
func GraphqlActionsClient(
|
||||
browserConnection *common.BrowserConnection,
|
||||
fromBrowserToGqlActionsChannel *common.SafeChannel,
|
||||
fromHasuraToBrowserChannel *common.SafeChannel) error {
|
||||
fromBrowserToGqlActionsChannel *common.SafeChannelByte,
|
||||
fromHasuraToBrowserChannel *common.SafeChannelByte) error {
|
||||
|
||||
log := log.WithField("_routine", "GraphqlActionsClient").WithField("browserConnectionId", browserConnection.Id)
|
||||
log.Debug("Starting GraphqlActionsClient")
|
||||
@ -38,19 +38,19 @@ RangeLoop:
|
||||
continue
|
||||
}
|
||||
|
||||
var fromBrowserMessageAsMap = fromBrowserMessage.(map[string]interface{})
|
||||
|
||||
if fromBrowserMessageAsMap["type"] == "subscribe" {
|
||||
queryId := fromBrowserMessageAsMap["id"].(string)
|
||||
payload := fromBrowserMessageAsMap["payload"].(map[string]interface{})
|
||||
var browserMessage common.BrowserSubscribeMessage
|
||||
err := json.Unmarshal(fromBrowserMessage, &browserMessage)
|
||||
if err != nil {
|
||||
log.Errorf("failed to unmarshal message: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if browserMessage.Type == "subscribe" {
|
||||
var errorMessage string
|
||||
var mutationFuncName string
|
||||
|
||||
query, okQuery := payload["query"].(string)
|
||||
variables, okVariables := payload["variables"].(map[string]interface{})
|
||||
if okQuery && okVariables && strings.HasPrefix(query, "mutation") {
|
||||
if funcName, inputs, err := parseGraphQLMutation(query, variables); err == nil {
|
||||
if strings.HasPrefix(browserMessage.Payload.Query, "mutation") {
|
||||
if funcName, inputs, err := parseGraphQLMutation(browserMessage.Payload.Query, browserMessage.Payload.Variables); err == nil {
|
||||
mutationFuncName = funcName
|
||||
if err = SendGqlActionsRequest(funcName, inputs, browserConnection.BBBWebSessionVariables); err == nil {
|
||||
} else {
|
||||
@ -66,7 +66,7 @@ RangeLoop:
|
||||
if errorMessage != "" {
|
||||
//Error on sending action, return error msg to client
|
||||
browserResponseData := map[string]interface{}{
|
||||
"id": queryId,
|
||||
"id": browserMessage.ID,
|
||||
"type": "error",
|
||||
"payload": []interface{}{
|
||||
map[string]interface{}{
|
||||
@ -74,12 +74,12 @@ RangeLoop:
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fromHasuraToBrowserChannel.Send(browserResponseData)
|
||||
jsonData, _ := json.Marshal(browserResponseData)
|
||||
fromHasuraToBrowserChannel.Send(jsonData)
|
||||
} else {
|
||||
//Action sent successfully, return data msg to client
|
||||
browserResponseData := map[string]interface{}{
|
||||
"id": queryId,
|
||||
"id": browserMessage.ID,
|
||||
"type": "next",
|
||||
"payload": map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
@ -87,15 +87,17 @@ RangeLoop:
|
||||
},
|
||||
},
|
||||
}
|
||||
fromHasuraToBrowserChannel.Send(browserResponseData)
|
||||
jsonData, _ := json.Marshal(browserResponseData)
|
||||
fromHasuraToBrowserChannel.Send(jsonData)
|
||||
}
|
||||
|
||||
//Return complete msg to client
|
||||
browserResponseComplete := map[string]interface{}{
|
||||
"id": queryId,
|
||||
"id": browserMessage.ID,
|
||||
"type": "complete",
|
||||
}
|
||||
fromHasuraToBrowserChannel.Send(browserResponseComplete)
|
||||
jsonData, _ := json.Marshal(browserResponseComplete)
|
||||
fromHasuraToBrowserChannel.Send(jsonData)
|
||||
}
|
||||
|
||||
//Fallback to Hasura was disabled (keeping the code temporarily)
|
||||
|
@ -24,8 +24,8 @@ var hasuraEndpoint = os.Getenv("BBB_GRAPHQL_MIDDLEWARE_HASURA_WS")
|
||||
// Hasura client connection
|
||||
func HasuraClient(
|
||||
browserConnection *common.BrowserConnection,
|
||||
fromBrowserToHasuraChannel *common.SafeChannel,
|
||||
fromHasuraToBrowserChannel *common.SafeChannel) error {
|
||||
fromBrowserToHasuraChannel *common.SafeChannelByte,
|
||||
fromHasuraToBrowserChannel *common.SafeChannelByte) error {
|
||||
log := log.WithField("_routine", "HasuraClient").WithField("browserConnectionId", browserConnection.Id)
|
||||
common.ActivitiesOverviewStarted("__HasuraConnection")
|
||||
defer common.ActivitiesOverviewCompleted("__HasuraConnection")
|
||||
|
@ -1,26 +1,21 @@
|
||||
package reader
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/iMDT/bbb-graphql-middleware/internal/common"
|
||||
"github.com/iMDT/bbb-graphql-middleware/internal/hasura/retransmiter"
|
||||
"github.com/iMDT/bbb-graphql-middleware/internal/msgpatch"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"hash/crc32"
|
||||
"nhooyr.io/websocket"
|
||||
"nhooyr.io/websocket/wsjson"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// HasuraConnectionReader consumes messages from Hasura connection and add send to the browser channel
|
||||
func HasuraConnectionReader(
|
||||
hc *common.HasuraConnection,
|
||||
fromHasuraToBrowserChannel *common.SafeChannel,
|
||||
fromBrowserToHasuraChannel *common.SafeChannel,
|
||||
wg *sync.WaitGroup) {
|
||||
func HasuraConnectionReader(hc *common.HasuraConnection, fromHasuraToBrowserChannel *common.SafeChannelByte, fromBrowserToHasuraChannel *common.SafeChannelByte, wg *sync.WaitGroup) {
|
||||
log := log.WithField("_routine", "HasuraConnectionReader").WithField("browserConnectionId", hc.BrowserConn.Id).WithField("hasuraConnectionId", hc.Id)
|
||||
defer log.Debugf("finished")
|
||||
log.Debugf("starting")
|
||||
@ -29,10 +24,7 @@ func HasuraConnectionReader(
|
||||
defer hc.ContextCancelFunc()
|
||||
|
||||
for {
|
||||
// Read a message from hasura
|
||||
var message interface{}
|
||||
err := wsjson.Read(hc.Context, hc.Websocket, &message)
|
||||
|
||||
messageType, message, err := hc.Websocket.Read(hc.Context)
|
||||
var closeError *websocket.CloseError
|
||||
|
||||
if err != nil {
|
||||
@ -58,113 +50,134 @@ func HasuraConnectionReader(
|
||||
return
|
||||
}
|
||||
|
||||
if messageType != websocket.MessageText {
|
||||
log.Warnf("received non-text message: %v", messageType)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Tracef("received from hasura: %v", message)
|
||||
|
||||
handleMessageReceivedFromHasura(hc, fromHasuraToBrowserChannel, fromBrowserToHasuraChannel, message)
|
||||
}
|
||||
}
|
||||
|
||||
func handleMessageReceivedFromHasura(hc *common.HasuraConnection, fromHasuraToBrowserChannel *common.SafeChannel, fromBrowserToHasuraChannel *common.SafeChannel, message interface{}) {
|
||||
var messageMap = message.(map[string]interface{})
|
||||
var QueryIdPlaceholderInBytes = []byte("--------------QUERY-ID--------------") //36 chars
|
||||
|
||||
if messageMap != nil {
|
||||
var messageType = messageMap["type"]
|
||||
var queryId, _ = messageMap["id"].(string)
|
||||
func handleMessageReceivedFromHasura(hc *common.HasuraConnection, fromHasuraToBrowserChannel *common.SafeChannelByte, fromBrowserToHasuraChannel *common.SafeChannelByte, message []byte) {
|
||||
type HasuraMessageInfo struct {
|
||||
Type string `json:"type"`
|
||||
ID string `json:"id"`
|
||||
}
|
||||
var hasuraMessageInfo HasuraMessageInfo
|
||||
err := json.Unmarshal(message, &hasuraMessageInfo)
|
||||
if err != nil {
|
||||
log.Errorf("failed to unmarshal message: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
//Check if subscription is still active!
|
||||
if queryId != "" {
|
||||
hc.BrowserConn.ActiveSubscriptionsMutex.RLock()
|
||||
subscription, ok := hc.BrowserConn.ActiveSubscriptions[queryId]
|
||||
hc.BrowserConn.ActiveSubscriptionsMutex.RUnlock()
|
||||
if !ok {
|
||||
log.Debugf("Subscription with Id %s doesn't exist anymore, skipping response.", queryId)
|
||||
queryIdReplacementApplied := false
|
||||
queryIdInBytes := []byte(hasuraMessageInfo.ID)
|
||||
|
||||
//Check if subscription is still active!
|
||||
if hasuraMessageInfo.ID != "" {
|
||||
hc.BrowserConn.ActiveSubscriptionsMutex.RLock()
|
||||
subscription, ok := hc.BrowserConn.ActiveSubscriptions[hasuraMessageInfo.ID]
|
||||
hc.BrowserConn.ActiveSubscriptionsMutex.RUnlock()
|
||||
if !ok {
|
||||
log.Debugf("Subscription with Id %s doesn't exist anymore, skipping response.", hasuraMessageInfo.ID)
|
||||
return
|
||||
}
|
||||
|
||||
//When Hasura send msg type "complete", this query is finished
|
||||
if hasuraMessageInfo.Type == "complete" {
|
||||
handleCompleteMessage(hc, hasuraMessageInfo.ID)
|
||||
common.ActivitiesOverviewCompleted(string(subscription.Type) + "-" + subscription.OperationName)
|
||||
common.ActivitiesOverviewCompleted("_Sum-" + string(subscription.Type))
|
||||
}
|
||||
|
||||
if hasuraMessageInfo.Type == "next" {
|
||||
common.ActivitiesOverviewDataReceived(string(subscription.Type) + "-" + subscription.OperationName)
|
||||
}
|
||||
|
||||
if hasuraMessageInfo.Type == "next" &&
|
||||
subscription.Type == common.Subscription {
|
||||
|
||||
//Remove queryId from message
|
||||
message = bytes.Replace(message, queryIdInBytes, QueryIdPlaceholderInBytes, 1)
|
||||
queryIdReplacementApplied = true
|
||||
|
||||
isDifferentFromPreviousMessage := handleSubscriptionMessage(hc, &message, subscription, hasuraMessageInfo.ID)
|
||||
|
||||
//Stop processing case it is the same message (probably is a reconnection with Hasura)
|
||||
if !isDifferentFromPreviousMessage {
|
||||
return
|
||||
}
|
||||
|
||||
//When Hasura send msg type "complete", this query is finished
|
||||
if messageType == "complete" {
|
||||
handleCompleteMessage(hc, queryId)
|
||||
common.ActivitiesOverviewCompleted(string(subscription.Type) + "-" + subscription.OperationName)
|
||||
common.ActivitiesOverviewCompleted("_Sum-" + string(subscription.Type))
|
||||
}
|
||||
|
||||
if messageType == "next" {
|
||||
common.ActivitiesOverviewDataReceived(string(subscription.Type) + "-" + subscription.OperationName)
|
||||
}
|
||||
|
||||
if messageType == "next" &&
|
||||
subscription.Type == common.Subscription {
|
||||
hasNoPreviousOccurrence := handleSubscriptionMessage(hc, messageMap, subscription, queryId)
|
||||
|
||||
if !hasNoPreviousOccurrence {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
//Set last cursor value for stream
|
||||
if subscription.Type == common.Streaming {
|
||||
handleStreamingMessage(hc, messageMap, subscription, queryId)
|
||||
}
|
||||
}
|
||||
|
||||
// Retransmit the subscription start commands when hasura confirms the connection
|
||||
// this is useful in case of a connection invalidation
|
||||
if messageType == "connection_ack" {
|
||||
handleConnectionAckMessage(hc, messageMap, fromHasuraToBrowserChannel, fromBrowserToHasuraChannel)
|
||||
} else {
|
||||
// Forward the message to browser
|
||||
fromHasuraToBrowserChannel.Send(messageMap)
|
||||
//Set last cursor value for stream
|
||||
if subscription.Type == common.Streaming {
|
||||
//Remove queryId from message
|
||||
messageWithoutId := bytes.Replace(message, queryIdInBytes, QueryIdPlaceholderInBytes, 1)
|
||||
|
||||
handleStreamingMessage(hc, messageWithoutId, subscription, hasuraMessageInfo.ID)
|
||||
}
|
||||
}
|
||||
|
||||
// Retransmit the subscription start commands when hasura confirms the connection
|
||||
// this is useful in case of a connection invalidation
|
||||
if hasuraMessageInfo.Type == "connection_ack" {
|
||||
handleConnectionAckMessage(hc, message, fromHasuraToBrowserChannel, fromBrowserToHasuraChannel)
|
||||
} else {
|
||||
if queryIdReplacementApplied {
|
||||
message = bytes.Replace(message, QueryIdPlaceholderInBytes, queryIdInBytes, 1)
|
||||
}
|
||||
|
||||
// Forward the message to browser
|
||||
fromHasuraToBrowserChannel.Send(message)
|
||||
}
|
||||
}
|
||||
|
||||
func handleSubscriptionMessage(hc *common.HasuraConnection, messageMap map[string]interface{}, subscription common.GraphQlSubscription, queryId string) bool {
|
||||
if payload, okPayload := messageMap["payload"].(map[string]interface{}); okPayload {
|
||||
if data, okData := payload["data"].(map[string]interface{}); okData {
|
||||
for dataKey, dataItem := range data {
|
||||
if currentDataProp, okCurrentDataProp := dataItem.([]interface{}); okCurrentDataProp {
|
||||
if dataAsJson, err := json.Marshal(currentDataProp); err == nil {
|
||||
if common.ActivitiesOverviewEnabled {
|
||||
dataSize := len(string(dataAsJson))
|
||||
dataCount := len(currentDataProp)
|
||||
common.ActivitiesOverviewDataSize(string(subscription.Type)+"-"+subscription.OperationName, int64(dataSize), int64(dataCount))
|
||||
}
|
||||
func handleSubscriptionMessage(hc *common.HasuraConnection, message *[]byte, subscription common.GraphQlSubscription, queryId string) bool {
|
||||
if common.ActivitiesOverviewEnabled {
|
||||
dataSize := len(string(*message))
|
||||
common.ActivitiesOverviewDataSize(string(subscription.Type)+"-"+subscription.OperationName, int64(dataSize), 0)
|
||||
}
|
||||
|
||||
//Check whether ReceivedData is different from the LastReceivedData
|
||||
//Otherwise stop forwarding this message
|
||||
dataChecksum := crc32.ChecksumIEEE(dataAsJson)
|
||||
if subscription.LastReceivedDataChecksum == dataChecksum {
|
||||
return false
|
||||
}
|
||||
dataChecksum, messageDataKey, messageData := getHasuraMessage(*message)
|
||||
|
||||
lastDataChecksumWas := subscription.LastReceivedDataChecksum
|
||||
lastDataAsJsonWas := subscription.LastReceivedData
|
||||
cacheKey := fmt.Sprintf("%s-%s-%v-%v", string(subscription.Type), subscription.OperationName, subscription.LastReceivedDataChecksum, dataChecksum)
|
||||
//Check whether ReceivedData is different from the LastReceivedData
|
||||
//Otherwise stop forwarding this message
|
||||
if subscription.LastReceivedDataChecksum == dataChecksum {
|
||||
return false
|
||||
}
|
||||
|
||||
//Store LastReceivedData Checksum
|
||||
if msgpatch.RawDataCacheStorageMode == "memory" {
|
||||
subscription.LastReceivedData = dataAsJson
|
||||
}
|
||||
subscription.LastReceivedDataChecksum = dataChecksum
|
||||
hc.BrowserConn.ActiveSubscriptionsMutex.Lock()
|
||||
hc.BrowserConn.ActiveSubscriptions[queryId] = subscription
|
||||
hc.BrowserConn.ActiveSubscriptionsMutex.Unlock()
|
||||
lastDataChecksumWas := subscription.LastReceivedDataChecksum
|
||||
lastReceivedDataWas := subscription.LastReceivedData
|
||||
cacheKey := mergeUint32(subscription.LastReceivedDataChecksum, dataChecksum)
|
||||
|
||||
//Apply msg patch when it supports it
|
||||
if subscription.JsonPatchSupported {
|
||||
msgpatch.PatchMessage(&messageMap, queryId, dataKey, lastDataAsJsonWas, dataAsJson, hc.BrowserConn.Id, hc.BrowserConn.SessionToken, cacheKey, lastDataChecksumWas, dataChecksum)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//Store LastReceivedData Checksum
|
||||
if msgpatch.RawDataCacheStorageMode == "memory" {
|
||||
subscription.LastReceivedData = messageData
|
||||
}
|
||||
subscription.LastReceivedDataChecksum = dataChecksum
|
||||
hc.BrowserConn.ActiveSubscriptionsMutex.Lock()
|
||||
hc.BrowserConn.ActiveSubscriptions[queryId] = subscription
|
||||
hc.BrowserConn.ActiveSubscriptionsMutex.Unlock()
|
||||
|
||||
//Apply msg patch when it supports it
|
||||
if subscription.JsonPatchSupported {
|
||||
*message = msgpatch.GetPatchedMessage(*message, queryId, messageDataKey, lastReceivedDataWas, messageData, hc.BrowserConn.Id, hc.BrowserConn.SessionToken, cacheKey, lastDataChecksumWas, dataChecksum)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func handleStreamingMessage(hc *common.HasuraConnection, messageMap map[string]interface{}, subscription common.GraphQlSubscription, queryId string) {
|
||||
lastCursor := common.GetLastStreamCursorValueFromReceivedMessage(messageMap, subscription.StreamCursorField)
|
||||
func mergeUint32(a, b uint32) uint32 {
|
||||
return (a << 16) | (b >> 16)
|
||||
}
|
||||
|
||||
func handleStreamingMessage(hc *common.HasuraConnection, message []byte, subscription common.GraphQlSubscription, queryId string) {
|
||||
lastCursor := common.GetLastStreamCursorValueFromReceivedMessage(message, subscription.StreamCursorField)
|
||||
if lastCursor != nil && subscription.StreamCursorCurrValue != lastCursor {
|
||||
subscription.StreamCursorCurrValue = lastCursor
|
||||
|
||||
@ -183,16 +196,45 @@ func handleCompleteMessage(hc *common.HasuraConnection, queryId string) {
|
||||
log.Debugf("%s (%s) with Id %s finished by Hasura.", queryType, operationName, queryId)
|
||||
}
|
||||
|
||||
func handleConnectionAckMessage(hc *common.HasuraConnection, messageMap map[string]interface{}, fromHasuraToBrowserChannel *common.SafeChannel, fromBrowserToHasuraChannel *common.SafeChannel) {
|
||||
func handleConnectionAckMessage(hc *common.HasuraConnection, message []byte, fromHasuraToBrowserChannel *common.SafeChannelByte, fromBrowserToHasuraChannel *common.SafeChannelByte) {
|
||||
log.Debugf("Received connection_ack")
|
||||
//Hasura connection was initialized, now it's able to send new messages to Hasura
|
||||
fromBrowserToHasuraChannel.UnfreezeChannel()
|
||||
|
||||
//Avoid to send `connection_ack` to the browser when it's a reconnection
|
||||
if hc.BrowserConn.ConnAckSentToBrowser == false {
|
||||
fromHasuraToBrowserChannel.Send(messageMap)
|
||||
fromHasuraToBrowserChannel.Send(message)
|
||||
hc.BrowserConn.ConnAckSentToBrowser = true
|
||||
}
|
||||
|
||||
go retransmiter.RetransmitSubscriptionStartMessages(hc, fromBrowserToHasuraChannel)
|
||||
}
|
||||
|
||||
func getHasuraMessage(message []byte) (uint32, string, common.HasuraMessage) {
|
||||
dataChecksum := crc32.ChecksumIEEE(message)
|
||||
|
||||
common.GlobalCacheLocks.Lock(dataChecksum)
|
||||
dataKey, hasuraMessage, dataMapExists := common.GetHasuraMessageCache(dataChecksum)
|
||||
if dataMapExists {
|
||||
//Unlock immediately once the cache was already created by other routine
|
||||
common.GlobalCacheLocks.Unlock(dataChecksum)
|
||||
return dataChecksum, dataKey, hasuraMessage
|
||||
} else {
|
||||
//It will create the cache and then Unlock (others will wait to benefit from this cache)
|
||||
defer common.GlobalCacheLocks.Unlock(dataChecksum)
|
||||
}
|
||||
|
||||
err := json.Unmarshal(message, &hasuraMessage)
|
||||
if err != nil {
|
||||
log.Fatalf("Error unmarshalling JSON: %v", err)
|
||||
}
|
||||
|
||||
for key := range hasuraMessage.Payload.Data {
|
||||
dataKey = key
|
||||
break
|
||||
}
|
||||
|
||||
common.StoreHasuraMessageCache(dataChecksum, dataKey, hasuraMessage)
|
||||
|
||||
return dataChecksum, dataKey, hasuraMessage
|
||||
}
|
||||
|
@ -2,11 +2,12 @@ package writer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/iMDT/bbb-graphql-middleware/internal/common"
|
||||
"github.com/iMDT/bbb-graphql-middleware/internal/msgpatch"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"nhooyr.io/websocket/wsjson"
|
||||
"nhooyr.io/websocket"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
@ -14,7 +15,7 @@ import (
|
||||
|
||||
// HasuraConnectionWriter
|
||||
// process messages (middleware to hasura)
|
||||
func HasuraConnectionWriter(hc *common.HasuraConnection, fromBrowserToHasuraChannel *common.SafeChannel, wg *sync.WaitGroup, initMessage map[string]interface{}) {
|
||||
func HasuraConnectionWriter(hc *common.HasuraConnection, fromBrowserToHasuraChannel *common.SafeChannelByte, wg *sync.WaitGroup, initMessage []byte) {
|
||||
log := log.WithField("_routine", "HasuraConnectionWriter")
|
||||
|
||||
browserConnection := hc.BrowserConn
|
||||
@ -33,7 +34,7 @@ func HasuraConnectionWriter(hc *common.HasuraConnection, fromBrowserToHasuraChan
|
||||
}
|
||||
|
||||
//Send init connection message to Hasura to start
|
||||
err := wsjson.Write(hc.Context, hc.Websocket, initMessage)
|
||||
err := hc.Websocket.Write(hc.Context, websocket.MessageText, initMessage)
|
||||
if err != nil {
|
||||
log.Errorf("error on write authentication (init) message (we're disconnected from hasura): %v", err)
|
||||
return
|
||||
@ -56,10 +57,17 @@ RangeLoop:
|
||||
continue
|
||||
}
|
||||
|
||||
var fromBrowserMessageAsMap = fromBrowserMessage.(map[string]interface{})
|
||||
//var fromBrowserMessageAsMap = fromBrowserMessage.(map[string]interface{})
|
||||
|
||||
if fromBrowserMessageAsMap["type"] == "subscribe" {
|
||||
var queryId = fromBrowserMessageAsMap["id"].(string)
|
||||
var browserMessage common.BrowserSubscribeMessage
|
||||
err := json.Unmarshal(fromBrowserMessage, &browserMessage)
|
||||
if err != nil {
|
||||
log.Errorf("failed to unmarshal message: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if browserMessage.Type == "subscribe" {
|
||||
var queryId = browserMessage.ID
|
||||
|
||||
//Identify type based on query string
|
||||
messageType := common.Query
|
||||
@ -67,26 +75,23 @@ RangeLoop:
|
||||
streamCursorField := ""
|
||||
streamCursorVariableName := ""
|
||||
var streamCursorInitialValue interface{}
|
||||
payload := fromBrowserMessageAsMap["payload"].(map[string]interface{})
|
||||
operationName, ok := payload["operationName"].(string)
|
||||
|
||||
query, ok := payload["query"].(string)
|
||||
if ok {
|
||||
query := browserMessage.Payload.Query
|
||||
if query != "" {
|
||||
if strings.HasPrefix(query, "subscription") {
|
||||
|
||||
//Validate if subscription is allowed
|
||||
if allowedSubscriptions := os.Getenv("BBB_GRAPHQL_MIDDLEWARE_ALLOWED_SUBSCRIPTIONS"); allowedSubscriptions != "" {
|
||||
allowedSubscriptionsSlice := strings.Split(allowedSubscriptions, ",")
|
||||
subscriptionAllowed := false
|
||||
for _, s := range allowedSubscriptionsSlice {
|
||||
if s == operationName {
|
||||
if s == browserMessage.Payload.OperationName {
|
||||
subscriptionAllowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !subscriptionAllowed {
|
||||
log.Infof("Subscription %s not allowed!", operationName)
|
||||
log.Infof("Subscription %s not allowed!", browserMessage.Payload.OperationName)
|
||||
continue
|
||||
}
|
||||
}
|
||||
@ -96,14 +101,14 @@ RangeLoop:
|
||||
deniedSubscriptionsSlice := strings.Split(deniedSubscriptions, ",")
|
||||
subscriptionAllowed := true
|
||||
for _, s := range deniedSubscriptionsSlice {
|
||||
if s == operationName {
|
||||
if s == browserMessage.Payload.OperationName {
|
||||
subscriptionAllowed = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !subscriptionAllowed {
|
||||
log.Infof("Subscription %s not allowed!", operationName)
|
||||
log.Infof("Subscription %s not allowed!", browserMessage.Payload.OperationName)
|
||||
continue
|
||||
}
|
||||
}
|
||||
@ -119,14 +124,15 @@ RangeLoop:
|
||||
|
||||
if strings.Contains(query, "_stream(") && strings.Contains(query, "cursor: {") {
|
||||
messageType = common.Streaming
|
||||
|
||||
if !queryIdExists {
|
||||
streamCursorField, streamCursorVariableName, streamCursorInitialValue = common.GetStreamCursorPropsFromQuery(payload, query)
|
||||
streamCursorField, streamCursorVariableName, streamCursorInitialValue = common.GetStreamCursorPropsFromBrowserMessage(browserMessage)
|
||||
|
||||
//It's necessary to assure the cursor field will return in the result of the query
|
||||
//To be able to store the last received cursor value
|
||||
payload["query"] = common.PatchQueryIncludingCursorField(query, streamCursorField)
|
||||
fromBrowserMessageAsMap["payload"] = payload
|
||||
browserMessage.Payload.Query = common.PatchQueryIncludingCursorField(query, streamCursorField)
|
||||
|
||||
newMessageJson, _ := json.Marshal(browserMessage)
|
||||
fromBrowserMessage = newMessageJson
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,7 +149,7 @@ RangeLoop:
|
||||
//Identify if the client that requested this subscription expects to receive json-patch
|
||||
//Client append `Patched_` to the query operationName to indicate that it supports
|
||||
jsonPatchSupported := false
|
||||
if ok && strings.HasPrefix(operationName, "Patched_") {
|
||||
if strings.HasPrefix(browserMessage.Payload.OperationName, "Patched_") {
|
||||
jsonPatchSupported = true
|
||||
}
|
||||
if jsonPatchDisabled := os.Getenv("BBB_GRAPHQL_MIDDLEWARE_JSON_PATCH_DISABLED"); jsonPatchDisabled != "" {
|
||||
@ -153,8 +159,8 @@ RangeLoop:
|
||||
browserConnection.ActiveSubscriptionsMutex.Lock()
|
||||
browserConnection.ActiveSubscriptions[queryId] = common.GraphQlSubscription{
|
||||
Id: queryId,
|
||||
Message: fromBrowserMessageAsMap,
|
||||
OperationName: operationName,
|
||||
Message: fromBrowserMessage,
|
||||
OperationName: browserMessage.Payload.OperationName,
|
||||
StreamCursorField: streamCursorField,
|
||||
StreamCursorVariableName: streamCursorVariableName,
|
||||
StreamCursorCurrValue: streamCursorInitialValue,
|
||||
@ -166,7 +172,7 @@ RangeLoop:
|
||||
// log.Tracef("Current queries: %v", browserConnection.ActiveSubscriptions)
|
||||
browserConnection.ActiveSubscriptionsMutex.Unlock()
|
||||
|
||||
common.ActivitiesOverviewStarted(string(messageType) + "-" + operationName)
|
||||
common.ActivitiesOverviewStarted(string(messageType) + "-" + browserMessage.Payload.OperationName)
|
||||
common.ActivitiesOverviewStarted("_Sum-" + string(messageType))
|
||||
|
||||
//Dump of all subscriptions for analysis purpose
|
||||
@ -174,36 +180,35 @@ RangeLoop:
|
||||
//saveItToFile(fmt.Sprintf("%s-%s-%02s", string(messageType), operationName, queryId), fromBrowserMessageAsMap)
|
||||
}
|
||||
|
||||
if fromBrowserMessageAsMap["type"] == "complete" {
|
||||
var queryId = fromBrowserMessageAsMap["id"].(string)
|
||||
if browserMessage.Type == "complete" {
|
||||
browserConnection.ActiveSubscriptionsMutex.RLock()
|
||||
jsonPatchSupported := browserConnection.ActiveSubscriptions[queryId].JsonPatchSupported
|
||||
jsonPatchSupported := browserConnection.ActiveSubscriptions[browserMessage.ID].JsonPatchSupported
|
||||
|
||||
//Remove subscriptions from ActivitiesOverview here once Hasura-Reader will ignore "complete" msg for them
|
||||
common.ActivitiesOverviewCompleted(string(browserConnection.ActiveSubscriptions[queryId].Type) + "-" + browserConnection.ActiveSubscriptions[queryId].OperationName)
|
||||
common.ActivitiesOverviewCompleted("_Sum-" + string(browserConnection.ActiveSubscriptions[queryId].Type))
|
||||
common.ActivitiesOverviewCompleted(string(browserConnection.ActiveSubscriptions[browserMessage.ID].Type) + "-" + browserConnection.ActiveSubscriptions[browserMessage.ID].OperationName)
|
||||
common.ActivitiesOverviewCompleted("_Sum-" + string(browserConnection.ActiveSubscriptions[browserMessage.ID].Type))
|
||||
|
||||
browserConnection.ActiveSubscriptionsMutex.RUnlock()
|
||||
if jsonPatchSupported {
|
||||
msgpatch.RemoveConnSubscriptionCacheFile(browserConnection.Id, browserConnection.SessionToken, queryId)
|
||||
msgpatch.RemoveConnSubscriptionCacheFile(browserConnection.Id, browserConnection.SessionToken, browserMessage.ID)
|
||||
}
|
||||
browserConnection.ActiveSubscriptionsMutex.Lock()
|
||||
delete(browserConnection.ActiveSubscriptions, queryId)
|
||||
delete(browserConnection.ActiveSubscriptions, browserMessage.ID)
|
||||
// log.Tracef("Current queries: %v", browserConnection.ActiveSubscriptions)
|
||||
browserConnection.ActiveSubscriptionsMutex.Unlock()
|
||||
}
|
||||
|
||||
if fromBrowserMessageAsMap["type"] == "connection_init" {
|
||||
if browserMessage.Type == "connection_init" {
|
||||
//browserConnection.ConnectionInitMessage = fromBrowserMessageAsMap
|
||||
//Skip message once it is handled by ConnInitHandler already
|
||||
continue
|
||||
}
|
||||
|
||||
log.Tracef("sending to hasura: %v", fromBrowserMessageAsMap)
|
||||
err := wsjson.Write(hc.Context, hc.Websocket, fromBrowserMessageAsMap)
|
||||
if err != nil {
|
||||
if !errors.Is(err, context.Canceled) {
|
||||
log.Errorf("error on write (we're disconnected from hasura): %v", err)
|
||||
log.Tracef("sending to hasura: %v", fromBrowserMessage)
|
||||
errWrite := hc.Websocket.Write(hc.Context, websocket.MessageText, fromBrowserMessage)
|
||||
if errWrite != nil {
|
||||
if !errors.Is(errWrite, context.Canceled) {
|
||||
log.Errorf("error on write (we're disconnected from hasura): %v", errWrite)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func RetransmitSubscriptionStartMessages(hc *common.HasuraConnection, fromBrowserToHasuraChannel *common.SafeChannel) {
|
||||
func RetransmitSubscriptionStartMessages(hc *common.HasuraConnection, fromBrowserToHasuraChannel *common.SafeChannelByte) {
|
||||
log := log.WithField("_routine", "RetransmitSubscriptionStartMessages").WithField("browserConnectionId", hc.BrowserConn.Id).WithField("hasuraConnectionId", hc.Id)
|
||||
|
||||
hc.BrowserConn.ActiveSubscriptionsMutex.RLock()
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var cacheDir = os.TempDir() + "/graphql-middleware-cache/"
|
||||
@ -137,79 +137,78 @@ func fileExists(filename string) bool {
|
||||
return !os.IsNotExist(err)
|
||||
}
|
||||
|
||||
func PatchMessage(
|
||||
receivedMessage *map[string]interface{},
|
||||
func GetPatchedMessage(
|
||||
receivedMessage []byte,
|
||||
subscriptionId string,
|
||||
dataKey string,
|
||||
lastDataAsJson []byte,
|
||||
dataAsJson []byte,
|
||||
lastHasuraMessage common.HasuraMessage,
|
||||
hasuraMessage common.HasuraMessage,
|
||||
browserConnectionId string,
|
||||
browserSessionToken string,
|
||||
cacheKey string,
|
||||
cacheKey uint32,
|
||||
lastDataChecksum uint32,
|
||||
currDataChecksum uint32) {
|
||||
currDataChecksum uint32) []byte {
|
||||
|
||||
if lastDataChecksum != 0 {
|
||||
common.JsonPatchBenchmarkingStarted(cacheKey)
|
||||
defer common.JsonPatchBenchmarkingCompleted(cacheKey)
|
||||
common.JsonPatchBenchmarkingStarted(strconv.Itoa(int(cacheKey)))
|
||||
defer common.JsonPatchBenchmarkingCompleted(strconv.Itoa(int(cacheKey)))
|
||||
}
|
||||
|
||||
//Avoid other routines from processing the same JsonPatch
|
||||
//Lock to avoid other routines from processing the same message
|
||||
common.GlobalCacheLocks.Lock(cacheKey)
|
||||
jsonDiffPatch, jsonDiffPatchExists := common.GetJsonPatchCache(cacheKey)
|
||||
if jsonDiffPatchExists {
|
||||
if patchedMessageCache, patchedMessageCacheExists := common.GetPatchedMessageCache(cacheKey); patchedMessageCacheExists {
|
||||
//Unlock immediately once the cache was already created by other routine
|
||||
common.GlobalCacheLocks.Unlock(cacheKey)
|
||||
return patchedMessageCache
|
||||
} else {
|
||||
//It will create the cache and then Unlock (others will wait to benefit from this cache)
|
||||
defer common.GlobalCacheLocks.Unlock(cacheKey)
|
||||
}
|
||||
|
||||
var receivedMessageMap = *receivedMessage
|
||||
var jsonDiffPatch []byte
|
||||
|
||||
if !jsonDiffPatchExists {
|
||||
if currDataChecksum == lastDataChecksum {
|
||||
//Content didn't change, set message as null to avoid sending it to the browser
|
||||
//This case is usual when the middleware reconnects with Hasura and receives the data again
|
||||
*receivedMessage = nil
|
||||
} else {
|
||||
//Content was changed, creating json patch
|
||||
//If data is small (< minLengthToPatch) it's not worth creating the patch
|
||||
if len(string(dataAsJson)) > minLengthToPatch {
|
||||
if lastContent, lastContentErr := GetRawDataCache(browserConnectionId, browserSessionToken, subscriptionId, dataKey, lastDataAsJson); lastContentErr == nil && string(lastContent) != "" {
|
||||
//Temporarily use CustomPatch only for UserList (testing feature)
|
||||
if strings.HasPrefix(cacheKey, "subscription-Patched_UserListSubscription") &&
|
||||
common.ValidateIfShouldUseCustomJsonPatch(lastContent, dataAsJson, "userId") {
|
||||
jsonDiffPatch = common.CreateJsonPatch(lastContent, dataAsJson, "userId")
|
||||
common.StoreJsonPatchCache(cacheKey, jsonDiffPatch)
|
||||
} else if diffPatch, diffPatchErr := jsonpatch.CreatePatch(lastContent, dataAsJson); diffPatchErr == nil {
|
||||
var err error
|
||||
if jsonDiffPatch, err = json.Marshal(diffPatch); err == nil {
|
||||
common.StoreJsonPatchCache(cacheKey, jsonDiffPatch)
|
||||
} else {
|
||||
log.Errorf("Error marshaling patch array: %v", err)
|
||||
}
|
||||
} else {
|
||||
log.Errorf("Error creating JSON patch: %v\n%v", diffPatchErr, string(dataAsJson))
|
||||
if currDataChecksum == lastDataChecksum {
|
||||
//Content didn't change, set message as null to avoid sending it to the browser
|
||||
//This case is usual when the middleware reconnects with Hasura and receives the data again
|
||||
jsonData, _ := json.Marshal(nil)
|
||||
common.StorePatchedMessageCache(cacheKey, jsonData)
|
||||
return jsonData
|
||||
} else {
|
||||
//Content was changed, creating json patch
|
||||
//If data is small (< minLengthToPatch) it's not worth creating the patch
|
||||
if len(hasuraMessage.Payload.Data[dataKey]) > minLengthToPatch {
|
||||
if lastContent, lastContentErr := GetRawDataCache(browserConnectionId, browserSessionToken, subscriptionId, dataKey, lastHasuraMessage.Payload.Data[dataKey]); lastContentErr == nil && string(lastContent) != "" {
|
||||
var shouldUseCustomJsonPatch bool
|
||||
if shouldUseCustomJsonPatch, jsonDiffPatch = common.ValidateIfShouldUseCustomJsonPatch(lastContent, hasuraMessage.Payload.Data[dataKey], "userId"); shouldUseCustomJsonPatch {
|
||||
common.StorePatchedMessageCache(cacheKey, jsonDiffPatch)
|
||||
} else if diffPatch, diffPatchErr := jsonpatch.CreatePatch(lastContent, hasuraMessage.Payload.Data[dataKey]); diffPatchErr == nil {
|
||||
var err error
|
||||
if jsonDiffPatch, err = json.Marshal(diffPatch); err != nil {
|
||||
log.Errorf("Error marshaling patch array: %v", err)
|
||||
}
|
||||
} else {
|
||||
log.Errorf("Error creating JSON patch: %v\n%v", diffPatchErr, string(hasuraMessage.Payload.Data[dataKey]))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Use patch if the length is {minShrinkToUsePatch}% smaller than the original msg
|
||||
if jsonDiffPatch != nil && float64(len(string(jsonDiffPatch)))/float64(len(string(dataAsJson))) < minShrinkToUsePatch {
|
||||
if jsonDiffPatch != nil && float64(len(string(jsonDiffPatch)))/float64(len(string(hasuraMessage.Payload.Data[dataKey]))) < minShrinkToUsePatch {
|
||||
//Modify receivedMessage to include the Patch and remove the previous data
|
||||
//The key of the original message is kept to avoid errors (Apollo-client expects to receive this prop)
|
||||
receivedMessageMap["payload"] = map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"patch": json.RawMessage(jsonDiffPatch),
|
||||
dataKey: json.RawMessage("[]"),
|
||||
},
|
||||
|
||||
hasuraMessage.Payload.Data = map[string]json.RawMessage{
|
||||
"patch": jsonDiffPatch,
|
||||
dataKey: json.RawMessage("[]"),
|
||||
}
|
||||
*receivedMessage = receivedMessageMap
|
||||
hasuraMessageJson, _ := json.Marshal(hasuraMessage)
|
||||
receivedMessage = hasuraMessageJson
|
||||
}
|
||||
|
||||
//Store current result to be used to create json patch in the future
|
||||
StoreRawDataCache(browserConnectionId, browserSessionToken, subscriptionId, dataKey, dataAsJson)
|
||||
StoreRawDataCache(browserConnectionId, browserSessionToken, subscriptionId, dataKey, hasuraMessage.Payload.Data[dataKey])
|
||||
|
||||
common.StorePatchedMessageCache(cacheKey, receivedMessage)
|
||||
return receivedMessage
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
package websrv
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/iMDT/bbb-graphql-middleware/internal/akka_apps"
|
||||
"github.com/iMDT/bbb-graphql-middleware/internal/bbb_web"
|
||||
@ -95,10 +97,10 @@ func ConnectionHandler(w http.ResponseWriter, r *http.Request) {
|
||||
log.Infof("connection accepted")
|
||||
|
||||
// Create channels
|
||||
fromBrowserToHasuraConnectionEstablishingChannel := common.NewSafeChannel(bufferSize)
|
||||
fromBrowserToHasuraChannel := common.NewSafeChannel(bufferSize)
|
||||
fromBrowserToGqlActionsChannel := common.NewSafeChannel(bufferSize)
|
||||
fromHasuraToBrowserChannel := common.NewSafeChannel(bufferSize)
|
||||
fromBrowserToHasuraConnectionEstablishingChannel := common.NewSafeChannelByte(bufferSize)
|
||||
fromBrowserToHasuraChannel := common.NewSafeChannelByte(bufferSize)
|
||||
fromBrowserToGqlActionsChannel := common.NewSafeChannelByte(bufferSize)
|
||||
fromHasuraToBrowserChannel := common.NewSafeChannelByte(bufferSize)
|
||||
|
||||
// Configure the wait group (to hold this routine execution until both are completed)
|
||||
var wgAll sync.WaitGroup
|
||||
@ -295,7 +297,7 @@ func refreshUserSessionVariables(browserConnection *common.BrowserConnection) er
|
||||
|
||||
func connectionInitHandler(
|
||||
browserConnection *common.BrowserConnection,
|
||||
fromBrowserToHasuraConnectionEstablishingChannel *common.SafeChannel) error {
|
||||
fromBrowserToHasuraConnectionEstablishingChannel *common.SafeChannelByte) error {
|
||||
|
||||
BrowserConnectionsMutex.RLock()
|
||||
browserConnectionId := browserConnection.Id
|
||||
@ -309,9 +311,13 @@ func connectionInitHandler(
|
||||
//Received all messages. Channel is closed
|
||||
return fmt.Errorf("error on receiving init connection")
|
||||
}
|
||||
var fromBrowserMessageAsMap = fromBrowserMessage.(map[string]interface{})
|
||||
if bytes.Contains(fromBrowserMessage, []byte("\"connection_init\"")) {
|
||||
var fromBrowserMessageAsMap map[string]interface{}
|
||||
if err := json.Unmarshal(fromBrowserMessage, &fromBrowserMessageAsMap); err != nil {
|
||||
log.Errorf("failed to unmarshal message: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if fromBrowserMessageAsMap["type"] == "connection_init" {
|
||||
var payloadAsMap = fromBrowserMessageAsMap["payload"].(map[string]interface{})
|
||||
var headersAsMap = payloadAsMap["headers"].(map[string]interface{})
|
||||
var sessionToken, existsSessionToken = headersAsMap["X-Session-Token"].(string)
|
||||
@ -349,7 +355,7 @@ func connectionInitHandler(
|
||||
browserConnection.ClientSessionUUID = clientSessionUUID
|
||||
browserConnection.MeetingId = meetingId
|
||||
browserConnection.UserId = userId
|
||||
browserConnection.ConnectionInitMessage = fromBrowserMessageAsMap
|
||||
browserConnection.ConnectionInitMessage = fromBrowserMessage
|
||||
BrowserConnectionsMutex.Unlock()
|
||||
|
||||
refreshUserSessionVariables(browserConnection)
|
||||
|
@ -1,13 +1,13 @@
|
||||
package reader
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/iMDT/bbb-graphql-middleware/internal/common"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"nhooyr.io/websocket"
|
||||
"nhooyr.io/websocket/wsjson"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@ -17,9 +17,9 @@ func BrowserConnectionReader(
|
||||
ctx context.Context,
|
||||
ctxCancel context.CancelFunc,
|
||||
browserWsConn *websocket.Conn,
|
||||
fromBrowserToGqlActionsChannel *common.SafeChannel,
|
||||
fromBrowserToHasuraChannel *common.SafeChannel,
|
||||
fromBrowserToHasuraConnectionEstablishingChannel *common.SafeChannel,
|
||||
fromBrowserToGqlActionsChannel *common.SafeChannelByte,
|
||||
fromBrowserToHasuraChannel *common.SafeChannelByte,
|
||||
fromBrowserToHasuraConnectionEstablishingChannel *common.SafeChannelByte,
|
||||
waitGroups []*sync.WaitGroup) {
|
||||
log := log.WithField("_routine", "BrowserConnectionReader").WithField("browserConnectionId", browserConnectionId)
|
||||
defer log.Debugf("finished")
|
||||
@ -43,8 +43,7 @@ func BrowserConnectionReader(
|
||||
defer ctxCancel()
|
||||
|
||||
for {
|
||||
var v interface{}
|
||||
err := wsjson.Read(ctx, browserWsConn, &v)
|
||||
messageType, message, err := browserWsConn.Read(ctx)
|
||||
if err != nil {
|
||||
if errors.Is(err, context.Canceled) {
|
||||
log.Debugf("Closing Browser ws connection as Context was cancelled!")
|
||||
@ -54,26 +53,30 @@ func BrowserConnectionReader(
|
||||
return
|
||||
}
|
||||
|
||||
log.Tracef("received from browser: %v", v)
|
||||
log.Tracef("received from browser: %v", message)
|
||||
|
||||
if v == nil {
|
||||
if messageType != websocket.MessageText {
|
||||
log.Warnf("received non-text message: %v", messageType)
|
||||
continue
|
||||
}
|
||||
|
||||
var fromBrowserMessageAsMap = v.(map[string]interface{})
|
||||
var browserMessageType struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
err = json.Unmarshal(message, &browserMessageType)
|
||||
if err != nil {
|
||||
log.Errorf("failed to unmarshal message: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if payload, ok := fromBrowserMessageAsMap["payload"].(map[string]interface{}); ok {
|
||||
if query, okQuery := payload["query"].(string); okQuery {
|
||||
//Forward Mutations directly to GraphqlActions
|
||||
//Update mutations must be handled by Hasura
|
||||
if strings.HasPrefix(query, "mutation") && !strings.Contains(query, "update_") {
|
||||
fromBrowserToGqlActionsChannel.Send(v)
|
||||
continue
|
||||
}
|
||||
if browserMessageType.Type == "subscribe" {
|
||||
if bytes.Contains(message, []byte("\"query\":\"mutation")) && !bytes.Contains(message, []byte("update_")) {
|
||||
fromBrowserToGqlActionsChannel.Send(message)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
fromBrowserToHasuraChannel.Send(v)
|
||||
fromBrowserToHasuraConnectionEstablishingChannel.Send(v)
|
||||
fromBrowserToHasuraChannel.Send(message)
|
||||
fromBrowserToHasuraConnectionEstablishingChannel.Send(message)
|
||||
}
|
||||
}
|
||||
|
@ -2,14 +2,20 @@ package writer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/iMDT/bbb-graphql-middleware/internal/common"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"nhooyr.io/websocket"
|
||||
"nhooyr.io/websocket/wsjson"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func BrowserConnectionWriter(browserConnectionId string, ctx context.Context, browserWsConn *websocket.Conn, fromHasuraToBrowserChannel *common.SafeChannel, wg *sync.WaitGroup) {
|
||||
func BrowserConnectionWriter(
|
||||
browserConnectionId string,
|
||||
ctx context.Context,
|
||||
browserWsConn *websocket.Conn,
|
||||
fromHasuraToBrowserChannel *common.SafeChannelByte,
|
||||
wg *sync.WaitGroup) {
|
||||
log := log.WithField("_routine", "BrowserConnectionWriter").WithField("browserConnectionId", browserConnectionId)
|
||||
defer log.Debugf("finished")
|
||||
log.Debugf("starting")
|
||||
@ -30,10 +36,8 @@ RangeLoop:
|
||||
continue
|
||||
}
|
||||
|
||||
var toBrowserMessageAsMap = toBrowserMessage.(map[string]interface{})
|
||||
|
||||
log.Tracef("sending to browser: %v", toBrowserMessage)
|
||||
err := wsjson.Write(ctx, browserWsConn, toBrowserMessage)
|
||||
err := browserWsConn.Write(ctx, websocket.MessageText, toBrowserMessage)
|
||||
if err != nil {
|
||||
log.Debugf("Browser is disconnected, skipping writing of ws message: %v", err)
|
||||
return
|
||||
@ -41,9 +45,15 @@ RangeLoop:
|
||||
|
||||
// After the error is sent to client, close its connection
|
||||
// Authentication hook unauthorized this request
|
||||
if toBrowserMessageAsMap["type"] == "connection_error" {
|
||||
var payloadAsString = toBrowserMessageAsMap["payload"].(string)
|
||||
browserWsConn.Close(websocket.StatusInternalError, payloadAsString)
|
||||
if strings.Contains(string(toBrowserMessage), "connection_error") {
|
||||
type HasuraMessage struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
var hasuraMessage HasuraMessage
|
||||
_ = json.Unmarshal(toBrowserMessage, &hasuraMessage)
|
||||
if hasuraMessage.Type == "connection_error" {
|
||||
_ = browserWsConn.Close(websocket.StatusInternalError, string(toBrowserMessage))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2137,7 +2137,14 @@ select "meeting"."meetingId",
|
||||
select 1
|
||||
from "v_screenshare"
|
||||
where "v_screenshare"."meetingId" = "meeting"."meetingId"
|
||||
and "contentType" = 'screenshare'
|
||||
) as "hasScreenshare",
|
||||
exists (
|
||||
select 1
|
||||
from "v_screenshare"
|
||||
where "v_screenshare"."meetingId" = "meeting"."meetingId"
|
||||
and "contentType" = 'camera'
|
||||
) as "hasCameraAsContent",
|
||||
exists (
|
||||
select 1
|
||||
from "v_externalVideo"
|
||||
|
@ -15,6 +15,7 @@ select_permissions:
|
||||
- hasExternalVideo
|
||||
- hasPoll
|
||||
- hasScreenshare
|
||||
- hasCameraAsContent
|
||||
- hasTimer
|
||||
- showRemainingTime
|
||||
filter:
|
||||
|
881
bbb-learning-dashboard/package-lock.json
generated
881
bbb-learning-dashboard/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -52,7 +52,7 @@
|
||||
"eslint-plugin-jsx-a11y": "^6.4.1",
|
||||
"eslint-plugin-react": "^7.24.0",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"postcss": "^8.4.5",
|
||||
"postcss": "^8.4.33",
|
||||
"tailwindcss": "^3.0.11"
|
||||
}
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
git clone --branch v3.2.0-beta.0 --depth 1 https://github.com/bigbluebutton/bbb-webhooks bbb-webhooks
|
||||
git clone --branch v3.2.0-beta.1 --depth 1 https://github.com/bigbluebutton/bbb-webhooks bbb-webhooks
|
||||
|
@ -104,6 +104,7 @@ export interface ComponentsFlags {
|
||||
hasScreenshare: boolean;
|
||||
hasTimer: boolean;
|
||||
showRemainingTime: boolean;
|
||||
hasCameraAsContent: boolean;
|
||||
}
|
||||
|
||||
export interface Metadata {
|
||||
|
@ -23,7 +23,6 @@ import { EXTERNAL_VIDEO_STOP } from '../external-video-player/mutations';
|
||||
import { PINNED_PAD_SUBSCRIPTION } from '../notes/queries';
|
||||
import useDeduplicatedSubscription from '../../core/hooks/useDeduplicatedSubscription';
|
||||
import connectionStatus from '../../core/graphql/singletons/connectionStatus';
|
||||
import { getSceenShareType } from './queries';
|
||||
|
||||
const isReactionsButtonEnabled = () => {
|
||||
const USER_REACTIONS_ENABLED = window.meetingClientSettings.public.userReaction.enabled;
|
||||
@ -39,10 +38,6 @@ const ActionsBarContainer = (props) => {
|
||||
const RAISE_HAND_BUTTON_CENTERED = window.meetingClientSettings
|
||||
.public.app.raiseHandActionButton.centered;
|
||||
|
||||
const {
|
||||
data: sceenShareType,
|
||||
} = useDeduplicatedSubscription(getSceenShareType);
|
||||
|
||||
const actionsBarStyle = layoutSelectOutput((i) => i.actionBar);
|
||||
const layoutContextDispatch = layoutDispatch();
|
||||
|
||||
@ -108,8 +103,7 @@ const ActionsBarContainer = (props) => {
|
||||
setPresentationIsOpen: MediaService.setPresentationIsOpen,
|
||||
hasScreenshare: currentMeeting?.componentsFlags?.hasScreenshare ?? false,
|
||||
isMeteorConnected: connected,
|
||||
hasCameraAsContent: currentMeeting?.componentsFlags?.hasScreenshare
|
||||
&& sceenShareType?.screenshare[0]?.contentType,
|
||||
hasCameraAsContent: currentMeeting?.componentsFlags?.hasCameraAsContent,
|
||||
intl,
|
||||
allowExternalVideo,
|
||||
isPollingEnabled,
|
||||
|
@ -1,21 +0,0 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
interface ScreenShare {
|
||||
contentType: string;
|
||||
}
|
||||
|
||||
export interface GetScreenShareTypeResponse {
|
||||
screenshare: ScreenShare[];
|
||||
}
|
||||
|
||||
export const getSceenShareType = gql`
|
||||
subscription getSceenShareType {
|
||||
screenshare {
|
||||
contentType
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default {
|
||||
getSceenShareType,
|
||||
};
|
@ -255,7 +255,8 @@ const AppContainer = (props) => {
|
||||
};
|
||||
|
||||
const shouldShowScreenshare = (viewScreenshare || isPresenter)
|
||||
&& currentMeeting?.componentsFlags?.hasScreenshare;
|
||||
&& (currentMeeting?.componentsFlags?.hasScreenshare
|
||||
|| currentMeeting?.componentsFlags?.hasCameraAsContent);
|
||||
const shouldShowPresentation = (!shouldShowScreenshare && !isSharedNotesPinned
|
||||
&& !shouldShowExternalVideo && !shouldShowGenericMainContent
|
||||
&& (presentationIsOpen || presentationRestoreOnUpdate)) && isPresentationEnabled;
|
||||
|
@ -121,6 +121,7 @@ const MEETING_SUBSCRIPTION = gql`
|
||||
hasScreenshare
|
||||
hasTimer
|
||||
showRemainingTime
|
||||
hasCameraAsContent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
30
bigbluebutton-tests/playwright/package-lock.json
generated
30
bigbluebutton-tests/playwright/package-lock.json
generated
@ -6,7 +6,7 @@
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@playwright/test": "^1.37.1",
|
||||
"axios": "^1.6.0",
|
||||
"axios": "^1.7.2",
|
||||
"chalk": "^4.1.2",
|
||||
"deep-equal": "^2.2.1",
|
||||
"dotenv": "^16.1.4",
|
||||
@ -81,11 +81,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz",
|
||||
"integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==",
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz",
|
||||
"integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
@ -226,9 +226,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
@ -899,11 +899,11 @@
|
||||
"integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw=="
|
||||
},
|
||||
"axios": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz",
|
||||
"integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==",
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz",
|
||||
"integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
@ -1008,9 +1008,9 @@
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA=="
|
||||
},
|
||||
"for-each": {
|
||||
"version": "0.3.3",
|
||||
|
@ -11,7 +11,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@playwright/test": "^1.37.1",
|
||||
"axios": "^1.6.0",
|
||||
"axios": "^1.7.2",
|
||||
"chalk": "^4.1.2",
|
||||
"deep-equal": "^2.2.1",
|
||||
"dotenv": "^16.1.4",
|
||||
|
300
bigbluebutton-tests/puppeteer/package-lock.json
generated
300
bigbluebutton-tests/puppeteer/package-lock.json
generated
@ -5,7 +5,7 @@
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"axios": "^1.6.0",
|
||||
"axios": "^1.7.2",
|
||||
"babel-jest": "^27.5.1",
|
||||
"child_process": "^1.0.2",
|
||||
"dotenv": "^16.0.1",
|
||||
@ -37,11 +37,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz",
|
||||
"integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==",
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz",
|
||||
"integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==",
|
||||
"dependencies": {
|
||||
"@babel/highlight": "^7.24.2",
|
||||
"@babel/highlight": "^7.24.7",
|
||||
"picocolors": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@ -91,11 +91,11 @@
|
||||
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="
|
||||
},
|
||||
"node_modules/@babel/generator": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz",
|
||||
"integrity": "sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==",
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz",
|
||||
"integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.24.5",
|
||||
"@babel/types": "^7.24.7",
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.25",
|
||||
"jsesc": "^2.5.1"
|
||||
@ -120,31 +120,34 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-environment-visitor": {
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
|
||||
"integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz",
|
||||
"integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.24.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-function-name": {
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
|
||||
"integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz",
|
||||
"integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==",
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.22.15",
|
||||
"@babel/types": "^7.23.0"
|
||||
"@babel/template": "^7.24.7",
|
||||
"@babel/types": "^7.24.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-hoist-variables": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
|
||||
"integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz",
|
||||
"integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.22.5"
|
||||
"@babel/types": "^7.24.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@ -199,28 +202,28 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-split-export-declaration": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz",
|
||||
"integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==",
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz",
|
||||
"integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.24.5"
|
||||
"@babel/types": "^7.24.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz",
|
||||
"integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==",
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz",
|
||||
"integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz",
|
||||
"integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==",
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
|
||||
"integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@ -247,11 +250,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz",
|
||||
"integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==",
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz",
|
||||
"integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==",
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.24.5",
|
||||
"@babel/helper-validator-identifier": "^7.24.7",
|
||||
"chalk": "^2.4.2",
|
||||
"js-tokens": "^4.0.0",
|
||||
"picocolors": "^1.0.0"
|
||||
@ -261,9 +264,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz",
|
||||
"integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==",
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz",
|
||||
"integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==",
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
},
|
||||
@ -435,31 +438,31 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.24.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz",
|
||||
"integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==",
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz",
|
||||
"integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.23.5",
|
||||
"@babel/parser": "^7.24.0",
|
||||
"@babel/types": "^7.24.0"
|
||||
"@babel/code-frame": "^7.24.7",
|
||||
"@babel/parser": "^7.24.7",
|
||||
"@babel/types": "^7.24.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz",
|
||||
"integrity": "sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==",
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz",
|
||||
"integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.24.2",
|
||||
"@babel/generator": "^7.24.5",
|
||||
"@babel/helper-environment-visitor": "^7.22.20",
|
||||
"@babel/helper-function-name": "^7.23.0",
|
||||
"@babel/helper-hoist-variables": "^7.22.5",
|
||||
"@babel/helper-split-export-declaration": "^7.24.5",
|
||||
"@babel/parser": "^7.24.5",
|
||||
"@babel/types": "^7.24.5",
|
||||
"@babel/code-frame": "^7.24.7",
|
||||
"@babel/generator": "^7.24.7",
|
||||
"@babel/helper-environment-visitor": "^7.24.7",
|
||||
"@babel/helper-function-name": "^7.24.7",
|
||||
"@babel/helper-hoist-variables": "^7.24.7",
|
||||
"@babel/helper-split-export-declaration": "^7.24.7",
|
||||
"@babel/parser": "^7.24.7",
|
||||
"@babel/types": "^7.24.7",
|
||||
"debug": "^4.3.1",
|
||||
"globals": "^11.1.0"
|
||||
},
|
||||
@ -468,12 +471,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz",
|
||||
"integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==",
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz",
|
||||
"integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.24.1",
|
||||
"@babel/helper-validator-identifier": "^7.24.5",
|
||||
"@babel/helper-string-parser": "^7.24.7",
|
||||
"@babel/helper-validator-identifier": "^7.24.7",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@ -2230,11 +2233,11 @@
|
||||
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz",
|
||||
"integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==",
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz",
|
||||
"integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
@ -2418,11 +2421,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"dependencies": {
|
||||
"fill-range": "^7.0.1"
|
||||
"fill-range": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@ -3071,9 +3074,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
},
|
||||
@ -3094,9 +3097,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz",
|
||||
"integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==",
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
@ -7460,11 +7463,11 @@
|
||||
}
|
||||
},
|
||||
"@babel/code-frame": {
|
||||
"version": "7.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz",
|
||||
"integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==",
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz",
|
||||
"integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==",
|
||||
"requires": {
|
||||
"@babel/highlight": "^7.24.2",
|
||||
"@babel/highlight": "^7.24.7",
|
||||
"picocolors": "^1.0.0"
|
||||
}
|
||||
},
|
||||
@ -7503,11 +7506,11 @@
|
||||
}
|
||||
},
|
||||
"@babel/generator": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz",
|
||||
"integrity": "sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==",
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz",
|
||||
"integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.24.5",
|
||||
"@babel/types": "^7.24.7",
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.25",
|
||||
"jsesc": "^2.5.1"
|
||||
@ -7526,25 +7529,28 @@
|
||||
}
|
||||
},
|
||||
"@babel/helper-environment-visitor": {
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
|
||||
"integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA=="
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz",
|
||||
"integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.24.7"
|
||||
}
|
||||
},
|
||||
"@babel/helper-function-name": {
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
|
||||
"integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz",
|
||||
"integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==",
|
||||
"requires": {
|
||||
"@babel/template": "^7.22.15",
|
||||
"@babel/types": "^7.23.0"
|
||||
"@babel/template": "^7.24.7",
|
||||
"@babel/types": "^7.24.7"
|
||||
}
|
||||
},
|
||||
"@babel/helper-hoist-variables": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
|
||||
"integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz",
|
||||
"integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.22.5"
|
||||
"@babel/types": "^7.24.7"
|
||||
}
|
||||
},
|
||||
"@babel/helper-module-imports": {
|
||||
@ -7581,22 +7587,22 @@
|
||||
}
|
||||
},
|
||||
"@babel/helper-split-export-declaration": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz",
|
||||
"integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==",
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz",
|
||||
"integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.24.5"
|
||||
"@babel/types": "^7.24.7"
|
||||
}
|
||||
},
|
||||
"@babel/helper-string-parser": {
|
||||
"version": "7.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz",
|
||||
"integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ=="
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz",
|
||||
"integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg=="
|
||||
},
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz",
|
||||
"integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA=="
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
|
||||
"integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w=="
|
||||
},
|
||||
"@babel/helper-validator-option": {
|
||||
"version": "7.23.5",
|
||||
@ -7614,20 +7620,20 @@
|
||||
}
|
||||
},
|
||||
"@babel/highlight": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz",
|
||||
"integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==",
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz",
|
||||
"integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==",
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.24.5",
|
||||
"@babel/helper-validator-identifier": "^7.24.7",
|
||||
"chalk": "^2.4.2",
|
||||
"js-tokens": "^4.0.0",
|
||||
"picocolors": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz",
|
||||
"integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg=="
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz",
|
||||
"integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw=="
|
||||
},
|
||||
"@babel/plugin-syntax-async-generators": {
|
||||
"version": "7.8.4",
|
||||
@ -7742,39 +7748,39 @@
|
||||
}
|
||||
},
|
||||
"@babel/template": {
|
||||
"version": "7.24.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz",
|
||||
"integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==",
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz",
|
||||
"integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==",
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.23.5",
|
||||
"@babel/parser": "^7.24.0",
|
||||
"@babel/types": "^7.24.0"
|
||||
"@babel/code-frame": "^7.24.7",
|
||||
"@babel/parser": "^7.24.7",
|
||||
"@babel/types": "^7.24.7"
|
||||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz",
|
||||
"integrity": "sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==",
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz",
|
||||
"integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==",
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.24.2",
|
||||
"@babel/generator": "^7.24.5",
|
||||
"@babel/helper-environment-visitor": "^7.22.20",
|
||||
"@babel/helper-function-name": "^7.23.0",
|
||||
"@babel/helper-hoist-variables": "^7.22.5",
|
||||
"@babel/helper-split-export-declaration": "^7.24.5",
|
||||
"@babel/parser": "^7.24.5",
|
||||
"@babel/types": "^7.24.5",
|
||||
"@babel/code-frame": "^7.24.7",
|
||||
"@babel/generator": "^7.24.7",
|
||||
"@babel/helper-environment-visitor": "^7.24.7",
|
||||
"@babel/helper-function-name": "^7.24.7",
|
||||
"@babel/helper-hoist-variables": "^7.24.7",
|
||||
"@babel/helper-split-export-declaration": "^7.24.7",
|
||||
"@babel/parser": "^7.24.7",
|
||||
"@babel/types": "^7.24.7",
|
||||
"debug": "^4.3.1",
|
||||
"globals": "^11.1.0"
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz",
|
||||
"integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==",
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz",
|
||||
"integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==",
|
||||
"requires": {
|
||||
"@babel/helper-string-parser": "^7.24.1",
|
||||
"@babel/helper-validator-identifier": "^7.24.5",
|
||||
"@babel/helper-string-parser": "^7.24.7",
|
||||
"@babel/helper-validator-identifier": "^7.24.7",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
@ -9127,11 +9133,11 @@
|
||||
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
|
||||
},
|
||||
"axios": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz",
|
||||
"integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==",
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz",
|
||||
"integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
},
|
||||
@ -9274,11 +9280,11 @@
|
||||
}
|
||||
},
|
||||
"braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"requires": {
|
||||
"fill-range": "^7.0.1"
|
||||
"fill-range": "^7.1.1"
|
||||
}
|
||||
},
|
||||
"browserslist": {
|
||||
@ -9736,9 +9742,9 @@
|
||||
}
|
||||
},
|
||||
"fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"requires": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
}
|
||||
@ -9753,9 +9759,9 @@
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.15.3",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz",
|
||||
"integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q=="
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA=="
|
||||
},
|
||||
"fs": {
|
||||
"version": "0.0.1-security",
|
||||
|
@ -6,7 +6,7 @@
|
||||
"verbose": false
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.6.0",
|
||||
"axios": "^1.7.2",
|
||||
"babel-jest": "^27.5.1",
|
||||
"child_process": "^1.0.2",
|
||||
"dotenv": "^16.0.1",
|
||||
|
@ -1299,14 +1299,14 @@ Next, run `sudo bbb-conf --setip <hostname>` where \<hostname\> is the external
|
||||
Next, to enable gladia.io, run the following two commands
|
||||
|
||||
```
|
||||
yq e -i '.public.app.audioCaptions.enabled = true' /etc/bigbluebutton/bbb-html5.yml
|
||||
yq e -i '.public.app.audioCaptions.provider = gladia' /etc/bigbluebutton/bbb-html5.yml
|
||||
sudo yq e -i '.public.app.audioCaptions.enabled = true' /etc/bigbluebutton/bbb-html5.yml
|
||||
sudo yq e -i '.public.app.audioCaptions.provider = "gladia"' /etc/bigbluebutton/bbb-html5.yml
|
||||
```
|
||||
|
||||
Next, set the gladia.io API key using the command below, replacing \<gladia_api_key\> with a glada.io API key obtained above.
|
||||
|
||||
```
|
||||
yq e -i '.gladia.startMessage = "{\"x_gladia_key\": \"<gladia-api-key>\", \"sample_rate\": 0, \"bit_depth\": 16, \"model_type\": \"fast\", \"endpointing\": 10 }"' /usr/local/bigbluebutton/bbb-transcription-controller/config/default.yml
|
||||
sudo yq e -i '.gladia.startMessage = "{\"x_gladia_key\": \"<gladia-api-key>\", \"sample_rate\": 0, \"bit_depth\": 16, \"model_type\": \"fast\", \"endpointing\": 10 }"' /usr/local/bigbluebutton/bbb-transcription-controller/config/default.yml
|
||||
```
|
||||
|
||||
Restart the BigBlueButton server with `bbb-conf --restart`. You will now be able to select a speech-to-text option when joining audio (including auto translate). When one or more users have selected the option, a CC button will appear at the bottom and a Transcript panel will also be available.
|
||||
|
@ -66,10 +66,13 @@ We have enhanced the view of the polling results that appear over the whiteboard
|
||||
We have made significant changes to the architecture of BigBlueButton and have introduced support to plugins -- optional custom modules included in the client which allow expanding the capabilities of BigBlueButton. A data channel is provided to allow for data exchange between clients. See the [HTML5 Plugin SDK](https://github.com/bigbluebutton/bigbluebutton-html-plugin-sdk) for examples and more information.
|
||||
|
||||
At the moment of writing these documentation, the official list of plugins includes:
|
||||
- [Select Random User](https://github.com/bigbluebutton/plugins/tree/main/pick-random-user-plugin)
|
||||
- [Generic Link Share](https://github.com/bigbluebutton/plugins/tree/main/generic-link-share)
|
||||
- [Session Share](https://github.com/bigbluebutton/plugins/tree/main/session-share)
|
||||
- [Decrease external video's volume on speak](https://github.com/bigbluebutton/plugins/tree/main/decrease-volume-on-speak)
|
||||
- [Select Random User](https://github.com/bigbluebutton/plugin-pick-random-user)
|
||||
- [Share a link](https://github.com/bigbluebutton/plugin-generic-link-share)
|
||||
- [H5P plugin for BigBlueButton](https://github.com/bigbluebutton/plugin-h5p)
|
||||
- [Session share](https://github.com/bigbluebutton/plugin-session-share)
|
||||
- [Decrease the volume of external video when someone speaks](https://github.com/bigbluebutton/plugin-decrease-volume-on-speak)
|
||||
- [Typed captions](https://github.com/bigbluebutton/plugin-typed-captions)
|
||||
- [Source code highlight](https://github.com/bigbluebutton/plugin-code-highlight)
|
||||
|
||||
#### Replaced Akka framework with Pekko
|
||||
|
||||
|
23
docs/docs/plugins.md
Normal file
23
docs/docs/plugins.md
Normal file
@ -0,0 +1,23 @@
|
||||
## Plugins for BigBlueButton
|
||||
|
||||
### What they are
|
||||
|
||||
|
||||
### How to use
|
||||
|
||||
|
||||
### List of official plugins
|
||||
|
||||
This list is updated periodically. All official plugins can be found at our [GitHub plugins repository list](https://github.com/orgs/bigbluebutton/repositories?language=&q=plugin-&sort=&type=public)
|
||||
|
||||
- [H5P plugin for BigBlueButton](https://github.com/bigbluebutton/plugin-h5p)
|
||||
- [Select random user](https://github.com/bigbluebutton/plugin-pick-random-user)
|
||||
- [Typed captions](https://github.com/bigbluebutton/plugin-typed-captions)
|
||||
- [Decrease the volume of external video when someone speaks](https://github.com/bigbluebutton/plugin-decrease-volume-on-speak)
|
||||
- [Share a link](https://github.com/bigbluebutton/plugin-generic-link-share)
|
||||
- [Session share](https://github.com/bigbluebutton/plugin-session-share)
|
||||
- [Source code highlight](https://github.com/bigbluebutton/plugin-code-highlight)
|
||||
|
||||
### Building information
|
||||
|
||||
|
@ -63,6 +63,7 @@ const config = {
|
||||
{to: '/administration/install', label: 'Administration', position: 'left'},
|
||||
{to: '/greenlight/v3/install', label: 'Greenlight', position: 'left'},
|
||||
{to: '/new-features', label: 'New Features', position: 'left'},
|
||||
// {to: '/plugins', label: 'Plugins', position: 'left'},
|
||||
{to: '/support/getting-help', label: 'Support', position: 'left'},
|
||||
{
|
||||
type: 'docsVersionDropdown',
|
||||
|
26
docs/package-lock.json
generated
26
docs/package-lock.json
generated
@ -4625,11 +4625,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"dependencies": {
|
||||
"fill-range": "^7.0.1"
|
||||
"fill-range": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@ -6628,9 +6628,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
},
|
||||
@ -14635,9 +14635,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-dev-server/node_modules/ws": {
|
||||
"version": "8.14.2",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz",
|
||||
"integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==",
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
||||
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
@ -14879,9 +14879,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "7.5.9",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
|
||||
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
|
||||
"version": "7.5.10",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
|
||||
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
|
||||
"engines": {
|
||||
"node": ">=8.3.0"
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user