From c0a8674699e2ce5412cfc9c3f446eb590f2a96d7 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 15:18:57 +0000 Subject: [PATCH] Prepare for the WordPress.org review: drop bundled translations, harden the auth endpoint - Remove load_plugin_textdomain(): WordPress loads translations for the hibira slug itself since 4.6, and the plugin requires 6.0. - Stop shipping .po/.mo files; translate.wordpress.org provides them. The .po files move to translations/ (export-ignored) for importing there. - REST /hibira/v1/auth: decide the channel in permission_callback, validate socket_id and channel_name, sanitize order_key and visitor. - Sanitize the secret and host settings, the order key from $_GET and live comment HTML (wp_kses_post). - Add phpcs.xml.dist (WordPress-Extra, PHPCompatibilityWP) and a CI job. - readme: source of the bundled pusher-js build. Version 0.1.2. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01SHtsCDXecYDWvMV2LaCkYm --- .gitattributes | 3 +- .github/workflows/ci.yml | 17 ++++ README.md | 13 ++- assets/vendor/pusher.min.js | 1 - hibira.php | 11 +-- includes/class-hibira-auth.php | 82 ++++++++++++++-- includes/class-hibira-client.php | 2 +- includes/class-hibira-comments.php | 20 ++-- includes/class-hibira-posts.php | 7 -- includes/class-hibira-settings.php | 27 +++++- includes/class-hibira-woocommerce.php | 6 +- includes/functions.php | 27 ++++-- languages/hibira-ja.mo | Bin 4360 -> 0 bytes languages/hibira-ko_KR.mo | Bin 4099 -> 0 bytes languages/hibira-th.mo | Bin 5741 -> 0 bytes languages/hibira-vi.mo | Bin 4142 -> 0 bytes languages/hibira-zh_CN.mo | Bin 3744 -> 0 bytes languages/hibira-zh_TW.mo | Bin 3762 -> 0 bytes languages/hibira.pot | 100 +++++++++++++++++++- phpcs.xml.dist | 42 ++++++++ readme.txt | 13 ++- tools/compile-mo.py | 57 ----------- {languages => translations}/hibira-ja.po | 0 {languages => translations}/hibira-ko_KR.po | 0 {languages => translations}/hibira-th.po | 0 {languages => translations}/hibira-vi.po | 0 {languages => translations}/hibira-zh_CN.po | 0 {languages => translations}/hibira-zh_TW.po | 0 28 files changed, 307 insertions(+), 121 deletions(-) delete mode 100644 languages/hibira-ja.mo delete mode 100644 languages/hibira-ko_KR.mo delete mode 100644 languages/hibira-th.mo delete mode 100644 languages/hibira-vi.mo delete mode 100644 languages/hibira-zh_CN.mo delete mode 100644 languages/hibira-zh_TW.mo create mode 100644 phpcs.xml.dist delete mode 100644 tools/compile-mo.py rename {languages => translations}/hibira-ja.po (100%) rename {languages => translations}/hibira-ko_KR.po (100%) rename {languages => translations}/hibira-th.po (100%) rename {languages => translations}/hibira-vi.po (100%) rename {languages => translations}/hibira-zh_CN.po (100%) rename {languages => translations}/hibira-zh_TW.po (100%) diff --git a/.gitattributes b/.gitattributes index ae37eed..e63111b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,7 @@ /.github export-ignore /tests export-ignore -/tools export-ignore +/translations export-ignore +/phpcs.xml.dist export-ignore /.gitattributes export-ignore /.gitignore export-ignore /README.md export-ignore diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f8b5019..e8ca3b1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,23 @@ jobs: run: find . -name '*.php' -not -path './.git/*' -print0 | xargs -0 -n1 php -l - run: php tests/run.php + phpcs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: shivammathur/setup-php@v2 + with: + php-version: "8.3" + coverage: none + tools: composer + - name: install WPCS + run: | + composer global config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true + composer global require --no-interaction wp-coding-standards/wpcs:^3 phpcompatibility/phpcompatibility-wp:"*" + - name: phpcs + run: | + "$(composer global config bin-dir --absolute --quiet)/phpcs" + js: runs-on: ubuntu-latest steps: diff --git a/README.md b/README.md index 29e9e73..e5b61ad 100644 --- a/README.md +++ b/README.md @@ -85,13 +85,16 @@ Filters: `hibira_live_post` (turn live features off per post), ## Develop ```sh -php tests/run.php # HTTP API signing, channel auth, requests (no WordPress needed) -python3 tools/compile-mo.py # languages/*.po → .mo +php tests/run.php # HTTP API signing, channel auth, requests (no WordPress needed) +phpcs # WordPress coding standards (phpcs.xml.dist) ``` -Translations: English, 繁體中文, 简体中文, 日本語, 한국어, ไทย, Tiếng Việt -(`languages/`). CI runs the tests on PHP 7.4–8.4 and the WordPress Plugin -Check. +Strings are in `languages/hibira.pot`. The plugin does not ship .po/.mo +files: WordPress.org builds and delivers them from +[translate.wordpress.org](https://translate.wordpress.org/projects/wp-plugins/hibira/). +`translations/` keeps our 繁體中文, 简体中文, 日本語, 한국어, ไทย and Tiếng Việt +.po files for importing there; they are left out of the plugin zip. CI runs the +tests on PHP 7.4–8.4, PHPCS and the WordPress Plugin Check. ## License diff --git a/assets/vendor/pusher.min.js b/assets/vendor/pusher.min.js index 6447756..0cd5533 100644 --- a/assets/vendor/pusher.min.js +++ b/assets/vendor/pusher.min.js @@ -1,3 +1,2 @@ /*! For license information please see pusher.min.js.LICENSE.txt */ !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.Pusher=e():t.Pusher=e()}(self,()=>(()=>{var t={594(t,e){"use strict";var n,i=this&&this.__extends||(n=function(t,e){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n])},n(t,e)},function(t,e){function i(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(i.prototype=e.prototype,new i)});Object.defineProperty(e,"__esModule",{value:!0});var s=256,r=function(){function t(t){void 0===t&&(t="="),this._paddingCharacter=t}return t.prototype.encodedLength=function(t){return this._paddingCharacter?(t+2)/3*4|0:(8*t+5)/6|0},t.prototype.encode=function(t){for(var e="",n=0;n>>18&63),e+=this._encodeByte(i>>>12&63),e+=this._encodeByte(i>>>6&63),e+=this._encodeByte(i>>>0&63)}var s=t.length-n;return s>0&&(i=t[n]<<16|(2===s?t[n+1]<<8:0),e+=this._encodeByte(i>>>18&63),e+=this._encodeByte(i>>>12&63),e+=2===s?this._encodeByte(i>>>6&63):this._paddingCharacter||"",e+=this._paddingCharacter||""),e},t.prototype.maxDecodedLength=function(t){return this._paddingCharacter?t/4*3|0:(6*t+7)/8|0},t.prototype.decodedLength=function(t){return this.maxDecodedLength(t.length-this._getPaddingLength(t))},t.prototype.decode=function(t){if(0===t.length)return new Uint8Array(0);for(var e=this._getPaddingLength(t),n=t.length-e,i=new Uint8Array(this.maxDecodedLength(n)),r=0,o=0,a=0,c=0,h=0,u=0,l=0;o>>4,i[r++]=h<<4|u>>>2,i[r++]=u<<6|l,a|=c&s,a|=h&s,a|=u&s,a|=l&s;if(o>>4,a|=c&s,a|=h&s),o>>2,a|=u&s),o>>8&6,e+=51-t>>>8&-75,e+=61-t>>>8&-15,e+=62-t>>>8&3,String.fromCharCode(e)},t.prototype._decodeChar=function(t){var e=s;return e+=(42-t&t-44)>>>8&-256+t-43+62,e+=(46-t&t-48)>>>8&-256+t-47+63,e+=(47-t&t-58)>>>8&-256+t-48+52,(e+=(64-t&t-91)>>>8&-256+t-65+0)+((96-t&t-123)>>>8&-256+t-97+26)},t.prototype._getPaddingLength=function(t){var e=0;if(this._paddingCharacter){for(var n=t.length-1;n>=0&&t[n]===this._paddingCharacter;n--)e++;if(t.length<4||e>2)throw new Error("Base64Coder: incorrect padding")}return e},t}();e.Coder=r;var o=new r;e.encode=function(t){return o.encode(t)},e.decode=function(t){return o.decode(t)};var a=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return i(e,t),e.prototype._encodeByte=function(t){var e=t;return e+=65,e+=25-t>>>8&6,e+=51-t>>>8&-75,e+=61-t>>>8&-13,e+=62-t>>>8&49,String.fromCharCode(e)},e.prototype._decodeChar=function(t){var e=s;return e+=(44-t&t-46)>>>8&-256+t-45+62,e+=(94-t&t-96)>>>8&-256+t-95+63,e+=(47-t&t-58)>>>8&-256+t-48+52,(e+=(64-t&t-91)>>>8&-256+t-65+0)+((96-t&t-123)>>>8&-256+t-97+26)},e}(r);e.URLSafeCoder=a;var c=new a;e.encodeURLSafe=function(t){return c.encode(t)},e.decodeURLSafe=function(t){return c.decode(t)},e.encodedLength=function(t){return o.encodedLength(t)},e.maxDecodedLength=function(t){return o.maxDecodedLength(t)},e.decodedLength=function(t){return o.decodedLength(t)}},978(t,e){"use strict";var n="utf8: invalid source encoding";e.D4=function(t){for(var e=[],i=0;i=t.length)throw new Error(n);if(128!=(192&(o=t[++i])))throw new Error(n);s=(31&s)<<6|63&o,r=128}else if(s<240){if(i>=t.length-1)throw new Error(n);var o=t[++i],a=t[++i];if(128!=(192&o)||128!=(192&a))throw new Error(n);s=(15&s)<<12|(63&o)<<6|63&a,r=2048}else{if(!(s<248))throw new Error(n);if(i>=t.length-2)throw new Error(n);o=t[++i],a=t[++i];var c=t[++i];if(128!=(192&o)||128!=(192&a)||128!=(192&c))throw new Error(n);s=(15&s)<<18|(63&o)<<12|(63&a)<<6|63&c,r=65536}if(s=55296&&s<=57343)throw new Error(n);if(s>=65536){if(s>1114111)throw new Error(n);s-=65536,e.push(String.fromCharCode(55296|s>>10)),s=56320|1023&s}}e.push(String.fromCharCode(s))}return e.join("")}},721(t,e,n){t.exports=n(207).default},207(t,e,n){"use strict";n.d(e,{default:()=>Je});class i{constructor(t,e){this.lastId=0,this.prefix=t,this.name=e}create(t){this.lastId++;var e=this.lastId,n=this.prefix+e,i=this.name+"["+e+"]",s=!1,r=function(){s||(t.apply(null,arguments),s=!0)};return this[e]=r,{number:e,id:n,name:i,callback:r}}remove(t){delete this[t.number]}}var s=new i("_pusher_script_","Pusher.ScriptReceivers");const r="8.6.0",o=7,a=80,c=443,h="",u="sockjs.pusher.com",l=80,d=443,p="/pusher",g="stats.pusher.com",f="/pusher/auth",m="ajax",b=12e4,v=3e4,y=1e4,w={endpoint:"/pusher/user-auth",transport:"ajax"},S={endpoint:"/pusher/auth",transport:"ajax"},_="http://js.pusher.com",k="https://js.pusher.com",C="";var T=new i("_pusher_dependencies","Pusher.DependenciesReceivers"),P=new class{constructor(t){this.options=t,this.receivers=t.receivers||s,this.loading={}}load(t,e,n){var i=this;if(i.loading[t]&&i.loading[t].length>0)i.loading[t].push(n);else{i.loading[t]=[n];var s=Pe.createScriptRequest(i.getPath(t,e)),r=i.receivers.create(function(e){if(i.receivers.remove(r),i.loading[t]){var n=i.loading[t];delete i.loading[t];for(var o=function(t){t||s.cleanup()},a=0;a>>6)+z(128|63&e):z(224|e>>>12&15)+z(128|e>>>6&63)+z(128|63&e)},X=function(t){var e=[0,2,1][t.length%3],n=t.charCodeAt(0)<<16|(t.length>1?t.charCodeAt(1):0)<<8|(t.length>2?t.charCodeAt(2):0);return[q.charAt(n>>>18),q.charAt(n>>>12&63),e>=2?"=":q.charAt(n>>>6&63),e>=1?"=":q.charAt(63&n)].join("")},J="undefined"!=typeof window&&window.btoa||function(t){return t.replace(/[\s\S]{1,3}/g,X)};const W=class{constructor(t,e,n,i){this.clear=e,this.timer=t(()=>{this.timer&&(this.timer=i(this.timer))},n)}isRunning(){return null!==this.timer}ensureAborted(){this.timer&&(this.clear(this.timer),this.timer=null)}};function G(t){window.clearTimeout(t)}function Y(t){window.clearInterval(t)}class K extends W{constructor(t,e){super(setTimeout,G,t,function(t){return e(),null})}}class Q extends W{constructor(t,e){super(setInterval,Y,t,function(t){return e(),t})}}var V={now:()=>Date.now?Date.now():(new Date).valueOf(),defer:t=>new K(0,t),method(t,...e){var n=Array.prototype.slice.call(arguments,1);return function(e){return e[t].apply(e,n.concat(arguments))}}};const Z=V;function tt(t,...e){for(var n=0;n{window.console&&window.console.log&&window.console.log(t)}}debug(...t){this.log(this.globalLog,t)}warn(...t){this.log(this.globalLogWarn,t)}error(...t){this.log(this.globalLogError,t)}globalLogWarn(t){window.console&&window.console.warn?window.console.warn(t):this.globalLog(t)}globalLogError(t){window.console&&window.console.error?window.console.error(t):this.globalLogWarn(t)}log(t,...e){var n=et.apply(this,arguments);Je.log?Je.log(n):Je.logToConsole&&t.bind(this)(n)}},pt=function(t,e,n,i,s){void 0===n.headers&&null==n.headersProvider||dt.warn(`To send headers with the ${i.toString()} request, you must use AJAX, rather than JSONP.`);var r=t.nextAuthCallbackID.toString();t.nextAuthCallbackID++;var o=t.getDocument(),a=o.createElement("script");t.auth_callbacks[r]=function(t){s(null,t)};var c="Pusher.auth_callbacks['"+r+"']";a.src=n.endpoint+"?callback="+encodeURIComponent(c)+"&"+e;var h=o.getElementsByTagName("head")[0]||o.documentElement;h.insertBefore(a,h.firstChild)};class gt{constructor(t){this.src=t}send(t){var e=this,n="Error loading "+e.src;e.script=document.createElement("script"),e.script.id=t.id,e.script.src=e.src,e.script.type="text/javascript",e.script.charset="UTF-8",e.script.addEventListener?(e.script.onerror=function(){t.callback(n)},e.script.onload=function(){t.callback(null)}):e.script.onreadystatechange=function(){"loaded"!==e.script.readyState&&"complete"!==e.script.readyState||t.callback(null)},void 0===e.script.async&&document.attachEvent&&/opera/i.test(navigator.userAgent)?(e.errorScript=document.createElement("script"),e.errorScript.id=t.id+"_error",e.errorScript.text=t.name+"('"+n+"');",e.script.async=e.errorScript.async=!1):e.script.async=!0;var i=document.getElementsByTagName("head")[0];i.insertBefore(e.script,i.firstChild),e.errorScript&&i.insertBefore(e.errorScript,e.script.nextSibling)}cleanup(){this.script&&(this.script.onload=this.script.onerror=null,this.script.onreadystatechange=null),this.script&&this.script.parentNode&&this.script.parentNode.removeChild(this.script),this.errorScript&&this.errorScript.parentNode&&this.errorScript.parentNode.removeChild(this.errorScript),this.script=null,this.errorScript=null}}class ft{constructor(t,e){this.url=t,this.data=e}send(t){if(!this.request){var e=(i=this.data,ot((s=ut(ct(i,function(t){return void 0!==t})),r=[],it(s,function(t,e){r.push([e,t])}),r),Z.method("join","=")).join("&")),n=this.url+"/"+t.number+"?"+e;this.request=Pe.createScriptRequest(n),this.request.send(t)}var i,s,r}cleanup(){this.request&&this.request.cleanup()}}const mt={name:"jsonp",getAgent:function(t,e){return function(n,i){var r="http"+(e?"s":"")+"://"+(t.host||t.options.host)+t.options.path,o=Pe.createJSONPRequest(r,n),a=Pe.ScriptReceivers.create(function(e,n){s.remove(a),o.cleanup(),n&&n.host&&(t.host=n.host),i&&i(e,n)});o.send(a)}}};function bt(t,e,n){return t+(e.useTLS?"s":"")+"://"+(e.useTLS?e.hostTLS:e.hostNonTLS)+n}function vt(t,e){return"/app/"+t+"?protocol="+o+"&client=js&version="+r+(e?"&"+e:"")}var yt={getInitial:function(t,e){return bt("ws",e,(e.httpPath||"")+vt(t,"flash=false"))}},wt={getInitial:function(t,e){return bt("http",e,(e.httpPath||"/pusher")+vt(t))}},St={getInitial:function(t,e){return bt("http",e,e.httpPath||"/pusher")},getPath:function(t,e){return vt(t)}};class _t{constructor(){this._callbacks={}}get(t){return this._callbacks[kt(t)]}add(t,e,n){var i=kt(t);this._callbacks[i]=this._callbacks[i]||[],this._callbacks[i].push({fn:e,context:n})}remove(t,e,n){if(t||e||n){var i=t?[kt(t)]:st(this._callbacks);e||n?this.removeCallback(i,e,n):this.removeAllCallbacks(i)}else this._callbacks={}}removeCallback(t,e,n){rt(t,function(t){this._callbacks[t]=at(this._callbacks[t]||[],function(t){return e&&e!==t.fn||n&&n!==t.context}),0===this._callbacks[t].length&&delete this._callbacks[t]},this)}removeAllCallbacks(t){rt(t,function(t){delete this._callbacks[t]},this)}}function kt(t){return"_"+t}class Ct{constructor(t){this.callbacks=new _t,this.global_callbacks=[],this.failThrough=t}bind(t,e,n){return this.callbacks.add(t,e,n),this}bind_global(t){return this.global_callbacks.push(t),this}unbind(t,e,n){return this.callbacks.remove(t,e,n),this}unbind_global(t){return t?(this.global_callbacks=at(this.global_callbacks||[],e=>e!==t),this):(this.global_callbacks=[],this)}unbind_all(){return this.unbind(),this.unbind_global(),this}emit(t,e,n){for(var i=0;i0)for(i=0;i{this.onError(t),this.changeState("closed")}),!1}return this.bindListeners(),dt.debug("Connecting",{transport:this.name,url:t}),this.changeState("connecting"),!0}close(){return!!this.socket&&(this.socket.close(),!0)}send(t){return"open"===this.state&&(Z.defer(()=>{this.socket&&this.socket.send(t)}),!0)}ping(){"open"===this.state&&this.supportsPing()&&this.socket.ping()}onOpen(){this.hooks.beforeOpen&&this.hooks.beforeOpen(this.socket,this.hooks.urls.getPath(this.key,this.options)),this.changeState("open"),this.socket.onopen=void 0}onError(t){this.emit("error",{type:"WebSocketError",error:t}),this.timeline.error(this.buildTimelineMessage({error:t.toString()}))}onClose(t){t?this.changeState("closed",{code:t.code,reason:t.reason,wasClean:t.wasClean}):this.changeState("closed"),this.unbindListeners(),this.socket=void 0}onMessage(t){this.emit("message",t)}onActivity(){this.emit("activity")}bindListeners(){this.socket.onopen=()=>{this.onOpen()},this.socket.onerror=t=>{this.onError(t)},this.socket.onclose=t=>{this.onClose(t)},this.socket.onmessage=t=>{this.onMessage(t)},this.supportsPing()&&(this.socket.onactivity=()=>{this.onActivity()})}unbindListeners(){this.socket&&(this.socket.onopen=void 0,this.socket.onerror=void 0,this.socket.onclose=void 0,this.socket.onmessage=void 0,this.supportsPing()&&(this.socket.onactivity=void 0))}changeState(t,e){this.state=t,this.timeline.info(this.buildTimelineMessage({state:t,params:e})),this.emit(t,e)}buildTimelineMessage(t){return tt({cid:this.id},t)}}class Pt{constructor(t){this.hooks=t}isSupported(t){return this.hooks.isSupported(t)}createConnection(t,e,n,i){return new Tt(this.hooks,t,e,n,i)}}var xt=new Pt({urls:yt,handlesActivityChecks:!1,supportsPing:!1,isInitialized:function(){return Boolean(Pe.getWebSocketAPI())},isSupported:function(){return Boolean(Pe.getWebSocketAPI())},getSocket:function(t){return Pe.createWebSocket(t)}}),Et={urls:wt,handlesActivityChecks:!1,supportsPing:!0,isInitialized:function(){return!0}},Ot=tt({getSocket:function(t){return Pe.HTTPFactory.createStreamingSocket(t)}},Et),Lt=tt({getSocket:function(t){return Pe.HTTPFactory.createPollingSocket(t)}},Et),At={isSupported:function(){return Pe.isXHRSupported()}};const Rt={ws:xt,xhr_streaming:new Pt(tt({},Ot,At)),xhr_polling:new Pt(tt({},Lt,At))};var It=new Pt({file:"sockjs",urls:St,handlesActivityChecks:!0,supportsPing:!1,isSupported:function(){return!0},isInitialized:function(){return void 0!==window.SockJS},getSocket:function(t,e){return new window.SockJS(t,null,{js_path:P.getPath("sockjs",{useTLS:e.useTLS}),ignore_null_origin:e.ignoreNullOrigin})},beforeOpen:function(t,e){t.send(JSON.stringify({path:e}))}}),Dt={isSupported:function(t){return Pe.isXDRSupported(t.useTLS)}},jt=new Pt(tt({},Ot,Dt)),Nt=new Pt(tt({},Lt,Dt));Rt.xdr_streaming=jt,Rt.xdr_polling=Nt,Rt.sockjs=It;const Ut=Rt;var Ht=new class extends Ct{constructor(){super();var t=this;"undefined"!=typeof window&&void 0!==window.addEventListener&&(window.addEventListener("online",function(){t.emit("online")},!1),window.addEventListener("offline",function(){t.emit("offline")},!1))}isOnline(){return void 0===window.navigator.onLine||window.navigator.onLine}};class Mt{constructor(t,e,n){this.manager=t,this.transport=e,this.minPingDelay=n.minPingDelay,this.maxPingDelay=n.maxPingDelay,this.pingDelay=void 0}createConnection(t,e,n,i){i=tt({},i,{activityTimeout:this.pingDelay});var s=this.transport.createConnection(t,e,n,i),r=null,o=function(){s.unbind("open",o),s.bind("closed",a),r=Z.now()},a=t=>{if(s.unbind("closed",a),1002===t.code||1003===t.code)this.manager.reportDeath();else if(!t.wasClean&&r){var e=Z.now()-r;e<2*this.maxPingDelay&&(this.manager.reportDeath(),this.pingDelay=Math.max(e/2,this.minPingDelay))}};return s.bind("open",o),s}isSupported(t){return this.manager.isAlive()&&this.transport.isSupported(t)}}const zt={decodeMessage:function(t){try{var e=JSON.parse(t.data),n=e.data;if("string"==typeof n)try{n=JSON.parse(e.data)}catch(t){}var i={event:e.event,channel:e.channel,data:n};return e.user_id&&(i.user_id=e.user_id),i}catch(e){throw{type:"MessageParseError",error:e,data:t.data}}},encodeMessage:function(t){return JSON.stringify(t)},processHandshake:function(t){var e=zt.decodeMessage(t);if("pusher:connection_established"===e.event){if(!e.data.activity_timeout)throw"No activity timeout specified in handshake";return{action:"connected",id:e.data.socket_id,activityTimeout:1e3*e.data.activity_timeout}}if("pusher:error"===e.event)return{action:this.getCloseAction(e.data),error:this.getCloseError(e.data)};throw"Invalid handshake"},getCloseAction:function(t){return t.code<4e3?t.code>=1002&&t.code<=1004?"backoff":null:4e3===t.code?"tls_only":t.code<4100?"refused":t.code<4200?"backoff":t.code<4300?"retry":"refused"},getCloseError:function(t){return 1e3!==t.code&&1001!==t.code?{type:"PusherError",data:{code:t.code,message:t.reason||t.message}}:null}},qt=zt;class Bt extends Ct{constructor(t,e){super(),this.id=t,this.transport=e,this.activityTimeout=e.activityTimeout,this.bindListeners()}handlesActivityChecks(){return this.transport.handlesActivityChecks()}send(t){return this.transport.send(t)}send_event(t,e,n){var i={event:t,data:e};return n&&(i.channel=n),dt.debug("Event sent",i),this.send(qt.encodeMessage(i))}ping(){this.transport.supportsPing()?this.transport.ping():this.send_event("pusher:ping",{})}close(){this.transport.close()}bindListeners(){var t={message:t=>{var e;try{e=qt.decodeMessage(t)}catch(e){this.emit("error",{type:"MessageParseError",error:e,data:t.data})}if(void 0!==e){switch(dt.debug("Event recd",e),e.event){case"pusher:error":this.emit("error",{type:"PusherError",data:e.data});break;case"pusher:ping":this.emit("ping");break;case"pusher:pong":this.emit("pong")}this.emit("message",e)}},activity:()=>{this.emit("activity")},error:t=>{this.emit("error",t)},closed:t=>{e(),t&&t.code&&this.handleCloseEvent(t),this.transport=null,this.emit("closed")}},e=()=>{it(t,(t,e)=>{this.transport.unbind(e,t)})};it(t,(t,e)=>{this.transport.bind(e,t)})}handleCloseEvent(t){var e=qt.getCloseAction(t),n=qt.getCloseError(t);n&&this.emit("error",n),e&&this.emit(e,{action:e,error:n})}}class $t{constructor(t,e){this.transport=t,this.callback=e,this.bindListeners()}close(){this.unbindListeners(),this.transport.close()}bindListeners(){this.onMessage=t=>{var e;this.unbindListeners();try{e=qt.processHandshake(t)}catch(t){return this.finish("error",{error:t}),void this.transport.close()}"connected"===e.action?this.finish("connected",{connection:new Bt(e.id,this.transport),activityTimeout:e.activityTimeout}):(this.finish(e.action,{error:e.error}),this.transport.close())},this.onClosed=t=>{this.unbindListeners();var e=qt.getCloseAction(t)||"backoff",n=qt.getCloseError(t);this.finish(e,{error:n})},this.transport.bind("message",this.onMessage),this.transport.bind("closed",this.onClosed)}unbindListeners(){this.transport.unbind("message",this.onMessage),this.transport.unbind("closed",this.onClosed)}finish(t,e){this.callback(tt({transport:this.transport,action:t},e))}}class Ft{constructor(t,e){this.timeline=t,this.options=e||{}}send(t,e){this.timeline.isEmpty()||this.timeline.send(Pe.TimelineTransport.getAgent(this,t),e)}}class Xt extends Ct{constructor(t,e){super(function(e,n){dt.debug("No callbacks on "+t+" for "+e)}),this.name=t,this.pusher=e,this.subscribed=!1,this.subscriptionPending=!1,this.subscriptionCancelled=!1}authorize(t,e){return e(null,{auth:""})}trigger(t,e){if(0!==t.indexOf("client-"))throw new L("Event '"+t+"' does not start with 'client-'");if(!this.subscribed){var n=E("triggeringClientEvents");dt.warn(`Client event triggered before channel 'subscription_succeeded' event . ${n}`)}return this.pusher.send_event(t,e,this.name)}disconnect(){this.subscribed=!1,this.subscriptionPending=!1}handleEvent(t){var e=t.event,n=t.data;"pusher_internal:subscription_succeeded"===e?this.handleSubscriptionSucceededEvent(t):"pusher_internal:subscription_count"===e?this.handleSubscriptionCountEvent(t):0!==e.indexOf("pusher_internal:")&&this.emit(e,n,{})}handleSubscriptionSucceededEvent(t){this.subscriptionPending=!1,this.subscribed=!0,this.subscriptionCancelled?this.pusher.unsubscribe(this.name):this.emit("pusher:subscription_succeeded",t.data)}handleSubscriptionCountEvent(t){t.data.subscription_count&&(this.subscriptionCount=t.data.subscription_count),this.emit("pusher:subscription_count",t.data)}subscribe(){this.subscribed||(this.subscriptionPending=!0,this.subscriptionCancelled=!1,this.authorize(this.pusher.connection.socket_id,(t,e)=>{t?(this.subscriptionPending=!1,dt.error(t.toString()),this.emit("pusher:subscription_error",Object.assign({},{type:"AuthError",error:t.message},t instanceof H?{status:t.status}:{}))):this.pusher.send_event("pusher:subscribe",{auth:e.auth,channel_data:e.channel_data,channel:this.name})}))}unsubscribe(){this.subscribed=!1,this.pusher.send_event("pusher:unsubscribe",{channel:this.name})}cancelSubscription(){this.subscriptionCancelled=!0}reinstateSubscription(){this.subscriptionCancelled=!1}}class Jt extends Xt{authorize(t,e){return this.pusher.config.channelAuthorizer({channelName:this.name,socketId:t},e)}}class Wt{constructor(){this.reset()}get(t){return Object.prototype.hasOwnProperty.call(this.members,t)?{id:t,info:this.members[t]}:null}each(t){it(this.members,(e,n)=>{t(this.get(n))})}setMyID(t){this.myID=t}onSubscription(t){this.members=t.presence.hash,this.count=t.presence.count,this.me=this.get(this.myID)}addMember(t){return null===this.get(t.user_id)&&this.count++,this.members[t.user_id]=t.user_info,this.get(t.user_id)}removeMember(t){var e=this.get(t.user_id);return e&&(delete this.members[t.user_id],this.count--),e}reset(){this.members={},this.count=0,this.myID=null,this.me=null}}class Gt extends Jt{constructor(t,e){super(t,e),this.members=new Wt}authorize(t,e){super.authorize(t,(t,n)=>{return i=this,s=void 0,o=function*(){if(!t)if(null!=n.channel_data){var i=JSON.parse(n.channel_data);this.members.setMyID(i.user_id)}else{if(yield this.pusher.user.signinDonePromise,null==this.pusher.user.user_data){let t=E("authorizationEndpoint");return dt.error(`Invalid auth response for channel '${this.name}', expected 'channel_data' field. ${t}, or the user should be signed in.`),void e("Invalid auth response")}this.members.setMyID(this.pusher.user.user_data.id)}e(t,n)},new((r=void 0)||(r=Promise))(function(t,e){function n(t){try{c(o.next(t))}catch(t){e(t)}}function a(t){try{c(o.throw(t))}catch(t){e(t)}}function c(e){var i;e.done?t(e.value):(i=e.value,i instanceof r?i:new r(function(t){t(i)})).then(n,a)}c((o=o.apply(i,s||[])).next())});var i,s,r,o})}handleEvent(t){var e=t.event;if(0===e.indexOf("pusher_internal:"))this.handleInternalEvent(t);else{var n=t.data,i={};t.user_id&&(i.user_id=t.user_id),this.emit(e,n,i)}}handleInternalEvent(t){var e=t.event,n=t.data;switch(e){case"pusher_internal:subscription_succeeded":this.handleSubscriptionSucceededEvent(t);break;case"pusher_internal:subscription_count":this.handleSubscriptionCountEvent(t);break;case"pusher_internal:member_added":var i=this.members.addMember(n);this.emit("pusher:member_added",i);break;case"pusher_internal:member_removed":var s=this.members.removeMember(n);s&&this.emit("pusher:member_removed",s)}}handleSubscriptionSucceededEvent(t){this.subscriptionPending=!1,this.subscribed=!0,this.subscriptionCancelled?this.pusher.unsubscribe(this.name):(this.members.onSubscription(t.data),this.emit("pusher:subscription_succeeded",this.members))}disconnect(){this.members.reset(),super.disconnect()}}var Yt=n(978),Kt=n(594);class Qt extends Jt{constructor(t,e,n){super(t,e),this.key=null,this.nacl=n}authorize(t,e){super.authorize(t,(t,n)=>{if(t)return void e(t,n);let i=n.shared_secret;i?(this.key=(0,Kt.decode)(i),delete n.shared_secret,e(null,n)):e(new Error(`No shared_secret key in auth payload for encrypted channel: ${this.name}`),null)})}trigger(t,e){throw new j("Client events are not currently supported for encrypted channels")}handleEvent(t){var e=t.event,n=t.data;0!==e.indexOf("pusher_internal:")&&0!==e.indexOf("pusher:")?this.handleEncryptedEvent(e,n):super.handleEvent(t)}handleEncryptedEvent(t,e){if(!this.key)return void dt.debug("Received encrypted event before key has been retrieved from the authEndpoint");if(!e.ciphertext||!e.nonce)return void dt.error("Unexpected format for encrypted event, expected object with `ciphertext` and `nonce` fields, got: "+e);let n=(0,Kt.decode)(e.ciphertext);if(n.length{e?dt.error(`Failed to make a request to the authEndpoint: ${r}. Unable to fetch new key, so dropping encrypted event`):(s=this.nacl.secretbox.open(n,i,this.key),null!==s?this.emit(t,this.getDataToEmit(s)):dt.error("Failed to decrypt event with new key. Dropping encrypted event"))});this.emit(t,this.getDataToEmit(s))}getDataToEmit(t){let e=(0,Yt.D4)(t);try{return JSON.parse(e)}catch(t){return e}}}class Vt extends Ct{constructor(t,e){super(),this.state="initialized",this.connection=null,this.key=t,this.options=e,this.timeline=this.options.timeline,this.usingTLS=this.options.useTLS,this.errorCallbacks=this.buildErrorCallbacks(),this.connectionCallbacks=this.buildConnectionCallbacks(this.errorCallbacks),this.handshakeCallbacks=this.buildHandshakeCallbacks(this.errorCallbacks);var n=Pe.getNetwork();n.bind("online",()=>{this.timeline.info({netinfo:"online"}),"connecting"!==this.state&&"unavailable"!==this.state||this.retryIn(0)}),n.bind("offline",()=>{this.timeline.info({netinfo:"offline"}),this.connection&&this.sendActivityCheck()}),this.updateStrategy()}switchCluster(t){this.key=t,this.updateStrategy(),this.retryIn(0)}connect(){this.connection||this.runner||(this.strategy.isSupported()?(this.updateState("connecting"),this.startConnecting(),this.setUnavailableTimer()):this.updateState("failed"))}send(t){return!!this.connection&&this.connection.send(t)}send_event(t,e,n){return!!this.connection&&this.connection.send_event(t,e,n)}disconnect(){this.disconnectInternally(),this.updateState("disconnected")}isUsingTLS(){return this.usingTLS}startConnecting(){var t=(e,n)=>{e?this.runner=this.strategy.connect(0,t):"error"===n.action?(this.emit("error",{type:"HandshakeError",error:n.error}),this.timeline.error({handshakeError:n.error})):(this.abortConnecting(),this.handshakeCallbacks[n.action](n))};this.runner=this.strategy.connect(0,t)}abortConnecting(){this.runner&&(this.runner.abort(),this.runner=null)}disconnectInternally(){this.abortConnecting(),this.clearRetryTimer(),this.clearUnavailableTimer(),this.connection&&this.abandonConnection().close()}updateStrategy(){this.strategy=this.options.getStrategy({key:this.key,timeline:this.timeline,useTLS:this.usingTLS})}retryIn(t){this.timeline.info({action:"retry",delay:t}),t>0&&this.emit("connecting_in",Math.round(t/1e3)),this.retryTimer=new K(t||0,()=>{this.disconnectInternally(),this.connect()})}clearRetryTimer(){this.retryTimer&&(this.retryTimer.ensureAborted(),this.retryTimer=null)}setUnavailableTimer(){this.unavailableTimer=new K(this.options.unavailableTimeout,()=>{this.updateState("unavailable")})}clearUnavailableTimer(){this.unavailableTimer&&this.unavailableTimer.ensureAborted()}sendActivityCheck(){this.stopActivityCheck(),this.connection.ping(),this.activityTimer=new K(this.options.pongTimeout,()=>{this.timeline.error({pong_timed_out:this.options.pongTimeout}),this.retryIn(0)})}resetActivityCheck(){this.stopActivityCheck(),this.connection&&!this.connection.handlesActivityChecks()&&(this.activityTimer=new K(this.activityTimeout,()=>{this.sendActivityCheck()}))}stopActivityCheck(){this.activityTimer&&this.activityTimer.ensureAborted()}buildConnectionCallbacks(t){return tt({},t,{message:t=>{this.resetActivityCheck(),this.emit("message",t)},ping:()=>{this.send_event("pusher:pong",{})},activity:()=>{this.resetActivityCheck()},error:t=>{this.emit("error",t)},closed:()=>{this.abandonConnection(),this.shouldRetry()&&this.retryIn(1e3)}})}buildHandshakeCallbacks(t){return tt({},t,{connected:t=>{this.activityTimeout=Math.min(this.options.activityTimeout,t.activityTimeout,t.connection.activityTimeout||1/0),this.clearUnavailableTimer(),this.setConnection(t.connection),this.socket_id=this.connection.id,this.updateState("connected",{socket_id:this.socket_id})}})}buildErrorCallbacks(){let t=t=>e=>{e.error&&this.emit("error",{type:"WebSocketError",error:e.error}),t(e)};return{tls_only:t(()=>{this.usingTLS=!0,this.updateStrategy(),this.retryIn(0)}),refused:t(()=>{this.disconnect()}),backoff:t(()=>{this.retryIn(1e3)}),retry:t(()=>{this.retryIn(0)})}}setConnection(t){for(var e in this.connection=t,this.connectionCallbacks)this.connection.bind(e,this.connectionCallbacks[e]);this.resetActivityCheck()}abandonConnection(){if(this.connection){for(var t in this.stopActivityCheck(),this.connectionCallbacks)this.connection.unbind(t,this.connectionCallbacks[t]);var e=this.connection;return this.connection=null,e}}updateState(t,e){var n=this.state;if(this.state=t,n!==t){var i=t;"connected"===i&&(i+=" with new socket ID "+e.socket_id),dt.debug("State changed",n+" -> "+i),this.timeline.info({state:t,params:e}),this.emit("state_change",{previous:n,current:t}),this.emit(t,e)}}shouldRetry(){return"connecting"===this.state||"connected"===this.state}}class Zt{constructor(){this.channels={}}add(t,e){return this.channels[t]||(this.channels[t]=function(t,e){if(0===t.indexOf("private-encrypted-")){if(e.config.nacl)return ee.createEncryptedChannel(t,e,e.config.nacl);let n="Tried to subscribe to a private-encrypted- channel but no nacl implementation available",i=E("encryptedChannelSupport");throw new j(`${n}. ${i}`)}if(0===t.indexOf("private-"))return ee.createPrivateChannel(t,e);if(0===t.indexOf("presence-"))return ee.createPresenceChannel(t,e);if(0===t.indexOf("#"))throw new A('Cannot create a channel with name "'+t+'".');return ee.createChannel(t,e)}(t,e)),this.channels[t]}all(){return t=this.channels,e=[],it(t,function(t){e.push(t)}),e;var t,e}find(t){return this.channels[t]}remove(t){var e=this.channels[t];return delete this.channels[t],e}disconnect(){it(this.channels,function(t){t.disconnect()})}}var te={createChannels:()=>new Zt,createConnectionManager:(t,e)=>new Vt(t,e),createChannel:(t,e)=>new Xt(t,e),createPrivateChannel:(t,e)=>new Jt(t,e),createPresenceChannel:(t,e)=>new Gt(t,e),createEncryptedChannel:(t,e,n)=>new Qt(t,e,n),createTimelineSender:(t,e)=>new Ft(t,e),createHandshake:(t,e)=>new $t(t,e),createAssistantToTheTransportManager:(t,e,n)=>new Mt(t,e,n)};const ee=te;class ne{constructor(t){this.options=t||{},this.livesLeft=this.options.lives||1/0}getAssistant(t){return ee.createAssistantToTheTransportManager(this,t,{minPingDelay:this.options.minPingDelay,maxPingDelay:this.options.maxPingDelay})}isAlive(){return this.livesLeft>0}reportDeath(){this.livesLeft-=1}}class ie{constructor(t,e){this.strategies=t,this.loop=Boolean(e.loop),this.failFast=Boolean(e.failFast),this.timeout=e.timeout,this.timeoutLimit=e.timeoutLimit}isSupported(){return ht(this.strategies,Z.method("isSupported"))}connect(t,e){var n=this.strategies,i=0,s=this.timeout,r=null,o=(a,c)=>{c?e(null,c):(i+=1,this.loop&&(i%=n.length),i0&&(s=new K(n.timeout,function(){r.abort(),i(!0)})),r=t.connect(e,function(t,e){t&&s&&s.isRunning()&&!n.failFast||(s&&s.ensureAborted(),i(t,e))}),{abort:function(){s&&s.ensureAborted(),r.abort()},forceMinPriority:function(t){r.forceMinPriority(t)}}}}class se{constructor(t){this.strategies=t}isSupported(){return ht(this.strategies,Z.method("isSupported"))}connect(t,e){return function(t,n){var i=ot(t,function(t,i,s,r){return t.connect(n,function(t,n){return function(i,s){n[t].error=i,i?function(t){return function(t,e){for(var n=0;n=Z.now()){var o=this.transports[i.transport];o&&(["ws","wss"].includes(i.transport)||s>3?(this.timeline.info({cached:!0,transport:i.transport,latency:i.latency}),r.push(new ie([o],{timeout:2*i.latency+1e3,failFast:!0}))):s++)}var a=Z.now(),c=r.pop().connect(t,function i(o,h){o?(ce(n),r.length>0?(a=Z.now(),c=r.pop().connect(t,i)):e(o)):(function(t,e,n,i){var s=Pe.getLocalStorage();if(s)try{s[ae(t)]=lt({timestamp:Z.now(),transport:e,latency:n,cacheSkipCount:i})}catch(t){}}(n,h.transport.name,Z.now()-a,s),e(null,h))});return{abort:function(){c.abort()},forceMinPriority:function(e){t=e,c&&c.forceMinPriority(e)}}}}function ae(t){return"pusherTransport"+(t?"TLS":"NonTLS")}function ce(t){var e=Pe.getLocalStorage();if(e)try{delete e[ae(t)]}catch(t){}}class he{constructor(t,{delay:e}){this.strategy=t,this.options={delay:e}}isSupported(){return this.strategy.isSupported()}connect(t,e){var n,i=this.strategy,s=new K(this.options.delay,function(){n=i.connect(t,e)});return{abort:function(){s.ensureAborted(),n&&n.abort()},forceMinPriority:function(e){t=e,n&&n.forceMinPriority(e)}}}}class ue{constructor(t,e,n){this.test=t,this.trueBranch=e,this.falseBranch=n}isSupported(){return(this.test()?this.trueBranch:this.falseBranch).isSupported()}connect(t,e){return(this.test()?this.trueBranch:this.falseBranch).connect(t,e)}}class le{constructor(t){this.strategy=t}isSupported(){return this.strategy.isSupported()}connect(t,e){var n=this.strategy.connect(t,function(t,i){i&&n.abort(),e(t,i)});return n}}function de(t){return function(){return t.isSupported()}}const pe={getRequest:function(t){var e=new window.XDomainRequest;return e.ontimeout=function(){t.emit("error",new R),t.close()},e.onerror=function(e){t.emit("error",e),t.close()},e.onprogress=function(){e.responseText&&e.responseText.length>0&&t.onChunk(200,e.responseText)},e.onload=function(){e.responseText&&e.responseText.length>0&&t.onChunk(200,e.responseText),t.emit("finished",200),t.close()},e},abortRequest:function(t){t.ontimeout=t.onerror=t.onprogress=t.onload=null,t.abort()}};class ge extends Ct{constructor(t,e,n){super(),this.hooks=t,this.method=e,this.url=n}start(t){this.position=0,this.xhr=this.hooks.getRequest(this),this.unloader=()=>{this.close()},Pe.addUnloadListener(this.unloader),this.xhr.open(this.method,this.url,!0),this.xhr.setRequestHeader&&this.xhr.setRequestHeader("Content-Type","application/json"),this.xhr.send(t)}close(){this.unloader&&(Pe.removeUnloadListener(this.unloader),this.unloader=null),this.xhr&&(this.hooks.abortRequest(this.xhr),this.xhr=null)}onChunk(t,e){for(;;){var n=this.advanceBuffer(e);if(!n)break;this.emit("chunk",{status:t,data:n})}this.isBufferTooLong(e)&&this.emit("buffer_too_long")}advanceBuffer(t){var e=t.slice(this.position),n=e.indexOf("\n");return-1!==n?(this.position+=n+1,e.slice(0,n)):null}isBufferTooLong(t){return this.position===t.length&&t.length>262144}}var fe;!function(t){t[t.CONNECTING=0]="CONNECTING",t[t.OPEN=1]="OPEN",t[t.CLOSED=3]="CLOSED"}(fe||(fe={}));const me=fe;var be=1;function ve(t){var e=-1===t.indexOf("?")?"?":"&";return t+e+"t="+ +new Date+"&n="+be++}function ye(t){return Pe.randomInt(t)}const we=class{constructor(t,e){this.hooks=t,this.session=ye(1e3)+"/"+function(){for(var t=[],e=0;e<8;e++)t.push(ye(32).toString(32));return t.join("")}(),this.location=function(t){var e=/([^\?]*)\/*(\??.*)/.exec(t);return{base:e[1],queryString:e[2]}}(e),this.readyState=me.CONNECTING,this.openStream()}send(t){return this.sendRaw(JSON.stringify([t]))}ping(){this.hooks.sendHeartbeat(this)}close(t,e){this.onClose(t,e,!0)}sendRaw(t){if(this.readyState!==me.OPEN)return!1;try{return Pe.createSocketRequest("POST",ve((e=this.location,n=this.session,e.base+"/"+n+"/xhr_send"))).start(t),!0}catch(t){return!1}var e,n}reconnect(){this.closeStream(),this.openStream()}onClose(t,e,n){this.closeStream(),this.readyState=me.CLOSED,this.onclose&&this.onclose({code:t,reason:e,wasClean:n})}onChunk(t){var e;if(200===t.status)switch(this.readyState===me.OPEN&&this.onActivity(),t.data.slice(0,1)){case"o":e=JSON.parse(t.data.slice(1)||"{}"),this.onOpen(e);break;case"a":e=JSON.parse(t.data.slice(1)||"[]");for(var n=0;n{this.onChunk(t)}),this.stream.bind("finished",t=>{this.hooks.onFinished(this,t)}),this.stream.bind("buffer_too_long",()=>{this.reconnect()});try{this.stream.start()}catch(t){Z.defer(()=>{this.onError(t),this.onClose(1006,"Could not start streaming",!1)})}}closeStream(){this.stream&&(this.stream.unbind_all(),this.stream.close(),this.stream=null)}},Se={getReceiveURL:function(t,e){return t.base+"/"+e+"/xhr_streaming"+t.queryString},onHeartbeat:function(t){t.sendRaw("[]")},sendHeartbeat:function(t){t.sendRaw("[]")},onFinished:function(t,e){t.onClose(1006,"Connection interrupted ("+e+")",!1)}},_e={getReceiveURL:function(t,e){return t.base+"/"+e+"/xhr"+t.queryString},onHeartbeat:function(){},sendHeartbeat:function(t){t.sendRaw("[]")},onFinished:function(t,e){200===e?t.reconnect():t.onClose(1006,"Connection interrupted ("+e+")",!1)}},ke={getRequest:function(t){var e=new(Pe.getXHRAPI());return e.onreadystatechange=e.onprogress=function(){switch(e.readyState){case 3:e.responseText&&e.responseText.length>0&&t.onChunk(e.status,e.responseText);break;case 4:e.responseText&&e.responseText.length>0&&t.onChunk(e.status,e.responseText),t.emit("finished",e.status),t.close()}},e},abortRequest:function(t){t.onreadystatechange=null,t.abort()}};const Ce={createStreamingSocket(t){return this.createSocket(Se,t)},createPollingSocket(t){return this.createSocket(_e,t)},createSocket:(t,e)=>new we(t,e),createXHR(t,e){return this.createRequest(ke,t,e)},createRequest:(t,e,n)=>new ge(t,e,n),createXDR:function(t,e){return this.createRequest(pe,t,e)}};var Te={nextAuthCallbackID:1,auth_callbacks:{},ScriptReceivers:s,DependenciesReceivers:T,getDefaultStrategy:function(t,e,n){var i={};function s(e,s,r,o,a){var c=n(t,e,s,r,o,a);return i[e]=c,c}var r,o=Object.assign({},e,{hostNonTLS:t.wsHost+":"+t.wsPort,hostTLS:t.wsHost+":"+t.wssPort,httpPath:t.wsPath}),a=Object.assign({},o,{useTLS:!0}),c=Object.assign({},e,{hostNonTLS:t.httpHost+":"+t.httpPort,hostTLS:t.httpHost+":"+t.httpsPort,httpPath:t.httpPath}),h={loop:!0,timeout:15e3,timeoutLimit:6e4},u=new ne({minPingDelay:1e4,maxPingDelay:t.activityTimeout}),l=new ne({lives:2,minPingDelay:1e4,maxPingDelay:t.activityTimeout}),d=s("ws","ws",3,o,u),p=s("wss","ws",3,a,u),g=s("sockjs","sockjs",1,c),f=s("xhr_streaming","xhr_streaming",1,c,l),m=s("xdr_streaming","xdr_streaming",1,c,l),b=s("xhr_polling","xhr_polling",1,c),v=s("xdr_polling","xdr_polling",1,c),y=new ie([d],h),w=new ie([p],h),S=new ie([g],h),_=new ie([new ue(de(f),f,m)],h),k=new ie([new ue(de(b),b,v)],h),C=new ie([new ue(de(_),new se([_,new he(k,{delay:4e3})]),k)],h),T=new ue(de(C),C,S);return r=e.useTLS?new se([y,new he(T,{delay:2e3})]):new se([y,new he(w,{delay:2e3}),new he(T,{delay:5e3})]),new oe(new le(new ue(de(d),r,T)),i,{ttl:18e5,timeline:e.timeline,useTLS:e.useTLS})},Transports:Ut,transportConnectionInitializer:function(){var t=this;t.timeline.info(t.buildTimelineMessage({transport:t.name+(t.options.useTLS?"s":"")})),t.hooks.isInitialized()?t.changeState("initialized"):t.hooks.file?(t.changeState("initializing"),P.load(t.hooks.file,{useTLS:t.options.useTLS},function(e,n){t.hooks.isInitialized()?(t.changeState("initialized"),n(!0)):(e&&t.onError(e),t.onClose(),n(!1))})):t.onClose()},HTTPFactory:Ce,TimelineTransport:mt,getXHRAPI:()=>window.XMLHttpRequest,getWebSocketAPI:()=>window.WebSocket||window.MozWebSocket,setup(t){if("undefined"!=typeof window){window.Pusher=t;var e=()=>{this.onDocumentBody(t.ready)};window.JSON?e():P.load("json2",{},e)}},getDocument:()=>document,getProtocol(){return this.getDocument().location.protocol},getAuthorizers:()=>({ajax:M,jsonp:pt}),onDocumentBody(t){document.body?t():setTimeout(()=>{this.onDocumentBody(t)},0)},createJSONPRequest:(t,e)=>new ft(t,e),createScriptRequest:t=>new gt(t),getLocalStorage(){try{return window.localStorage}catch(t){return}},createXHR(){return this.getXHRAPI()?this.createXMLHttpRequest():this.createMicrosoftXHR()},createXMLHttpRequest(){return new(this.getXHRAPI())},createMicrosoftXHR:()=>new ActiveXObject("Microsoft.XMLHTTP"),getNetwork:()=>Ht,createWebSocket(t){return new(this.getWebSocketAPI())(t)},createSocketRequest(t,e){if(this.isXHRSupported())return this.HTTPFactory.createXHR(t,e);if(this.isXDRSupported(0===e.indexOf("https:")))return this.HTTPFactory.createXDR(t,e);throw"Cross-origin HTTP requests are not supported"},isXHRSupported(){var t=this.getXHRAPI();return Boolean(t)&&void 0!==(new t).withCredentials},isXDRSupported(t){var e=t?"https:":"http:",n=this.getProtocol();return Boolean(window.XDomainRequest)&&n===e},addUnloadListener(t){void 0!==window.addEventListener?window.addEventListener("pagehide",t,!1):void 0!==window.attachEvent&&window.attachEvent("onunload",t)},removeUnloadListener(t){void 0!==window.addEventListener?window.removeEventListener("pagehide",t,!1):void 0!==window.detachEvent&&window.detachEvent("onunload",t)},randomInt(t){const e=window.crypto||window.msCrypto,n=Math.floor(Math.pow(2,32)/t)*t;let i;do{i=e.getRandomValues(new Uint32Array(1))[0]}while(i>=n);return i%t}};const Pe=Te;var xe;!function(t){t[t.ERROR=3]="ERROR",t[t.INFO=6]="INFO",t[t.DEBUG=7]="DEBUG"}(xe||(xe={}));const Ee=xe;class Oe{constructor(t,e,n){this.key=t,this.session=e,this.events=[],this.options=n||{},this.sent=0,this.uniqueID=0}log(t,e){t<=this.options.level&&(this.events.push(tt({},e,{timestamp:Z.now()})),this.options.limit&&this.events.length>this.options.limit&&this.events.shift())}error(t){this.log(Ee.ERROR,t)}info(t){this.log(Ee.INFO,t)}debug(t){this.log(Ee.DEBUG,t)}isEmpty(){return 0===this.events.length}send(t,e){var n=tt({session:this.session,bundle:this.sent+1,key:this.key,lib:"js",version:this.options.version,cluster:this.options.cluster,features:this.options.features,timeline:this.events},this.options.params);return this.events=[],t(n,(t,n)=>{t||this.sent++,e&&e(t,n)}),!0}generateUniqueID(){return this.uniqueID++,this.uniqueID}}class Le{constructor(t,e,n,i){this.name=t,this.priority=e,this.transport=n,this.options=i||{}}isSupported(){return this.transport.isSupported({useTLS:this.options.useTLS})}connect(t,e){if(!this.isSupported())return Ae(new U,e);if(this.priority{n||(h(),s?s.close():i.close())},forceMinPriority:t=>{n||this.priority"customHandler"in t&&null!=t.customHandler;function qe(t){const e=Object.assign(Object.assign({},w),t.userAuthentication);return ze(e)?e.customHandler:(t=>{if(void 0===Pe.getAuthorizers()[t.transport])throw`'${t.transport}' is not a recognized auth transport`;return(e,n)=>{const i=((t,e)=>{var n="socket_id="+encodeURIComponent(t.socketId);for(var i in e.params)n+="&"+encodeURIComponent(i)+"="+encodeURIComponent(e.params[i]);if(null!=e.paramsProvider){let t=e.paramsProvider();for(var i in t)n+="&"+encodeURIComponent(i)+"="+encodeURIComponent(t[i])}return n})(e,t);Pe.getAuthorizers()[t.transport](Pe,i,t,O.UserAuthentication,n)}})(e)}function Be(t,e){const n=function(t,e){let n;return"channelAuthorization"in t?n=Object.assign(Object.assign({},S),t.channelAuthorization):(n={transport:t.authTransport||m,endpoint:t.authEndpoint||f},"auth"in t&&("params"in t.auth&&(n.params=t.auth.params),"headers"in t.auth&&(n.headers=t.auth.headers)),"authorizer"in t&&(n.customHandler=((t,e,n)=>{const i={authTransport:e.transport,authEndpoint:e.endpoint,auth:{params:e.params,headers:e.headers}};return(e,s)=>{const r=t.channel(e.channelName);n(r,i).authorize(e.socketId,s)}})(e,n,t.authorizer))),n}(t,e);return ze(n)?n.customHandler:(t=>{if(void 0===Pe.getAuthorizers()[t.transport])throw`'${t.transport}' is not a recognized auth transport`;return(e,n)=>{const i=((t,e)=>{var n="socket_id="+encodeURIComponent(t.socketId);for(var i in n+="&channel_name="+encodeURIComponent(t.channelName),e.params)n+="&"+encodeURIComponent(i)+"="+encodeURIComponent(e.params[i]);if(null!=e.paramsProvider){let t=e.paramsProvider();for(var i in t)n+="&"+encodeURIComponent(i)+"="+encodeURIComponent(t[i])}return n})(e,t);Pe.getAuthorizers()[t.transport](Pe,i,t,O.ChannelAuthorization,n)}})(n)}class $e extends Ct{constructor(t){super(function(t,e){dt.debug(`No callbacks on watchlist events for ${t}`)}),this.pusher=t,this.bindWatchlistInternalEvent()}handleEvent(t){t.data.events.forEach(t=>{this.emit(t.name,t)})}bindWatchlistInternalEvent(){this.pusher.connection.bind("message",t=>{"pusher_internal:watchlist_events"===t.event&&this.handleEvent(t)})}}class Fe extends Ct{constructor(t){super(function(t,e){dt.debug("No callbacks on user for "+t)}),this.signin_requested=!1,this.user_data=null,this.serverToUserChannel=null,this.signinDonePromise=null,this._signinDoneResolve=null,this._onAuthorize=(t,e)=>{if(t)return dt.warn(`Error during signin: ${t}`),this.emit("pusher:signin_error",Object.assign({},{type:"AuthError",error:t.message},t instanceof H?{status:t.status}:{})),void this._cleanup();this.pusher.send_event("pusher:signin",{auth:e.auth,user_data:e.user_data})},this.pusher=t,this.pusher.connection.bind("state_change",({previous:t,current:e})=>{"connected"!==t&&"connected"===e&&this._signin(),"connected"===t&&"connected"!==e&&(this._cleanup(),this._newSigninPromiseIfNeeded())}),this.watchlist=new $e(t),this.pusher.connection.bind("message",t=>{"pusher:signin_success"===t.event&&this._onSigninSuccess(t.data),this.serverToUserChannel&&this.serverToUserChannel.name===t.channel&&this.serverToUserChannel.handleEvent(t)})}signin(){this.signin_requested||(this.signin_requested=!0,this._signin())}_signin(){this.signin_requested&&(this._newSigninPromiseIfNeeded(),"connected"===this.pusher.connection.state&&this.pusher.config.userAuthenticator({socketId:this.pusher.connection.socket_id},this._onAuthorize))}_onSigninSuccess(t){try{this.user_data=JSON.parse(t.user_data)}catch(e){return dt.error(`Failed parsing user data after signin: ${t.user_data}`),void this._cleanup()}if("string"!=typeof this.user_data.id||""===this.user_data.id)return dt.error(`user_data doesn't contain an id. user_data: ${this.user_data}`),void this._cleanup();this._signinDoneResolve(),this._subscribeChannels()}_subscribeChannels(){this.serverToUserChannel=new Xt(`#server-to-user-${this.user_data.id}`,this.pusher),this.serverToUserChannel.bind_global((t,e)=>{0!==t.indexOf("pusher_internal:")&&0!==t.indexOf("pusher:")&&this.emit(t,e)}),(t=>{t.subscriptionPending&&t.subscriptionCancelled?t.reinstateSubscription():t.subscriptionPending||"connected"!==this.pusher.connection.state||t.subscribe()})(this.serverToUserChannel)}_cleanup(){this.user_data=null,this.serverToUserChannel&&(this.serverToUserChannel.unbind_all(),this.serverToUserChannel.disconnect(),this.serverToUserChannel=null),this.signin_requested&&this._signinDoneResolve()}_newSigninPromiseIfNeeded(){if(!this.signin_requested)return;if(this.signinDonePromise&&!this.signinDonePromise.done)return;const{promise:t,resolve:e,reject:n}=function(){let t,e;return{promise:new Promise((n,i)=>{t=n,e=i}),resolve:t,reject:e}}();t.done=!1;const i=()=>{t.done=!0};t.then(i).catch(i),this.signinDonePromise=t,this._signinDoneResolve=e}}class Xe{static ready(){Xe.isReady=!0;for(var t=0,e=Xe.instances.length;tPe.getDefaultStrategy(this.config,t,Ie),timeline:this.timeline,activityTimeout:this.config.activityTimeout,pongTimeout:this.config.pongTimeout,unavailableTimeout:this.config.unavailableTimeout,useTLS:Boolean(this.config.useTLS)}),this.connection.bind("connected",()=>{this.subscribeAll(),this.timelineSender&&this.timelineSender.send(this.connection.isUsingTLS())}),this.connection.bind("message",t=>{var e=0===t.event.indexOf("pusher_internal:");if(t.channel){var n=this.channel(t.channel);n&&n.handleEvent(t)}e||this.global_emitter.emit(t.event,t.data)}),this.connection.bind("connecting",()=>{this.channels.disconnect()}),this.connection.bind("disconnected",()=>{this.channels.disconnect()}),this.connection.bind("error",t=>{dt.warn(t)}),Xe.instances.push(this),this.timeline.info({instances:Xe.instances.length}),this.user=new Fe(this),Xe.isReady&&this.connect()}switchCluster(t){const{appKey:e,cluster:n}=t;this.key=e,this.options=Object.assign(Object.assign({},this.options),{cluster:n}),this.config=je(this.options,this),this.connection.switchCluster(this.key)}channel(t){return this.channels.find(t)}allChannels(){return this.channels.all()}connect(){if(this.connection.connect(),this.timelineSender&&!this.timelineSenderTimer){var t=this.connection.isUsingTLS(),e=this.timelineSender;this.timelineSenderTimer=new Q(6e4,function(){e.send(t)})}}disconnect(){this.connection.disconnect(),this.timelineSenderTimer&&(this.timelineSenderTimer.ensureAborted(),this.timelineSenderTimer=null)}bind(t,e,n){return this.global_emitter.bind(t,e,n),this}unbind(t,e,n){return this.global_emitter.unbind(t,e,n),this}bind_global(t){return this.global_emitter.bind_global(t),this}unbind_global(t){return this.global_emitter.unbind_global(t),this}unbind_all(t){return this.global_emitter.unbind_all(),this}subscribeAll(){var t;for(t in this.channels.channels)this.channels.channels.hasOwnProperty(t)&&this.subscribe(t)}subscribe(t){var e=this.channels.add(t,this);return e.subscriptionPending&&e.subscriptionCancelled?e.reinstateSubscription():e.subscriptionPending||"connected"!==this.connection.state||e.subscribe(),e}unsubscribe(t){var e=this.channels.find(t);e&&e.subscriptionPending?e.cancelSubscription():(e=this.channels.remove(t))&&e.subscribed&&e.unsubscribe()}send_event(t,e,n){return this.connection.send_event(t,e,n)}shouldUseTLS(){return this.config.useTLS}signin(){this.user.signin()}}Xe.instances=[],Xe.isReady=!1,Xe.logToConsole=!1,Xe.Runtime=Pe,Xe.ScriptReceivers=Pe.ScriptReceivers,Xe.DependenciesReceivers=Pe.DependenciesReceivers,Xe.auth_callbacks=Pe.auth_callbacks;const Je=Xe;Pe.setup(Xe)}},e={};function n(i){var s=e[i];if(void 0!==s)return s.exports;var r=e[i]={exports:{}};return t[i].call(r.exports,r,r.exports,n),r.exports}return n.d=(t,e)=>{for(var i in e)n.o(e,i)&&!n.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:e[i]})},n.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),n(721)})()); -//# sourceMappingURL=pusher.min.js.map \ No newline at end of file diff --git a/hibira.php b/hibira.php index 96c6f2f..39f2442 100644 --- a/hibira.php +++ b/hibira.php @@ -3,7 +3,7 @@ * Plugin Name: Hibira Realtime * Plugin URI: https://github.com/hibira-io/wordpress * Description: Live comments, "this post was updated" notices, a readers-now count and live WooCommerce order status, on Hibira's Pusher-compatible realtime service. - * Version: 0.1.1 + * Version: 0.1.2 * Requires at least: 6.0 * Requires PHP: 7.4 * Author: Hibira @@ -18,7 +18,7 @@ defined( 'ABSPATH' ) || exit; -define( 'HIBIRA_VERSION', '0.1.1' ); +define( 'HIBIRA_VERSION', '0.1.2' ); define( 'HIBIRA_FILE', __FILE__ ); define( 'HIBIRA_DIR', plugin_dir_path( __FILE__ ) ); define( 'HIBIRA_URL', plugin_dir_url( __FILE__ ) ); @@ -32,13 +32,6 @@ require_once HIBIRA_DIR . 'includes/class-hibira-admin-notices.php'; require_once HIBIRA_DIR . 'includes/class-hibira-woocommerce.php'; -add_action( - 'init', - function () { - load_plugin_textdomain( 'hibira', false, dirname( plugin_basename( HIBIRA_FILE ) ) . '/languages' ); - } -); - Hibira_Settings::init(); Hibira_Auth::init(); Hibira_Posts::init(); diff --git a/includes/class-hibira-auth.php b/includes/class-hibira-auth.php index e0b2dba..690fdc7 100644 --- a/includes/class-hibira-auth.php +++ b/includes/class-hibira-auth.php @@ -18,10 +18,23 @@ class Hibira_Auth { + /** + * Decisions made during this request, by request object. + * + * @var array + */ + private static $decisions = array(); + + /** + * Hooks the REST route. + */ public static function init() { add_action( 'rest_api_init', array( __CLASS__, 'routes' ) ); } + /** + * Registers POST /wp-json/hibira/v1/auth. + */ public static function routes() { register_rest_route( 'hibira/v1', @@ -29,35 +42,72 @@ public static function routes() { array( 'methods' => 'POST', 'callback' => array( __CLASS__, 'auth' ), - 'permission_callback' => '__return_true', // Each channel is checked in decide(). + 'permission_callback' => array( __CLASS__, 'permission' ), 'args' => array( 'socket_id' => array( - 'type' => 'string', - 'required' => true, + 'type' => 'string', + 'required' => true, + 'validate_callback' => array( 'Hibira_Client', 'valid_socket_id' ), ), 'channel_name' => array( - 'type' => 'string', - 'required' => true, + 'type' => 'string', + 'required' => true, + 'validate_callback' => array( 'Hibira_Client', 'valid_channel' ), + ), + 'order_key' => array( + 'type' => 'string', + 'default' => '', + 'sanitize_callback' => 'sanitize_text_field', + ), + 'visitor' => array( + 'type' => 'string', + 'default' => '', + 'sanitize_callback' => 'sanitize_text_field', ), ), ) ); } + /** + * Lets the request through only for a channel the visitor may join. Pusher + * clients call this endpoint for visitors too (Readers now, guest order + * pages), so the check is per channel rather than per capability. + * + * @param WP_REST_Request $request The auth request. + * @return true|WP_Error + */ + public static function permission( WP_REST_Request $request ) { + if ( ! Hibira_Settings::configured() ) { + return new WP_Error( 'hibira_config', __( 'Hibira is not configured.', 'hibira' ), array( 'status' => 503 ) ); + } + $decision = self::decision( $request ); + if ( false === $decision || null === $decision ) { + return new WP_Error( 'hibira_forbidden', __( 'You cannot subscribe to this channel.', 'hibira' ), array( 'status' => 403 ) ); + } + return true; + } + + /** + * Signs the subscription. + * + * @param WP_REST_Request $request The auth request. + * @return WP_REST_Response|WP_Error + */ public static function auth( WP_REST_Request $request ) { $client = hibira(); if ( ! $client ) { - return new WP_Error( 'hibira_config', 'Hibira is not configured.', array( 'status' => 503 ) ); + return new WP_Error( 'hibira_config', __( 'Hibira is not configured.', 'hibira' ), array( 'status' => 503 ) ); } $channel = (string) $request['channel_name']; - $decision = self::decide( $channel, $request ); + $decision = self::decision( $request ); if ( false === $decision || null === $decision ) { - return new WP_Error( 'hibira_forbidden', 'Forbidden', array( 'status' => 403 ) ); + return new WP_Error( 'hibira_forbidden', __( 'You cannot subscribe to this channel.', 'hibira' ), array( 'status' => 403 ) ); } $presence = null; if ( 0 === strpos( $channel, 'presence-' ) ) { if ( ! is_array( $decision ) || empty( $decision['user_id'] ) ) { - return new WP_Error( 'hibira_forbidden', 'Presence channels need a user ID.', array( 'status' => 403 ) ); + return new WP_Error( 'hibira_forbidden', __( 'Presence channels need a user ID.', 'hibira' ), array( 'status' => 403 ) ); } $presence = $decision; } @@ -71,6 +121,20 @@ public static function auth( WP_REST_Request $request ) { return $response; } + /** + * The decision for a request, made once and shared by permission() and auth(). + * + * @param WP_REST_Request $request The auth request. + * @return bool|array|null + */ + private static function decision( WP_REST_Request $request ) { + $id = spl_object_hash( $request ); + if ( ! array_key_exists( $id, self::$decisions ) ) { + self::$decisions[ $id ] = self::decide( (string) $request['channel_name'], $request ); + } + return self::$decisions[ $id ]; + } + /** * Whether the current visitor may subscribe. * diff --git a/includes/class-hibira-client.php b/includes/class-hibira-client.php index 34eb67b..098d8d8 100644 --- a/includes/class-hibira-client.php +++ b/includes/class-hibira-client.php @@ -120,7 +120,7 @@ public function authorize_channel( $socket_id, $channel, $presence = null ) { $to_sign = $socket_id . ':' . $channel; $out = array(); if ( null !== $presence ) { - $channel_data = wp_json_encode( + $channel_data = wp_json_encode( array( 'user_id' => (string) $presence['user_id'], 'user_info' => isset( $presence['user_info'] ) ? (object) $presence['user_info'] : new stdClass(), diff --git a/includes/class-hibira-comments.php b/includes/class-hibira-comments.php index 5af1b85..3791fed 100644 --- a/includes/class-hibira-comments.php +++ b/includes/class-hibira-comments.php @@ -30,16 +30,16 @@ public static function transition( $new_status, $old_status, $comment ) { /** The event data: what a reader's browser needs to show the comment. */ public static function payload( WP_Comment $comment ) { $data = array( - 'id' => (int) $comment->comment_ID, - 'parent' => (int) $comment->comment_parent, - 'author' => get_comment_author( $comment ), - 'url' => get_comment_author_url( $comment ), - 'avatar' => get_avatar_url( $comment, array( 'size' => 64 ) ), - 'date' => get_comment_date( '', $comment ) . ' ' . get_comment_time( '', false, true, $comment ), - 'iso' => get_comment_date( 'c', $comment ), - 'link' => get_comment_link( $comment ), - // Comment text is filtered by kses when saved; comment_text adds the formatting. - 'html' => apply_filters( 'comment_text', get_comment_text( $comment ), $comment, array() ), + 'id' => (int) $comment->comment_ID, + 'parent' => (int) $comment->comment_parent, + 'author' => get_comment_author( $comment ), + 'url' => get_comment_author_url( $comment ), + 'avatar' => get_avatar_url( $comment, array( 'size' => 64 ) ), + 'date' => get_comment_date( '', $comment ) . ' ' . get_comment_time( '', false, true, $comment ), + 'iso' => get_comment_date( 'c', $comment ), + 'link' => get_comment_link( $comment ), + // Core's comment_text filter adds the formatting; kses again before it leaves the site. + 'html' => wp_kses_post( apply_filters( 'comment_text', get_comment_text( $comment ), $comment, array() ) ), // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- core filter. ); /** * Changes the data sent for a live comment. Keep it under 10 KB. diff --git a/includes/class-hibira-posts.php b/includes/class-hibira-posts.php index b7d5228..d4e669b 100644 --- a/includes/class-hibira-posts.php +++ b/includes/class-hibira-posts.php @@ -102,10 +102,3 @@ public static function readers_html() { return ''; } } - -/** - * Prints the readers-now count of the current post (when Readers now is on). - */ -function hibira_readers() { - echo Hibira_Posts::readers_html(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- fixed markup. -} diff --git a/includes/class-hibira-settings.php b/includes/class-hibira-settings.php index 1a75895..71e4d18 100644 --- a/includes/class-hibira-settings.php +++ b/includes/class-hibira-settings.php @@ -95,11 +95,12 @@ public static function sanitize( $in ) { foreach ( array( 'app_id', 'key', 'host' ) as $f ) { $out[ $f ] = isset( $in[ $f ] ) ? sanitize_text_field( wp_unslash( $in[ $f ] ) ) : ''; } + $out['host'] = self::sanitize_host( $out['host'] ); if ( '' === $out['host'] ) { $out['host'] = 'ws.hibira.io'; } // An empty secret field keeps the saved secret; the form never shows it. - $secret = isset( $in['secret'] ) ? trim( (string) wp_unslash( $in['secret'] ) ) : ''; + $secret = isset( $in['secret'] ) ? sanitize_text_field( wp_unslash( $in['secret'] ) ) : ''; $out['secret'] = '' !== $secret ? $secret : ( isset( $old['secret'] ) ? (string) $old['secret'] : '' ); $out['port'] = isset( $in['port'] ) ? max( 1, min( 65535, (int) $in['port'] ) ) : 443; foreach ( array( 'tls', 'live_comments', 'post_updates', 'readers', 'woo_orders', 'admin_notices' ) as $f ) { @@ -108,13 +109,35 @@ public static function sanitize( $in ) { return $out; } + /** + * A host name only, also from a pasted URL: no scheme, port or path. + * + * @param string $host What was typed. + * @return string + */ + public static function sanitize_host( $host ) { + $host = trim( (string) $host ); + if ( false !== strpos( $host, '://' ) ) { + $host = (string) wp_parse_url( $host, PHP_URL_HOST ); + } + $host = preg_replace( '#[/:?\#].*$#', '', $host ); + return strtolower( preg_replace( '/[^A-Za-z0-9.\-]/', '', $host ) ); + } + /** Sends a test event and lists channels, which checks the ID, key and secret. */ public static function test() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You are not allowed to do this.', 'hibira' ), 403 ); } check_admin_referer( 'hibira_test' ); - $result = hibira_trigger( 'hibira-test', 'test', array( 'from' => 'wordpress', 'at' => time() ) ); + $result = hibira_trigger( + 'hibira-test', + 'test', + array( + 'from' => 'wordpress', + 'at' => time(), + ) + ); if ( ! is_wp_error( $result ) ) { $result = hibira()->get( '/channels' ); } diff --git a/includes/class-hibira-woocommerce.php b/includes/class-hibira-woocommerce.php index ab899f7..57afc4e 100644 --- a/includes/class-hibira-woocommerce.php +++ b/includes/class-hibira-woocommerce.php @@ -15,7 +15,7 @@ class Hibira_WooCommerce { public static function init() { - add_action( 'woocommerce_order_status_changed', array( __CLASS__, 'status_changed' ), 10, 4 ); + add_action( 'woocommerce_order_status_changed', array( __CLASS__, 'status_changed' ), 10, 3 ); add_action( 'woocommerce_checkout_order_processed', array( __CLASS__, 'new_order' ), 10, 1 ); add_action( 'woocommerce_store_api_checkout_order_processed', array( __CLASS__, 'new_order' ), 10, 1 ); add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue' ) ); @@ -42,7 +42,7 @@ public static function can_watch_order( $order_id, $user, $order_key ) { return '' !== $order_key && hash_equals( (string) $order->get_order_key(), $order_key ); } - public static function status_changed( $order_id, $from, $to, $order = null ) { + public static function status_changed( $order_id, $from, $to ) { if ( ! Hibira_Settings::enabled( 'woo_orders' ) ) { return; } @@ -85,7 +85,7 @@ public static function enqueue() { $key = ''; if ( is_order_received_page() ) { $order_id = absint( get_query_var( 'order-received' ) ); - $key = isset( $_GET['key'] ) ? wc_clean( wp_unslash( $_GET['key'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- WooCommerce's own order received URL. + $key = isset( $_GET['key'] ) ? sanitize_text_field( wp_unslash( $_GET['key'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- WooCommerce's own order received URL. $order = wc_get_order( $order_id ); } elseif ( is_view_order_page() ) { $order = wc_get_order( absint( get_query_var( 'view-order' ) ) ); diff --git a/includes/functions.php b/includes/functions.php index 26f126d..fff7095 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -13,10 +13,10 @@ * @return Hibira_Client|null */ function hibira() { - static $client = null; + static $client = null; static $signature = null; - $s = Hibira_Settings::get(); - $sig = md5( wp_json_encode( array( $s['app_id'], $s['key'], $s['secret'], $s['host'], $s['port'], $s['tls'] ) ) ); + $s = Hibira_Settings::get(); + $sig = md5( wp_json_encode( array( $s['app_id'], $s['key'], $s['secret'], $s['host'], $s['port'], $s['tls'] ) ) ); if ( $sig !== $signature ) { $signature = $sig; $client = Hibira_Settings::configured() @@ -85,15 +85,15 @@ function hibira_register_client_script() { 'hibira', 'HibiraConfig', array( - 'key' => $s['key'], - 'host' => $s['host'], - 'port' => (int) $s['port'], - 'tls' => (bool) $s['tls'], - 'authUrl' => esc_url_raw( rest_url( 'hibira/v1/auth' ) ), + 'key' => $s['key'], + 'host' => $s['host'], + 'port' => (int) $s['port'], + 'tls' => (bool) $s['tls'], + 'authUrl' => esc_url_raw( rest_url( 'hibira/v1/auth' ) ), // Only signed-in users send a nonce: a cached page can hold a stale // one, which the REST API refuses even for visitors. - 'nonce' => is_user_logged_in() ? wp_create_nonce( 'wp_rest' ) : '', - 'i18n' => array( + 'nonce' => is_user_logged_in() ? wp_create_nonce( 'wp_rest' ) : '', + 'i18n' => array( 'updated' => __( 'This post was updated.', 'hibira' ), 'reload' => __( 'Reload', 'hibira' ), 'newComment' => __( 'New comment', 'hibira' ), @@ -112,3 +112,10 @@ function hibira_register_client_script() { ) ); } + +/** + * Prints the readers-now count of the current post (when Readers now is on). + */ +function hibira_readers() { + echo Hibira_Posts::readers_html(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- fixed markup. +} diff --git a/languages/hibira-ja.mo b/languages/hibira-ja.mo deleted file mode 100644 index 8279d8ca6ffaf602449a94a9481cca8e5994ca35..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4360 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 R2`TQNJZh!phmBT~`9F1Hw7UQR diff --git a/languages/hibira-th.mo b/languages/hibira-th.mo deleted file mode 100644 index 298a40ef01088cdcb09f6c1e89acc620dd7b2725..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5741 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 diff --git a/languages/hibira-vi.mo b/languages/hibira-vi.mo deleted file mode 100644 index 2e0d3919504b933587beb347d38e7007e115a0d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4142 zcmZ{mU2Ggz6~}LB2w)0<@Ld7DL^8z1J5D8pB1Z@u$4N|Lh~lLLMJT$vd%ZLD?#yK7 zuDAP$LRD2IQX+^d6r#qYQ7P0)qVOTSs*tjgdEuEyq!ox~cqtO%f#12ayIv=*wch`H z+7_AJ^()n zPCTj9kHKfa3GfZD4Bi1h0PfkN)Ca+*z|Vo70{N&JzF4BZ28#W|;0xe!@WbFea1wk6 zdsjAC&dq0zU(u0OkD!@I1H*{ulf+D7-yDG$Hs` zumpZ{UlC^$oaFu@_$>GuxC%Z1)nu+SYidzk*KyKT&mB7S)g2G!YRxF-m5Zu2S958q z7mo>K$(pU{YSL`l*yY-!sWq8iNiuEMY}WBMY^IYH?Lw>5BzL+UxiD!tZFt#8Of69J zp{=g^1=S>uZPi6dtmcbLTMP7@c9xA;jM;8-oy&zwiY=jGPeUIFqe_&Sk}N%NM0b*w zu9}#oR!2?~yaZ6Ql~$c_<2-3tiHc>JL_J(^(iJ9Wx-)ahu=ju=;cVX_#nG4Gc^Jf>zLPR?)6`q4M6lQXVGy+_;hknPewP~BkiE-u7RxWqfsr>SH+aCuR*cRIRFhT@Uh4)mh)4|2)3VR06d zrD2(xItn3lh$IwYGDSmDKGbK9V2VA3qC5vI!b3>)6rG0C z4x|;R=dq@~pCt3rt1?I%->rVY8HIMKI@4$*ZDK$eHR&-CM%#H%94v?upH=Zs4VhMh zGHtXdku1>jbfGmNYR!c)qbt$e@YqTX*~282L zyO*oQ$l3i>IqdzGg_YiVqA#}ko407@S-<=0a1I^qy&Z?T!JCa3b7K8gRo7abe)nZM zZ3M(RL*-$jn>dSh4*>@^Bi-!X)T@QVYJc-hT;VX%jsE6(6sS`eyVS(J(X0KD@<%c-Ajkah?bp!b4qSVV&T*uSL4r@4jhtzxx)CYyIwRz-1Vf7uUEc)WjLa0xx6V zlR~gNb~Xw6rC6t7Z$lVZkNm9LNle(^>@x32#ZM!h8|Glxdn?s*{@1~@W?1Pp*esf= zTqbh-Uv>iF>wa8U<=zHIjvrp)dOqB`ON#oNw_7^&2_43})LZXhn$!9DwbmQv;z8N}c#`2s{5SY4dt=Xeq>O}GRQ|ZJk^8tR!sFlFe0IP?>==wlddD) z1RAP3-a;}(ItyaPruSomJv8KRY;+Gg_;B(coZ2zVg{5Mai$`dr%XxYEhKxpKfse+w zuyLB{e)pvAM}nlx=!v`N+(}HLg==);qmti&uH)WKT1~(E8zTuU+W)x4K%KMA_bweh z_On18^>QCJIwWyX=E#p?-RRwHio@>9Fp0xQeiX=Xq0!!Mq8lMw;(Rp@lxRbN`ai2!-#7pO diff --git a/languages/hibira-zh_CN.mo b/languages/hibira-zh_CN.mo deleted file mode 100644 index 2a9cc6ba873b87e65d6b944b8161d8e80c086741..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3744 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\n" +"Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"POT-Creation-Date: 2026-11-02T15:17:04+00:00\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"X-Generator: WP-CLI 2.12.0\n" "X-Domain: hibira\n" +#. Plugin Name of the plugin +#: hibira.php +msgid "Hibira Realtime" +msgstr "" + +#. Plugin URI of the plugin +#: hibira.php +msgid "https://github.com/hibira-io/wordpress" +msgstr "" + +#. Description of the plugin +#: hibira.php +msgid "Live comments, \"this post was updated\" notices, a readers-now count and live WooCommerce order status, on Hibira's Pusher-compatible realtime service." +msgstr "" + +#. Author of the plugin +#: hibira.php +msgid "Hibira" +msgstr "" + +#. Author URI of the plugin +#: hibira.php +msgid "https://hibira.io/?utm_source=wordpress&utm_medium=plugin&utm_campaign=plugins" +msgstr "" + +#: hibira.php:45 msgid "Settings" msgstr "" +#: includes/class-hibira-auth.php:82 +#: includes/class-hibira-auth.php:100 +#: includes/functions.php:47 +msgid "Hibira is not configured." +msgstr "" + +#: includes/class-hibira-auth.php:86 +#: includes/class-hibira-auth.php:105 +msgid "You cannot subscribe to this channel." +msgstr "" + +#: includes/class-hibira-auth.php:110 +msgid "Presence channels need a user ID." +msgstr "" + +#: includes/class-hibira-settings.php:116 msgid "You are not allowed to do this." msgstr "" +#: includes/class-hibira-settings.php:132 msgid "Connected. A test event was sent to the channel \"hibira-test\"; you can see it in the debug console of your app in the Hibira portal." msgstr "" +#. translators: %s: link to the Hibira portal +#: includes/class-hibira-settings.php:160 +#, php-format msgid "Create a free app at %s (no card needed), then copy its app ID, key and secret here." msgstr "" +#: includes/class-hibira-settings.php:167 msgid "Connection" msgstr "" +#: includes/class-hibira-settings.php:170 msgid "App ID" msgstr "" +#: includes/class-hibira-settings.php:174 msgid "Key" msgstr "" +#: includes/class-hibira-settings.php:178 msgid "Secret" msgstr "" +#: includes/class-hibira-settings.php:180 msgid "Saved. Leave empty to keep it." msgstr "" +#: includes/class-hibira-settings.php:181 msgid "Never sent to browsers. To keep it out of the database, define HIBIRA_APP_SECRET in wp-config.php instead." msgstr "" +#: includes/class-hibira-settings.php:185 msgid "Host" msgstr "" +#: includes/class-hibira-settings.php:188 msgid "ws.hibira.io for the default cluster. Change it only for another cluster or a self-hosted server." msgstr "" +#: includes/class-hibira-settings.php:192 msgid "Port" msgstr "" +#: includes/class-hibira-settings.php:195 msgid "TLS (https and wss)" msgstr "" +#: includes/class-hibira-settings.php:200 msgid "Features" msgstr "" +#: includes/class-hibira-settings.php:204 msgid "Live comments" msgstr "" +#: includes/class-hibira-settings.php:204 msgid "Approved comments appear for every reader of the post without a reload." msgstr "" +#: includes/class-hibira-settings.php:205 msgid "Post updated notice" msgstr "" +#: includes/class-hibira-settings.php:205 msgid "Readers of a post see a notice with a reload button when you update it." msgstr "" +#: includes/class-hibira-settings.php:206 msgid "Readers now" msgstr "" +#: includes/class-hibira-settings.php:206 msgid "Shows how many people are reading a post. Each reader holds a presence channel connection; counts above 100 show as 100+. It shows in a corner of the page, or where you put the [hibira_readers] shortcode or the hibira_readers() template tag." msgstr "" +#: includes/class-hibira-settings.php:207 msgid "WooCommerce order status" msgstr "" +#: includes/class-hibira-settings.php:207 msgid "The order received and view order pages update when the order status changes." msgstr "" +#: includes/class-hibira-settings.php:208 msgid "Admin notices" msgstr "" +#: includes/class-hibira-settings.php:208 msgid "Shows a notice in wp-admin for new orders and for comments awaiting approval." msgstr "" +#: includes/class-hibira-settings.php:231 msgid "Check the connection" msgstr "" +#: includes/class-hibira-settings.php:235 msgid "Send a test event" msgstr "" -msgid "Hibira is not configured." -msgstr "" - +#: includes/functions.php:97 msgid "This post was updated." msgstr "" +#: includes/functions.php:98 msgid "Reload" msgstr "" +#: includes/functions.php:99 msgid "New comment" msgstr "" +#. translators: %s: number of readers +#: includes/functions.php:101 +#, php-format msgid "%s reading now" msgstr "" +#: includes/functions.php:102 msgid "100+ reading now" msgstr "" +#. translators: %s: order status name +#: includes/functions.php:104 +#, php-format msgid "Order status: %s" msgstr "" +#. translators: %s: order number +#: includes/functions.php:106 +#, php-format msgid "New order %s" msgstr "" +#. translators: %s: post title +#: includes/functions.php:108 +#, php-format msgid "New comment awaiting approval on \"%s\"" msgstr "" +#: includes/functions.php:109 msgid "View" msgstr "" +#: includes/functions.php:110 msgid "Dismiss" msgstr "" - diff --git a/phpcs.xml.dist b/phpcs.xml.dist new file mode 100644 index 0000000..2abb958 --- /dev/null +++ b/phpcs.xml.dist @@ -0,0 +1,42 @@ + + + WordPress coding standards for Hibira Realtime. + + . + /tests/* + /vendor/* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/readme.txt b/readme.txt index 51be0c3..e721b8b 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Tags: realtime, live comments, websocket, woocommerce, notifications Requires at least: 6.0 Tested up to: 7.1 Requires PHP: 7.4 -Stable tag: 0.1.1 +Stable tag: 0.1.2 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html @@ -32,6 +32,12 @@ This plugin connects to Hibira (ws.hibira.io by default, or the host you set): Hibira's [terms](https://hibira.io/legal/terms) and [privacy policy](https://hibira.io/legal/privacy). += Source code and libraries = + +The plugin's source, with its tests, is at [github.com/hibira-io/wordpress](https://github.com/hibira-io/wordpress). + +`assets/vendor/pusher.min.js` is the minified build of [pusher-js](https://github.com/pusher/pusher-js) 8.6.0 (MIT), unchanged except for the removed source map comment. Its source is at [github.com/pusher/pusher-js](https://github.com/pusher/pusher-js), and the same file can be built with `npm install pusher-js@8.6.0` (`dist/web/pusher.min.js`). + == Installation == 1. Install and activate the plugin. @@ -64,6 +70,11 @@ Live comments use WordPress's classic comment markup (`li.comment > article.comm == Changelog == += 0.1.2 = +* Translations come from translate.wordpress.org; the plugin no longer ships .po/.mo files or calls load_plugin_textdomain(). +* The auth endpoint checks each channel in its permission callback and validates its parameters. +* Stricter sanitizing of settings and of live comment HTML. + = 0.1.1 = * README: cover image and screenshots. diff --git a/tools/compile-mo.py b/tools/compile-mo.py deleted file mode 100644 index e6e7cbf..0000000 --- a/tools/compile-mo.py +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env python3 -"""Compiles languages/*.po into .mo files: python3 tools/compile-mo.py - -Same output as gettext's msgfmt for these files, without needing gettext. -""" -import glob -import re -import struct - - -def parse(path): - entries, cur, field = {}, {}, None - for line in open(path, encoding='utf-8').read().splitlines() + ['']: - m = re.match(r'^(msgid|msgstr) "(.*)"$', line) - if m: - if m.group(1) == 'msgid' and 'msgstr' in cur: - entries[cur['msgid']] = cur['msgstr'] - cur = {} - field = m.group(1) - cur[field] = unquote(m.group(2)) - elif line.startswith('"') and field: - cur[field] += unquote(line[1:-1]) - elif not line.strip() and 'msgstr' in cur: - entries[cur['msgid']] = cur['msgstr'] - cur, field = {}, None - return {k: v for k, v in entries.items() if v} - - -def unquote(s): - return re.sub(r'\\(.)', lambda m: {'n': '\n', 't': '\t'}.get(m.group(1), m.group(1)), s) - - -def write_mo(entries, path): - items = sorted(entries.items()) - ids = strs = b'' - offsets = [] - for k, v in items: - kb, vb = k.encode(), v.encode() - offsets.append((len(ids), len(kb), len(strs), len(vb))) - ids += kb + b'\0' - strs += vb + b'\0' - n = len(items) - keys_at = 28 - values_at = keys_at + n * 8 - ids_at = values_at + n * 8 - strs_at = ids_at + len(ids) - out = struct.pack('<7I', 0x950412DE, 0, n, keys_at, values_at, 0, 0) - for o, length, _, _ in offsets: - out += struct.pack('<2I', length, ids_at + o) - for _, _, o, length in offsets: - out += struct.pack('<2I', length, strs_at + o) - open(path, 'wb').write(out + ids + strs) - - -for po in sorted(glob.glob('languages/*.po')): - write_mo(parse(po), po[:-3] + '.mo') - print(po[:-3] + '.mo') diff --git a/languages/hibira-ja.po b/translations/hibira-ja.po similarity index 100% rename from languages/hibira-ja.po rename to translations/hibira-ja.po diff --git a/languages/hibira-ko_KR.po b/translations/hibira-ko_KR.po similarity index 100% rename from languages/hibira-ko_KR.po rename to translations/hibira-ko_KR.po diff --git a/languages/hibira-th.po b/translations/hibira-th.po similarity index 100% rename from languages/hibira-th.po rename to translations/hibira-th.po diff --git a/languages/hibira-vi.po b/translations/hibira-vi.po similarity index 100% rename from languages/hibira-vi.po rename to translations/hibira-vi.po diff --git a/languages/hibira-zh_CN.po b/translations/hibira-zh_CN.po similarity index 100% rename from languages/hibira-zh_CN.po rename to translations/hibira-zh_CN.po diff --git a/languages/hibira-zh_TW.po b/translations/hibira-zh_TW.po similarity index 100% rename from languages/hibira-zh_TW.po rename to translations/hibira-zh_TW.po