├── CHANGELOG.md ├── LICENSE ├── composer.json ├── extend.php ├── icon.png ├── js ├── admin.ts ├── dist-typings │ ├── admin │ │ ├── extend.d.ts │ │ └── index.d.ts │ └── forum │ │ └── index.d.ts ├── dist │ ├── admin.js │ ├── admin.js.map │ ├── forum.js │ └── forum.js.map ├── forum.ts ├── package.json ├── src │ ├── admin │ │ ├── extend.tsx │ │ └── index.ts │ └── forum │ │ ├── index.tsx │ │ └── shims.d.ts ├── tsconfig.json └── webpack.config.js ├── less ├── admin.less └── forum.less ├── locale └── en.yml └── src ├── Api └── Controller │ └── AuthController.php ├── Listener └── PushNewPost.php ├── Provider └── PusherProvider.php ├── PusherNotificationDriver.php └── SendPusherNotificationsJob.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [1.2.0](https://github.com/flarum/pusher/compare/v1.1.0...v1.2.0) 4 | 5 | ### Changed 6 | - Convert to TypeScript (https://github.com/flarum/pusher/pull/34). 7 | - Replace jQuery with vanilla JS (https://github.com/flarum/pusher/pull/35). 8 | 9 | ### Fixed 10 | - Discussion list pagination breaks when clicking on the update button (https://github.com/flarum/pusher/pull/33). 11 | 12 | ## [1.1.0](https://github.com/flarum/pusher/compare/v1.0.0...v1.1.0) 13 | 14 | No changes. 15 | 16 | ## [1.0.0](https://github.com/flarum/pusher/compare/v0.1.0-beta.16...v1.0.0) 17 | 18 | ### Added 19 | - Make non public discussions realtime (https://github.com/flarum/pusher/pull/17) 20 | 21 | ### Changed 22 | - Compatibility with Flarum v1.0.0. 23 | 24 | ## [0.1.0-beta.16](https://github.com/flarum/pusher/compare/v0.1.0-beta.15...v0.1.0-beta.16) 25 | 26 | ### Changed 27 | - Moved locale files from translation pack to extension (https://github.com/flarum/pusher/pull/26) 28 | 29 | ## [0.1.0-beta.15](https://github.com/flarum/pusher/compare/v0.1.0-beta.14.1...v0.1.0-beta.15) 30 | 31 | ### Changed 32 | - Updated composer.json and admin javascript for new admin area. 33 | - Updated to use newest extenders. 34 | 35 | ## [0.1.0-beta.14.1](https://github.com/flarum/pusher/compare/v0.1.0-beta.14...v0.1.0-beta.14.1) 36 | 37 | ### Fixed 38 | - Children were incorrectly passed to show update highlight 39 | - Update discussion list caused an error (#27) 40 | 41 | ## [0.1.0-beta.14](https://github.com/flarum/pusher/compare/v0.1.0-beta.13...v0.1.0-beta.14) 42 | 43 | ### Changed 44 | - Updated mithril to version 2 45 | - Load language strings correctly on en-/disable 46 | - Updated JS dependencies 47 | 48 | ### Fixed 49 | - Publish no events for tags when flarum/tags isn't installed (#25) 50 | 51 | ## [0.1.0-beta.13](https://github.com/flarum/pusher/compare/v0.1.0-beta.12...v0.1.0-beta.13) 52 | 53 | ### Changed 54 | - Use different CDN for loading Pusher JS library (#20) 55 | - Updated JS dependencies 56 | 57 | ## [0.1.0-beta.9](https://github.com/flarum/pusher/compare/v0.1.0-beta.8.1...v0.1.0-beta.9) 58 | 59 | ### Changed 60 | - Replace event subscribers (that resolve services too early) with listeners ([da0f0af](https://github.com/flarum/pusher/commit/da0f0afb24bae39535b4beaf750f311c403adef1) and [28a70ff](https://github.com/flarum/pusher/commit/28a70ff074014bc75acee6eff7a74faecf5ae341)) 61 | 62 | ## [0.1.0-beta.8.1](https://github.com/flarum/pusher/compare/v0.1.0-beta.8...v0.1.0-beta.8.1) 63 | 64 | ### Fixed 65 | - Fix broken functionality ([00b127c](https://github.com/flarum/pusher/commit/00b127c576e5554bc04b491ec47ae57f8525fac3)) 66 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019-2024 Stichting Flarum (Flarum Foundation) 4 | Copyright (c) 2014-2019 Toby Zerner (toby.zerner@gmail.com) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flarum/pusher", 3 | "description": "See new discussions and posts in real-time using Pusher.", 4 | "type": "flarum-extension", 5 | "keywords": [ 6 | "discussion" 7 | ], 8 | "license": "MIT", 9 | "support": { 10 | "issues": "https://github.com/flarum/framework/issues", 11 | "source": "https://github.com/flarum/pusher", 12 | "forum": "https://discuss.flarum.org" 13 | }, 14 | "homepage": "https://flarum.org", 15 | "funding": [ 16 | { 17 | "type": "website", 18 | "url": "https://flarum.org/donate/" 19 | } 20 | ], 21 | "require": { 22 | "flarum/core": "^2.0.0-beta.3", 23 | "pusher/pusher-php-server": "^7.2" 24 | }, 25 | "require-dev": { 26 | "flarum/tags": "^1.0" 27 | }, 28 | "autoload": { 29 | "psr-4": { 30 | "Flarum\\Pusher\\": "src/" 31 | } 32 | }, 33 | "extra": { 34 | "branch-alias": { 35 | "dev-main": "2.x-dev" 36 | }, 37 | "flarum-extension": { 38 | "title": "Pusher", 39 | "category": "feature", 40 | "icon": { 41 | "image": "icon.png", 42 | "backgroundSize": "46% 63%", 43 | "backgroundPosition": "center", 44 | "backgroundRepeat": "no-repeat", 45 | "backgroundColor": "#40bad8", 46 | "color": "#fff" 47 | } 48 | }, 49 | "flarum-cli": { 50 | "modules": { 51 | "admin": true, 52 | "forum": true, 53 | "js": true, 54 | "jsCommon": false, 55 | "css": true, 56 | "gitConf": true, 57 | "githubActions": true, 58 | "prettier": true, 59 | "typescript": true, 60 | "bundlewatch": false, 61 | "backendTesting": false, 62 | "editorConfig": true, 63 | "styleci": true 64 | } 65 | } 66 | }, 67 | "repositories": [ 68 | { 69 | "type": "path", 70 | "url": "../../*/*" 71 | } 72 | ], 73 | "minimum-stability": "dev", 74 | "prefer-stable": true 75 | } 76 | -------------------------------------------------------------------------------- /extend.php: -------------------------------------------------------------------------------- 1 | js(__DIR__.'/js/dist/forum.js') 20 | ->css(__DIR__.'/less/forum.less'), 21 | 22 | (new Extend\Frontend('admin')) 23 | ->js(__DIR__.'/js/dist/admin.js'), 24 | 25 | (new Extend\Routes('api')) 26 | ->post('/pusher/auth', 'pusher.auth', AuthController::class), 27 | 28 | new Extend\Locales(__DIR__.'/locale'), 29 | 30 | (new Extend\Notification()) 31 | ->driver('pusher', PusherNotificationDriver::class), 32 | 33 | (new Extend\Settings()) 34 | ->serializeToForum('pusherKey', 'flarum-pusher.app_key') 35 | ->serializeToForum('pusherCluster', 'flarum-pusher.app_cluster'), 36 | 37 | (new Extend\Event()) 38 | ->listen(Posted::class, Listener\PushNewPost::class), 39 | 40 | (new Extend\ServiceProvider()) 41 | ->register(PusherProvider::class), 42 | ]; 43 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flarum/pusher/8ed1cf1869f03bd44bd0cfb609abfd8558986f66/icon.png -------------------------------------------------------------------------------- /js/admin.ts: -------------------------------------------------------------------------------- 1 | export * from './src/admin'; 2 | -------------------------------------------------------------------------------- /js/dist-typings/admin/extend.d.ts: -------------------------------------------------------------------------------- 1 | declare const _default: import("flarum/common/extenders/Admin").default[]; 2 | export default _default; 3 | -------------------------------------------------------------------------------- /js/dist-typings/admin/index.d.ts: -------------------------------------------------------------------------------- 1 | export { default as extend } from './extend'; 2 | -------------------------------------------------------------------------------- /js/dist-typings/forum/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as PusherTypes from 'pusher-js'; 2 | export type PusherBinding = { 3 | channels: { 4 | main: PusherTypes.Channel; 5 | user: PusherTypes.Channel | null; 6 | }; 7 | pusher: PusherTypes.default; 8 | }; 9 | -------------------------------------------------------------------------------- /js/dist/admin.js: -------------------------------------------------------------------------------- 1 | (()=>{var e={n:t=>{var r=t&&t.__esModule?()=>t.default:()=>t;return e.d(r,{a:r}),r},d:(t,r)=>{for(var a in r)e.o(r,a)&&!e.o(t,a)&&Object.defineProperty(t,a,{enumerable:!0,get:r[a]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r:e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})}},t={};(()=>{"use strict";e.r(t),e.d(t,{extend:()=>l});const r=flarum.reg.get("core","admin/app");var a=e.n(r);const s=flarum.reg.get("core","common/extenders"),l=[(new(e.n(s)().Admin)).setting((()=>({setting:"flarum-pusher.app_id",label:a().translator.trans("flarum-pusher.admin.pusher_settings.app_id_label"),type:"text"})),30).setting((()=>({setting:"flarum-pusher.app_key",label:a().translator.trans("flarum-pusher.admin.pusher_settings.app_key_label"),type:"text"})),20).setting((()=>({setting:"flarum-pusher.app_secret",label:a().translator.trans("flarum-pusher.admin.pusher_settings.app_secret_label"),type:"text"})),10).setting((()=>({setting:"flarum-pusher.app_cluster",label:a().translator.trans("flarum-pusher.admin.pusher_settings.app_cluster_label"),type:"text"})),0)];a().initializers.add("flarum-pusher",(()=>{}))})(),module.exports=t})(); 2 | //# sourceMappingURL=admin.js.map -------------------------------------------------------------------------------- /js/dist/admin.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"admin.js","mappings":"MACA,IAAIA,EAAsB,CCA1BA,EAAyBC,IACxB,IAAIC,EAASD,GAAUA,EAAOE,WAC7B,IAAOF,EAAiB,QACxB,IAAM,EAEP,OADAD,EAAoBI,EAAEF,EAAQ,CAAEG,EAAGH,IAC5BA,CAAM,ECLdF,EAAwB,CAACM,EAASC,KACjC,IAAI,IAAIC,KAAOD,EACXP,EAAoBS,EAAEF,EAAYC,KAASR,EAAoBS,EAAEH,EAASE,IAC5EE,OAAOC,eAAeL,EAASE,EAAK,CAAEI,YAAY,EAAMC,IAAKN,EAAWC,IAE1E,ECNDR,EAAwB,CAACc,EAAKC,IAAUL,OAAOM,UAAUC,eAAeC,KAAKJ,EAAKC,GCClFf,EAAyBM,IACH,oBAAXa,QAA0BA,OAAOC,aAC1CV,OAAOC,eAAeL,EAASa,OAAOC,YAAa,CAAEC,MAAO,WAE7DX,OAAOC,eAAeL,EAAS,aAAc,CAAEe,OAAO,GAAO,G,qDCL9D,MAAM,EAA+BC,OAAOC,IAAIV,IAAI,OAAQ,a,aCA5D,MAAM,EAA+BS,OAAOC,IAAIV,IAAI,OAAQ,oBCE5D,IAAgB,I,MAAI,WAAeW,SAAQ,KAAM,CAC/CA,QAAS,uBACTC,MAAO,eAAeC,MAAM,oDAC5BC,KAAM,UACJ,IAAIH,SAAQ,KAAM,CACpBA,QAAS,wBACTC,MAAO,eAAeC,MAAM,qDAC5BC,KAAM,UACJ,IAAIH,SAAQ,KAAM,CACpBA,QAAS,2BACTC,MAAO,eAAeC,MAAM,wDAC5BC,KAAM,UACJ,IAAIH,SAAQ,KAAM,CACpBA,QAAS,4BACTC,MAAO,eAAeC,MAAM,yDAC5BC,KAAM,UACJ,IChBJ,iBAAiBC,IAAI,iBAAiB,Q","sources":["webpack://@flarum/pusher/webpack/bootstrap","webpack://@flarum/pusher/webpack/runtime/compat get default export","webpack://@flarum/pusher/webpack/runtime/define property getters","webpack://@flarum/pusher/webpack/runtime/hasOwnProperty shorthand","webpack://@flarum/pusher/webpack/runtime/make namespace object","webpack://@flarum/pusher/external root \"flarum.reg.get('core', 'admin/app')\"","webpack://@flarum/pusher/external root \"flarum.reg.get('core', 'common/extenders')\"","webpack://@flarum/pusher/./src/admin/extend.tsx","webpack://@flarum/pusher/./src/admin/index.ts"],"sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'admin/app');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/extenders');","import Extend from 'flarum/common/extenders';\nimport app from 'flarum/admin/app';\nexport default [new Extend.Admin().setting(() => ({\n setting: 'flarum-pusher.app_id',\n label: app.translator.trans('flarum-pusher.admin.pusher_settings.app_id_label'),\n type: 'text'\n}), 30).setting(() => ({\n setting: 'flarum-pusher.app_key',\n label: app.translator.trans('flarum-pusher.admin.pusher_settings.app_key_label'),\n type: 'text'\n}), 20).setting(() => ({\n setting: 'flarum-pusher.app_secret',\n label: app.translator.trans('flarum-pusher.admin.pusher_settings.app_secret_label'),\n type: 'text'\n}), 10).setting(() => ({\n setting: 'flarum-pusher.app_cluster',\n label: app.translator.trans('flarum-pusher.admin.pusher_settings.app_cluster_label'),\n type: 'text'\n}), 0)];","import app from 'flarum/admin/app';\nexport { default as extend } from './extend';\napp.initializers.add('flarum-pusher', () => {\n // ...\n});"],"names":["__webpack_require__","module","getter","__esModule","d","a","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","Symbol","toStringTag","value","flarum","reg","setting","label","trans","type","add"],"sourceRoot":""} -------------------------------------------------------------------------------- /js/dist/forum.js: -------------------------------------------------------------------------------- 1 | (()=>{var e={n:t=>{var s=t&&t.__esModule?()=>t.default:()=>t;return e.d(s,{a:s}),s},d:(t,s)=>{for(var n in s)e.o(s,n)&&!e.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:s[n]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)};(()=>{"use strict";const t=flarum.reg.get("core","forum/app");var s=e.n(t);const n=flarum.reg.get("core","common/extend"),o=flarum.reg.get("core","forum/components/DiscussionList");var r=e.n(o);const i=flarum.reg.get("core","forum/components/DiscussionPage");var u=e.n(i);const a=flarum.reg.get("core","forum/components/IndexPage");var c=e.n(a);const d=flarum.reg.get("core","common/components/Button");var p=e.n(d);s().initializers.add("flarum-pusher",(()=>{s().pusher=(async()=>{await import("//cdn.jsdelivr.net/npm/pusher-js@7.0.3/dist/web/pusher.min.js");const e=new Pusher(s().forum.attribute("pusherKey"),{authEndpoint:`${s().forum.attribute("apiUrl")}/pusher/auth`,cluster:s().forum.attribute("pusherCluster"),auth:{headers:{"X-CSRF-Token":s().session.csrfToken}}});return{channels:{main:e.subscribe("public"),user:s().session.user?e.subscribe(`private-user${s().session.user.id()}`):null},pusher:e}})(),s().pushedUpdates=[],(0,n.extend)(r().prototype,"oncreate",(function(){s().pusher.then((e=>{e.pusher.bind("newPost",(e=>{const t=s().discussions.getParams();if(!t.q&&!t.sort&&!t.filter){if(t.tags){const n=s().store.getBy("tags","slug",t.tags),o=n?.id();if(!o||!e.tagIds.includes(o))return}const n=String(e.discussionId);s().current.get("discussion")&&n===s().current.get("discussion").id()||-1!==s().pushedUpdates.indexOf(n)||(s().pushedUpdates.push(n),s().current.matches(c())&&s().setTitleCount(s().pushedUpdates.length),m.redraw())}}))}))})),(0,n.extend)(r().prototype,"onremove",(function(){s().pusher.then((e=>{e.pusher.unbind("newPost")}))})),(0,n.extend)(r().prototype,"view",(function(e){if(s().pushedUpdates){const t=s().pushedUpdates.length;t&&"object"==typeof e&&e&&"children"in e&&e.children instanceof Array&&e.children.unshift(m(p(),{className:"Button Button--block DiscussionList-update",onclick:()=>{this.attrs.state.refresh().then((()=>{this.loadingUpdated=!1,s().pushedUpdates=[],s().setTitleCount(0),m.redraw()})),this.loadingUpdated=!0},loading:this.loadingUpdated},s().translator.trans("flarum-pusher.forum.discussion_list.show_updates_text",{count:t})))}})),(0,n.extend)(u().prototype,"oncreate",(function(){s().pusher.then((e=>{e.pusher.bind("newPost",(e=>{const t=String(e.discussionId),n=this.discussion?.id();if(this.discussion&&n===t&&this.stream){const e=this.discussion.commentCount()??0;s().store.find("discussions",n).then((()=>{this.stream?.update().then(m.redraw),document.hasFocus()||(s().setTitleCount(Math.max(0,(this.discussion?.commentCount()??0)-e)),window.addEventListener("focus",(()=>s().setTitleCount(0)),{once:!0}))}))}}))}))})),(0,n.extend)(u().prototype,"onremove",(function(){s().pusher.then((e=>{e.pusher.unbind("newPost")}))})),(0,n.extend)(c().prototype,"actionItems",(e=>{e.remove("refresh")})),s().pusher.then((e=>{const t=e.channels;t.user&&t.user.bind("notification",(()=>{s().session.user&&s().session.user.pushAttributes({unreadNotificationCount:s().session.user.unreadNotificationCount()??1,newNotificationCount:s().session.user.newNotificationCount()??1}),s().notifications.clear(),m.redraw()}))}))}))})(),module.exports={}})(); 2 | //# sourceMappingURL=forum.js.map -------------------------------------------------------------------------------- /js/dist/forum.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"forum.js","mappings":"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","sources":["webpack://@flarum/pusher/webpack/bootstrap","webpack://@flarum/pusher/webpack/runtime/compat get default export","webpack://@flarum/pusher/webpack/runtime/define property getters","webpack://@flarum/pusher/webpack/runtime/hasOwnProperty shorthand","webpack://@flarum/pusher/external root \"flarum.reg.get('core', 'forum/app')\"","webpack://@flarum/pusher/external root \"flarum.reg.get('core', 'common/extend')\"","webpack://@flarum/pusher/external root \"flarum.reg.get('core', 'forum/components/DiscussionList')\"","webpack://@flarum/pusher/external root \"flarum.reg.get('core', 'forum/components/DiscussionPage')\"","webpack://@flarum/pusher/external root \"flarum.reg.get('core', 'forum/components/IndexPage')\"","webpack://@flarum/pusher/external root \"flarum.reg.get('core', 'common/components/Button')\"","webpack://@flarum/pusher/./src/forum/index.tsx"],"sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'forum/app');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/extend');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'forum/components/DiscussionList');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'forum/components/DiscussionPage');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'forum/components/IndexPage');","const __WEBPACK_NAMESPACE_OBJECT__ = flarum.reg.get('core', 'common/components/Button');","import app from 'flarum/forum/app';\nimport { extend } from 'flarum/common/extend';\nimport DiscussionList from 'flarum/forum/components/DiscussionList';\nimport DiscussionPage from 'flarum/forum/components/DiscussionPage';\nimport IndexPage from 'flarum/forum/components/IndexPage';\nimport Button from 'flarum/common/components/Button';\napp.initializers.add('flarum-pusher', () => {\n app.pusher = (async () => {\n // @ts-expect-error\n await import('//cdn.jsdelivr.net/npm/pusher-js@7.0.3/dist/web/pusher.min.js' /* webpackIgnore: true, webpackPrefetch: true */);\n\n // @ts-expect-error Imported dynamically\n const socket = new Pusher(app.forum.attribute('pusherKey'), {\n authEndpoint: `${app.forum.attribute('apiUrl')}/pusher/auth`,\n cluster: app.forum.attribute('pusherCluster'),\n auth: {\n headers: {\n 'X-CSRF-Token': app.session.csrfToken\n }\n }\n });\n return {\n channels: {\n main: socket.subscribe('public'),\n user: app.session.user ? socket.subscribe(`private-user${app.session.user.id()}`) : null\n },\n pusher: socket\n };\n })();\n app.pushedUpdates = [];\n extend(DiscussionList.prototype, 'oncreate', function () {\n app.pusher.then(binding => {\n const pusher = binding.pusher;\n pusher.bind('newPost', data => {\n const params = app.discussions.getParams();\n if (!params.q && !params.sort && !params.filter) {\n if (params.tags) {\n const tag = app.store.getBy('tags', 'slug', params.tags);\n const tagId = tag?.id();\n if (!tagId || !data.tagIds.includes(tagId)) return;\n }\n const id = String(data.discussionId);\n if ((!app.current.get('discussion') || id !== app.current.get('discussion').id()) && app.pushedUpdates.indexOf(id) === -1) {\n app.pushedUpdates.push(id);\n if (app.current.matches(IndexPage)) {\n app.setTitleCount(app.pushedUpdates.length);\n }\n m.redraw();\n }\n }\n });\n });\n });\n extend(DiscussionList.prototype, 'onremove', function () {\n app.pusher.then(binding => {\n binding.pusher.unbind('newPost');\n });\n });\n extend(DiscussionList.prototype, 'view', function (vdom) {\n if (app.pushedUpdates) {\n const count = app.pushedUpdates.length;\n if (count && typeof vdom === 'object' && vdom && 'children' in vdom && vdom.children instanceof Array) {\n vdom.children.unshift(m(Button, {\n className: \"Button Button--block DiscussionList-update\",\n onclick: () => {\n this.attrs.state.refresh().then(() => {\n this.loadingUpdated = false;\n app.pushedUpdates = [];\n app.setTitleCount(0);\n m.redraw();\n });\n this.loadingUpdated = true;\n },\n loading: this.loadingUpdated\n }, app.translator.trans('flarum-pusher.forum.discussion_list.show_updates_text', {\n count\n })));\n }\n }\n });\n extend(DiscussionPage.prototype, 'oncreate', function () {\n app.pusher.then(binding => {\n const pusher = binding.pusher;\n pusher.bind('newPost', data => {\n const id = String(data.discussionId);\n const discussionId = this.discussion?.id();\n if (this.discussion && discussionId === id && this.stream) {\n const oldCount = this.discussion.commentCount() ?? 0;\n app.store.find('discussions', discussionId).then(() => {\n this.stream?.update().then(m.redraw);\n if (!document.hasFocus()) {\n app.setTitleCount(Math.max(0, (this.discussion?.commentCount() ?? 0) - oldCount));\n window.addEventListener('focus', () => app.setTitleCount(0), {\n once: true\n });\n }\n });\n }\n });\n });\n });\n extend(DiscussionPage.prototype, 'onremove', function () {\n app.pusher.then(binding => {\n binding.pusher.unbind('newPost');\n });\n });\n extend(IndexPage.prototype, 'actionItems', items => {\n items.remove('refresh');\n });\n app.pusher.then(binding => {\n const channels = binding.channels;\n if (channels.user) {\n channels.user.bind('notification', () => {\n if (app.session.user) {\n app.session.user.pushAttributes({\n unreadNotificationCount: app.session.user.unreadNotificationCount() ?? 0 + 1,\n newNotificationCount: app.session.user.newNotificationCount() ?? 0 + 1\n });\n }\n app.notifications.clear();\n m.redraw();\n });\n }\n });\n});"],"names":["__webpack_require__","module","getter","__esModule","d","a","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","flarum","reg","add","import","socket","Pusher","attribute","authEndpoint","cluster","auth","headers","csrfToken","channels","main","subscribe","user","id","pusher","extend","then","binding","bind","data","params","getParams","q","sort","filter","tags","tag","getBy","tagId","tagIds","includes","String","discussionId","indexOf","push","matches","length","m","redraw","unbind","vdom","count","children","Array","unshift","className","onclick","this","attrs","state","refresh","loadingUpdated","loading","trans","discussion","stream","oldCount","commentCount","find","update","document","hasFocus","Math","max","window","addEventListener","once","items","remove","pushAttributes","unreadNotificationCount","newNotificationCount","clear"],"sourceRoot":""} -------------------------------------------------------------------------------- /js/forum.ts: -------------------------------------------------------------------------------- 1 | export * from './src/forum'; 2 | -------------------------------------------------------------------------------- /js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "@flarum/pusher", 4 | "version": "0.0.0", 5 | "prettier": "@flarum/prettier-config", 6 | "scripts": { 7 | "dev": "webpack --mode development --watch", 8 | "build": "webpack --mode production", 9 | "analyze": "cross-env ANALYZER=true yarn run build", 10 | "format": "prettier --write src", 11 | "format-check": "prettier --check src", 12 | "clean-typings": "npx rimraf dist-typings && mkdir dist-typings", 13 | "build-typings": "yarn run clean-typings && ([ -e src/@types ] && cp -r src/@types dist-typings/@types || true) && tsc && yarn run post-build-typings", 14 | "post-build-typings": "find dist-typings -type f -name '*.d.ts' -print0 | xargs -0 sed -i 's,../src/@types,@types,g'", 15 | "check-typings": "tsc --noEmit --emitDeclarationOnly false", 16 | "check-typings-coverage": "typescript-coverage-report" 17 | }, 18 | "devDependencies": { 19 | "@flarum/prettier-config": "^1.0.0", 20 | "@types/pusher-js": "^5.1.0", 21 | "flarum-tsconfig": "^2.0.0", 22 | "flarum-webpack-config": "^3.0.0", 23 | "prettier": "^2.5.1", 24 | "typescript": "^4.5.4", 25 | "typescript-coverage-report": "^0.6.1", 26 | "webpack": "^5.76.0", 27 | "webpack-cli": "^4.9.1" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /js/src/admin/extend.tsx: -------------------------------------------------------------------------------- 1 | import Extend from 'flarum/common/extenders'; 2 | import app from 'flarum/admin/app'; 3 | 4 | export default [ 5 | new Extend.Admin() 6 | .setting( 7 | () => ({ 8 | setting: 'flarum-pusher.app_id', 9 | label: app.translator.trans('flarum-pusher.admin.pusher_settings.app_id_label'), 10 | type: 'text', 11 | }), 12 | 30 13 | ) 14 | .setting( 15 | () => ({ 16 | setting: 'flarum-pusher.app_key', 17 | label: app.translator.trans('flarum-pusher.admin.pusher_settings.app_key_label'), 18 | type: 'text', 19 | }), 20 | 20 21 | ) 22 | .setting( 23 | () => ({ 24 | setting: 'flarum-pusher.app_secret', 25 | label: app.translator.trans('flarum-pusher.admin.pusher_settings.app_secret_label'), 26 | type: 'text', 27 | }), 28 | 10 29 | ) 30 | .setting( 31 | () => ({ 32 | setting: 'flarum-pusher.app_cluster', 33 | label: app.translator.trans('flarum-pusher.admin.pusher_settings.app_cluster_label'), 34 | type: 'text', 35 | }), 36 | 0 37 | ), 38 | ]; 39 | -------------------------------------------------------------------------------- /js/src/admin/index.ts: -------------------------------------------------------------------------------- 1 | import app from 'flarum/admin/app'; 2 | 3 | export { default as extend } from './extend'; 4 | 5 | app.initializers.add('flarum-pusher', () => { 6 | // ... 7 | }); 8 | -------------------------------------------------------------------------------- /js/src/forum/index.tsx: -------------------------------------------------------------------------------- 1 | import * as PusherTypes from 'pusher-js'; 2 | import app from 'flarum/forum/app'; 3 | import { extend } from 'flarum/common/extend'; 4 | import DiscussionList from 'flarum/forum/components/DiscussionList'; 5 | import DiscussionPage from 'flarum/forum/components/DiscussionPage'; 6 | import IndexPage from 'flarum/forum/components/IndexPage'; 7 | import Button from 'flarum/common/components/Button'; 8 | import ItemList from 'flarum/common/utils/ItemList'; 9 | import type { Children } from 'mithril'; 10 | import type Tag from 'ext:flarum/tags/common/models/Tag'; 11 | 12 | export type PusherBinding = { 13 | channels: { 14 | main: PusherTypes.Channel; 15 | user: PusherTypes.Channel | null; 16 | }; 17 | pusher: PusherTypes.default; 18 | }; 19 | 20 | app.initializers.add('flarum-pusher', () => { 21 | app.pusher = (async () => { 22 | // @ts-expect-error 23 | await import('//cdn.jsdelivr.net/npm/pusher-js@7.0.3/dist/web/pusher.min.js' /* webpackIgnore: true, webpackPrefetch: true */); 24 | 25 | // @ts-expect-error Imported dynamically 26 | const socket: PusherTypes.default = new Pusher(app.forum.attribute('pusherKey'), { 27 | authEndpoint: `${app.forum.attribute('apiUrl')}/pusher/auth`, 28 | cluster: app.forum.attribute('pusherCluster'), 29 | auth: { 30 | headers: { 31 | 'X-CSRF-Token': app.session.csrfToken, 32 | }, 33 | }, 34 | }); 35 | 36 | return { 37 | channels: { 38 | main: socket.subscribe('public'), 39 | user: app.session.user ? socket.subscribe(`private-user${app.session.user.id()}`) : null, 40 | }, 41 | pusher: socket, 42 | }; 43 | })(); 44 | 45 | app.pushedUpdates = []; 46 | 47 | extend(DiscussionList.prototype, 'oncreate', function () { 48 | app.pusher.then((binding: PusherBinding) => { 49 | const pusher = binding.pusher; 50 | 51 | pusher.bind('newPost', (data: { tagIds: string[]; discussionId: number }) => { 52 | const params = app.discussions.getParams(); 53 | 54 | if (!params.q && !params.sort && !params.filter) { 55 | if (params.tags) { 56 | const tag = app.store.getBy('tags', 'slug', params.tags); 57 | const tagId = tag?.id(); 58 | 59 | if (!tagId || !data.tagIds.includes(tagId)) return; 60 | } 61 | 62 | const id = String(data.discussionId); 63 | 64 | if ((!app.current.get('discussion') || id !== app.current.get('discussion').id()) && app.pushedUpdates.indexOf(id) === -1) { 65 | app.pushedUpdates.push(id); 66 | 67 | if (app.current.matches(IndexPage)) { 68 | app.setTitleCount(app.pushedUpdates.length); 69 | } 70 | 71 | m.redraw(); 72 | } 73 | } 74 | }); 75 | }); 76 | }); 77 | 78 | extend(DiscussionList.prototype, 'onremove', function () { 79 | app.pusher.then((binding: PusherBinding) => { 80 | binding.pusher.unbind('newPost'); 81 | }); 82 | }); 83 | 84 | extend(DiscussionList.prototype, 'view', function (this: DiscussionList, vdom: Children) { 85 | if (app.pushedUpdates) { 86 | const count = app.pushedUpdates.length; 87 | 88 | if (count && typeof vdom === 'object' && vdom && 'children' in vdom && vdom.children instanceof Array) { 89 | vdom.children.unshift( 90 | 105 | ); 106 | } 107 | } 108 | }); 109 | 110 | extend(DiscussionPage.prototype, 'oncreate', function (this: DiscussionPage) { 111 | app.pusher.then((binding: PusherBinding) => { 112 | const pusher = binding.pusher; 113 | 114 | pusher.bind('newPost', (data: { discussionId: number }) => { 115 | const id = String(data.discussionId); 116 | const discussionId = this.discussion?.id(); 117 | 118 | if (this.discussion && discussionId === id && this.stream) { 119 | const oldCount = this.discussion.commentCount() ?? 0; 120 | 121 | app.store.find('discussions', discussionId).then(() => { 122 | this.stream?.update().then(m.redraw); 123 | 124 | if (!document.hasFocus()) { 125 | app.setTitleCount(Math.max(0, (this.discussion?.commentCount() ?? 0) - oldCount)); 126 | 127 | window.addEventListener('focus', () => app.setTitleCount(0), { once: true }); 128 | } 129 | }); 130 | } 131 | }); 132 | }); 133 | }); 134 | 135 | extend(DiscussionPage.prototype, 'onremove', function () { 136 | app.pusher.then((binding: PusherBinding) => { 137 | binding.pusher.unbind('newPost'); 138 | }); 139 | }); 140 | 141 | extend(IndexPage.prototype, 'actionItems', (items: ItemList) => { 142 | items.remove('refresh'); 143 | }); 144 | 145 | app.pusher.then((binding: PusherBinding) => { 146 | const channels = binding.channels; 147 | 148 | if (channels.user) { 149 | channels.user.bind('notification', () => { 150 | if (app.session.user) { 151 | app.session.user.pushAttributes({ 152 | unreadNotificationCount: app.session.user.unreadNotificationCount() ?? 0 + 1, 153 | newNotificationCount: app.session.user.newNotificationCount() ?? 0 + 1, 154 | }); 155 | } 156 | app.notifications.clear(); 157 | m.redraw(); 158 | }); 159 | } 160 | }); 161 | }); 162 | -------------------------------------------------------------------------------- /js/src/forum/shims.d.ts: -------------------------------------------------------------------------------- 1 | import * as PusherTypes from 'pusher-js'; 2 | 3 | declare module 'flarum/forum/ForumApplication' { 4 | export default interface ForumApplication { 5 | pusher: Promise<{ 6 | channels: { 7 | main: PusherTypes.Channel; 8 | user: PusherTypes.Channel | null; 9 | }; 10 | pusher: PusherTypes.default; 11 | }>; 12 | 13 | pushedUpdates: Array; 14 | } 15 | } 16 | 17 | declare module 'flarum/forum/components/DiscussionList' { 18 | export default interface DiscussionList { 19 | loadingUpdated?: boolean; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /js/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use Flarum's tsconfig as a starting point 3 | "extends": "flarum-tsconfig", 4 | // This will match all .ts, .tsx, .d.ts, .js, .jsx files in your `src` folder 5 | // and also tells your Typescript server to read core's global typings for 6 | // access to `dayjs` and `$` in the global namespace. 7 | "include": ["src/**/*", "../../../*/*/js/dist-typings/@types/**/*", "@types/**/*"], 8 | "compilerOptions": { 9 | // This will output typings to `dist-typings` 10 | "declarationDir": "./dist-typings", 11 | "paths": { 12 | "flarum/*": ["../../../framework/core/js/dist-typings/*"], 13 | "ext:flarum/tags/*": ["../../tags/js/dist-typings/*"] 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /js/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('flarum-webpack-config')(); 2 | -------------------------------------------------------------------------------- /less/admin.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flarum/pusher/8ed1cf1869f03bd44bd0cfb609abfd8558986f66/less/admin.less -------------------------------------------------------------------------------- /less/forum.less: -------------------------------------------------------------------------------- 1 | .DiscussionList-update { 2 | .Button--color(@alert-color, @alert-bg); 3 | margin-bottom: 5px; 4 | 5 | .DiscussionPage & { 6 | border-radius: 0; 7 | margin-bottom: 0; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /locale/en.yml: -------------------------------------------------------------------------------- 1 | flarum-pusher: 2 | 3 | ## 4 | # UNIQUE KEYS - The following keys are used in only one location each. 5 | ## 6 | 7 | # Translations in this namespace are used by the admin interface. 8 | admin: 9 | 10 | # These translations are used in the Pusher Settings modal dialog. 11 | pusher_settings: 12 | app_cluster_label: Cluster 13 | app_id_label: App ID 14 | app_key_label: App Key 15 | app_secret_label: App Secret 16 | title: Pusher Settings 17 | 18 | # Translations in this namespace are used by the admin interface. 19 | forum: 20 | 21 | # These translations are used in the discussion list. 22 | discussion_list: 23 | show_updates_text: "{count, plural, one {Show # updated discussion} other {Show # updated discussions}}" 24 | -------------------------------------------------------------------------------- /src/Api/Controller/AuthController.php: -------------------------------------------------------------------------------- 1 | id; 32 | $body = $request->getParsedBody(); 33 | 34 | if (Arr::get($body, 'channel_name') === $userChannel) { 35 | $pusher = new Pusher( 36 | $this->settings->get('flarum-pusher.app_key'), 37 | $this->settings->get('flarum-pusher.app_secret'), 38 | $this->settings->get('flarum-pusher.app_id'), 39 | ['cluster' => $this->settings->get('flarum-pusher.app_cluster')] 40 | ); 41 | 42 | $payload = json_decode($pusher->socket_auth($userChannel, Arr::get($body, 'socket_id')), true); 43 | 44 | return new JsonResponse($payload); 45 | } 46 | 47 | return new EmptyResponse(403); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Listener/PushNewPost.php: -------------------------------------------------------------------------------- 1 | post->isVisibleTo(new Guest)) { 32 | $channels[] = 'public'; 33 | } else { 34 | // Retrieve private channels, used for each user. 35 | $response = $this->pusher->getChannels([ 36 | 'filter_by_prefix' => 'private-user' 37 | ]); 38 | 39 | // @phpstan-ignore-next-line 40 | if (! $response) { 41 | return; 42 | } 43 | 44 | foreach ($response->channels as $name => $channel) { 45 | $userId = Str::after($name, 'private-user'); 46 | 47 | if (($user = User::find($userId)) && $event->post->isVisibleTo($user)) { 48 | $channels[] = $name; 49 | } 50 | } 51 | } 52 | 53 | if (count($channels)) { 54 | $tags = $this->extensions->isEnabled('flarum-tags') ? $event->post->discussion->tags : null; 55 | 56 | $this->pusher->trigger($channels, 'newPost', [ 57 | 'postId' => $event->post->id, 58 | 'discussionId' => $event->post->discussion->id, 59 | 'tagIds' => $tags ? $tags->pluck('id') : null 60 | ]); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Provider/PusherProvider.php: -------------------------------------------------------------------------------- 1 | container->bind(Pusher::class, function () { 21 | $settings = $this->container->make(SettingsRepositoryInterface::class); 22 | 23 | $options = []; 24 | 25 | if ($cluster = $settings->get('flarum-pusher.app_cluster')) { 26 | $options['cluster'] = $cluster; 27 | } 28 | 29 | return new Pusher( 30 | $settings->get('flarum-pusher.app_key'), 31 | $settings->get('flarum-pusher.app_secret'), 32 | $settings->get('flarum-pusher.app_id'), 33 | $options 34 | ); 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/PusherNotificationDriver.php: -------------------------------------------------------------------------------- 1 | queue->push(new SendPusherNotificationsJob($blueprint, $users)); 27 | } 28 | } 29 | 30 | public function registerType(string $blueprintClass, array $driversEnabledByDefault): void 31 | { 32 | // ... 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/SendPusherNotificationsJob.php: -------------------------------------------------------------------------------- 1 | recipients as $user) { 29 | if ($user->shouldAlert($this->blueprint::getType())) { 30 | $pusher->trigger('private-user'.$user->id, 'notification', null); 31 | } 32 | } 33 | } 34 | } 35 | --------------------------------------------------------------------------------