-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add real-time comments to Reader full-post view (#40396)
* Add Lasagna websocket middleware (first pass) * Step forward * Sync to latest changes * One other file * Fix topics * Check in a bunch of stuff * Remove Presence for now * Fix logging path * Remove Presence stragglers * More Presence stragglers * More cleanup * Iterate * Push connection management back into the middleware Duh? * Add Reader full post view state subtree We need a way to query what is onscreen in a Reader full post view if a user hits a reader link directly. The use case is joining related channel(s) after the websocket connects. * Use new full view post state logic * Switch to dynamic load of Phoenix.js * Additional cleanup * Remove stray addition To come in follow up PR * Add config.isEnabled check back for lasagna * Add webpackchunkname to dynamic import * To be handled in a subsequent PR * Final adjustment Co-authored-by: undemian <undemian@gmail.com>
- Loading branch information
Showing
30 changed files
with
471 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export const socketConnected = () => ( { type: 'LASAGNA_SOCKET_CONNECTED' } ); | ||
export const socketDisconnected = () => ( { type: 'LASAGNA_SOCKET_DISCONNECTED' } ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
/** | ||
* Internal dependencies | ||
*/ | ||
import wpcom from 'lib/wp'; | ||
import { getCurrentUser } from 'state/current-user/selectors'; | ||
import { socket, socketConnect, socketDisconnect } from './socket'; | ||
import privatePostChannelMiddleware from './private-post-channel/actions-to-events'; | ||
import publicPostChannelMiddleware from './public-post-channel/actions-to-events'; | ||
import userChannelMiddleware from './user-channel/actions-to-events'; | ||
|
||
let socketConnecting = false; | ||
|
||
/** | ||
* Compose a list of middleware into one middleware | ||
* Props @rhc3 | ||
* | ||
* @param m middlewares to compose | ||
*/ | ||
const combineMiddleware = ( ...m ) => { | ||
return store => { | ||
const initialized = m.map( middleware => middleware( store ) ); | ||
return next => initialized.reduce( ( chain, mw ) => mw( chain ), next ); | ||
}; | ||
}; | ||
|
||
/** | ||
* Connection management middleware | ||
* | ||
* @param store middleware store | ||
*/ | ||
const connectMiddleware = store => next => action => { | ||
// bail unless this is a section set with the section definition | ||
if ( action.type !== 'SECTION_SET' || ! action.section ) { | ||
return next( action ); | ||
} | ||
|
||
// connect if we are going to the reader without a socket | ||
if ( ! socket && ! socketConnecting && action.section.name === 'reader' ) { | ||
socketConnecting = true; | ||
const user = getCurrentUser( store.getState() ); | ||
|
||
wpcom | ||
.request( { | ||
method: 'POST', | ||
path: '/jwt/sign', | ||
body: { payload: JSON.stringify( { user } ) }, | ||
} ) | ||
.then( ( { jwt } ) => { | ||
socketConnect( store, jwt, user.ID ); | ||
socketConnecting = false; | ||
} ); | ||
} | ||
|
||
// disconnect if we are leaving the reader with a socket | ||
else if ( socket && action.section.name !== 'reader' ) { | ||
socketDisconnect( store ); | ||
} | ||
|
||
return next( action ); | ||
}; | ||
|
||
export default combineMiddleware( | ||
connectMiddleware, | ||
userChannelMiddleware, | ||
privatePostChannelMiddleware, | ||
publicPostChannelMiddleware | ||
); |
78 changes: 78 additions & 0 deletions
78
client/state/lasagna/private-post-channel/actions-to-events.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import debugFactory from 'debug'; | ||
|
||
/** | ||
* Internal Dependencies | ||
*/ | ||
import { LASAGNA_SOCKET_CONNECTED, ROUTE_SET } from 'state/action-types'; | ||
import { READER_POST_SEEN } from 'state/reader/action-types'; | ||
import { getReaderFullViewPostKey } from 'state/reader/full-view/selectors/get-reader-full-view-post-key'; | ||
import { getPostByKey } from 'state/reader/posts/selectors'; | ||
import { getSite } from 'state/reader/sites/selectors'; | ||
import registerEventHandlers from './events-to-actions'; | ||
import { socket } from '../socket'; | ||
|
||
let channel = null; | ||
|
||
const channelTopicPrefix = 'private:push:wp_post:'; | ||
const debug = debugFactory( 'lasagna:channel:private:push:wp_post' ); | ||
|
||
const joinChannel = ( store, site, post, postKey ) => { | ||
if ( ! socket || ! site.is_private ) { | ||
return; | ||
} | ||
|
||
channel = socket.channel( channelTopicPrefix + post.global_ID, { post_key: postKey } ); | ||
registerEventHandlers( channel, store ); | ||
|
||
channel | ||
.join() | ||
.receive( 'ok', () => debug( 'channel join ok' ) ) | ||
.receive( 'error', ( { reason } ) => { | ||
debug( 'channel join error', reason ); | ||
channel.leave(); | ||
channel = null; | ||
} ); | ||
}; | ||
|
||
const leaveChannel = () => { | ||
channel && channel.leave(); | ||
channel = null; | ||
}; | ||
|
||
export default store => next => action => { | ||
switch ( action.type ) { | ||
case LASAGNA_SOCKET_CONNECTED: { | ||
const state = store.getState(); | ||
const postKey = getReaderFullViewPostKey( state ); | ||
const post = getPostByKey( state, postKey ); | ||
|
||
if ( ! post ) { | ||
break; | ||
} | ||
|
||
const site = getSite( state, post.site_ID ); | ||
|
||
if ( ! site ) { | ||
break; | ||
} | ||
|
||
joinChannel( store, site, post, postKey ); | ||
break; | ||
} | ||
|
||
case READER_POST_SEEN: { | ||
const postKey = getReaderFullViewPostKey( store.getState() ); | ||
joinChannel( store, action.payload.site, action.payload.post, postKey ); | ||
break; | ||
} | ||
|
||
case ROUTE_SET: | ||
leaveChannel(); | ||
break; | ||
} | ||
|
||
return next( action ); | ||
}; |
30 changes: 30 additions & 0 deletions
30
client/state/lasagna/private-post-channel/events-to-actions.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import debugFactory from 'debug'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { receiveComments } from 'state/comments/actions'; | ||
|
||
const debug = debugFactory( 'lasagna:channel:private:push:wp_post' ); | ||
|
||
export default function( channel, store ) { | ||
channel.on( 'new_comment', ( { payload: comment } ) => { | ||
debug( 'New comment', comment ); | ||
|
||
if ( ! comment ) { | ||
return; | ||
} | ||
|
||
store.dispatch( | ||
receiveComments( { | ||
siteId: comment.post.site_ID, | ||
postId: comment.post.ID, | ||
comments: [ comment ], | ||
commentById: true, | ||
} ) | ||
); | ||
} ); | ||
} |
76 changes: 76 additions & 0 deletions
76
client/state/lasagna/public-post-channel/actions-to-events.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import debugFactory from 'debug'; | ||
|
||
/** | ||
* Internal Dependencies | ||
*/ | ||
import { LASAGNA_SOCKET_CONNECTED, ROUTE_SET } from 'state/action-types'; | ||
import { READER_POST_SEEN } from 'state/reader/action-types'; | ||
import { getPostByKey } from 'state/reader/posts/selectors'; | ||
import { getReaderFullViewPostKey } from 'state/reader/full-view/selectors/get-reader-full-view-post-key'; | ||
import { getSite } from 'state/reader/sites/selectors'; | ||
import registerEventHandlers from './events-to-actions'; | ||
import { socket } from '../socket'; | ||
|
||
let channel = null; | ||
|
||
const channelTopicPrefix = 'public:push:wp_post:'; | ||
const debug = debugFactory( 'lasagna:channel:public:push:wp_post' ); | ||
|
||
const joinChannel = ( store, site, post ) => { | ||
if ( ! socket || site.is_private ) { | ||
return; | ||
} | ||
|
||
channel = socket.channel( channelTopicPrefix + post.global_ID ); | ||
registerEventHandlers( channel, store ); | ||
|
||
channel | ||
.join() | ||
.receive( 'ok', () => debug( 'channel join ok' ) ) | ||
.receive( 'error', ( { reason } ) => { | ||
debug( 'channel join error', reason ); | ||
channel.leave(); | ||
channel = null; | ||
} ); | ||
}; | ||
|
||
const leaveChannel = () => { | ||
channel && channel.leave(); | ||
channel = null; | ||
}; | ||
|
||
export default store => next => action => { | ||
switch ( action.type ) { | ||
case LASAGNA_SOCKET_CONNECTED: { | ||
const state = store.getState(); | ||
const postKey = getReaderFullViewPostKey( state ); | ||
const post = getPostByKey( state, postKey ); | ||
|
||
if ( ! post ) { | ||
break; | ||
} | ||
|
||
const site = getSite( state, post.site_ID ); | ||
|
||
if ( ! site ) { | ||
break; | ||
} | ||
|
||
joinChannel( store, site, post ); | ||
break; | ||
} | ||
|
||
case READER_POST_SEEN: | ||
joinChannel( store, action.payload.site, action.payload.post ); | ||
break; | ||
|
||
case ROUTE_SET: | ||
leaveChannel(); | ||
break; | ||
} | ||
|
||
return next( action ); | ||
}; |
Oops, something went wrong.