Skip to content
This repository has been archived by the owner on Oct 11, 2022. It is now read-only.

Commit

Permalink
Merge pull request #3765 from withspectrum/2.4.25
Browse files Browse the repository at this point in the history
2.4.25
  • Loading branch information
brianlovin authored Aug 13, 2018
2 parents 5fadf98 + 7a5ff25 commit e5ca1d8
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 20 deletions.
8 changes: 3 additions & 5 deletions api/routes/create-subscription-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,17 @@ const createSubscriptionsServer = (server: any, path: string) => {
},
onDisconnect: rawSocket => {
return getUserIdFromReq(rawSocket.upgradeReq)
.then(id => {
return setUserOnline(id, false);
})
.then(id => id && setUserOnline(id, false))
.catch(err => {
console.error(err);
});
},
onConnect: (connectionParams, rawSocket) =>
getUserIdFromReq(rawSocket.upgradeReq)
.then(id => setUserOnline(id, true))
.then(id => (id ? setUserOnline(id, true) : null))
.then(user => {
return {
user,
user: user || null,
loaders: createLoaders({ cache: false }),
};
})
Expand Down
11 changes: 7 additions & 4 deletions api/utils/session-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ const ONE_DAY = 86400000;
/**
* Get the sessions' users' ID of a req manually, needed for websocket authentication
*/
export const getUserIdFromReq = (req: any): Promise<string> =>
export const getUserIdFromReq = (req: any): Promise<?string> =>
new Promise((res, rej) => {
session(req, {}, err => {
if (err) return rej(err);
if (!req.session || !req.session.passport || !req.session.passport.user)
return rej();
if (err) {
return rej(err);
}
if (!req.session || !req.session.passport || !req.session.passport.user) {
return res(null);
}

return res(req.session.passport.user);
});
Expand Down
4 changes: 0 additions & 4 deletions now.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
{
"scale": {
"bru1": {
"min": 0,
"max": 0
},
"sfo1": {
"min": 1,
"max": "auto"
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "Spectrum",
"version": "2.4.24",
"version": "2.4.25",
"license": "BSD-3-Clause",
"devDependencies": {
"babel-cli": "^6.24.1",
Expand Down
1 change: 1 addition & 0 deletions shared/middlewares/cors.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export default cors({
/\.spectrum\.chat$/,
process.env.NOW_URL,
'https://zeit.co',
/(\.|https:\/\/)zeit\.sh$/,
].filter(Boolean)
: [/localhost/],
credentials: true,
Expand Down
75 changes: 75 additions & 0 deletions src/helpers/generate-image-from-text.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// @flow
// Generates a meta image which shows a title and a footer text on a nice Spectrum background.
import theme from 'shared/theme';
import { btoa } from 'abab';
import { stringify } from 'query-string';

// NOTES(@mxstbr):
// Imgix has a useful ~text endpoint that allows us to add text to any image, but unfortunately that endpoint only allows us to add one piece of text to an image—but we need two!
// We work around this by generating two images that only contain the text of the title and the footer respectively, nothing else, and then using the blend option to blend the background image with the title image (which only has the title text) and used the mark option to add our footer text as a "watermark" to the footer.

const WIDTH = 1500;
const IMGIX_TEXT_ENDPOINT = 'https://assets.imgix.net/~text';

const TITLE_PARAMS = {
w: WIDTH * 0.8, // 10% padding on each side
h: 270, // Magic number, clips text after three lines
txtsize: 56,
txtlead: 16,
txtfont: 'Helvetica,Bold',
txtalign: 'left,middle',
txtcolor: 'ffffff',
// NOTE(@mxstbr): txtclip (i.e. ellipsis on overflowing text) only works with single-line text, which titles aren't, so this doesn't do anything rn
txtclip: 'end,ellipsis',
};

const FOOTER_PARAMS = {
h: 64,
txtsize: 36,
txtcolor: theme.brand.default.replace('#', ''),
txtalign: 'right,middle',
txtfont: 'Helvetica,Bold',
};

const BACKGROUND_URL = `https://spectrum.imgix.net/default_images/twitter-share-card.png`;

type GetMetaImageInput = {
title: string,
footer: string,
};

const generateImageFromText = ({ title, footer }: GetMetaImageInput) => {
const base64title = btoa(title);
const base64footer = btoa(footer);
if (!base64title || !base64footer) return;

const titleUrl = `${IMGIX_TEXT_ENDPOINT}?${stringify(
{ ...TITLE_PARAMS, txt64: base64title.replace('=', '') },
{ encode: false }
)}`;
const footerUrl = `${IMGIX_TEXT_ENDPOINT}?${stringify(
{ ...FOOTER_PARAMS, txt64: base64footer.replace(/=/g, '') },
{ encode: false }
)}`;

const base64titleurl = btoa(titleUrl);
const base64footerurl = btoa(footerUrl);

if (!base64titleurl || !base64footerurl) return;

const BACKGROUND_PARAMS = {
w: WIDTH,
bm: 'normal', // Blend the title normally, don't change opacity or color or anything, just overlay it
by: 170, // Magic numbers that get the position right
bx: 180,
markalign: 'left,bottom', // Show the footer on the left side
markpad: 24, // We overwrite the X pos, so the padding only applies on the y-axis
markx: 100,
blend64: btoa(titleUrl).replace(/=/g, ''),
mark64: btoa(footerUrl).replace(/=/g, ''),
};

return `${BACKGROUND_URL}?${stringify(BACKGROUND_PARAMS, { encode: false })}`;
};

export default generateImageFromText;
2 changes: 1 addition & 1 deletion src/views/notifications/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ const threadToString = (context, currentUser) => {
<Link
to={{
pathname: window.location.pathname,
search: `?thread=${context.payload.id}`,
search: `?thread=${context.payload.threadId}`,
}}
>
{context.payload.content.title}
Expand Down
27 changes: 22 additions & 5 deletions src/views/thread/container.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
} from './style';
import WatercoolerActionBar from './components/watercoolerActionBar';
import { ErrorBoundary } from 'src/components/error';
import generateImageFromText from 'src/helpers/generate-image-from-text';

type Props = {
data: {
Expand Down Expand Up @@ -226,7 +227,11 @@ class ThreadContainer extends React.Component<Props, State> {
// we never autofocus on mobile
if (window && window.innerWidth < 768) return;

const { currentUser, data: { thread }, threadSliderIsOpen } = this.props;
const {
currentUser,
data: { thread },
threadSliderIsOpen,
} = this.props;

// if no thread has been returned yet from the query, we don't know whether or not to focus yet
if (!thread) return;
Expand Down Expand Up @@ -270,7 +275,10 @@ class ThreadContainer extends React.Component<Props, State> {

renderChatInputOrUpsell = () => {
const { isEditing } = this.state;
const { data: { thread }, currentUser } = this.props;
const {
data: { thread },
currentUser,
} = this.props;

if (!thread) return null;
if (thread.isLocked) return null;
Expand Down Expand Up @@ -319,7 +327,11 @@ class ThreadContainer extends React.Component<Props, State> {
};

renderPost = () => {
const { data: { thread }, slider, currentUser } = this.props;
const {
data: { thread },
slider,
currentUser,
} = this.props;
if (!thread || !thread.id) return null;

if (thread.watercooler) {
Expand Down Expand Up @@ -445,8 +457,13 @@ class ThreadContainer extends React.Component<Props, State> {
<Head
title={headTitle}
description={headDescription}
image={thread.community.profilePhoto}
/>
image={generateImageFromText({
title: headTitle,
footer: `spectrum.chat/${thread.community.slug}`,
})}
>
<meta name="twitter:card" content="summary_large_image" />
</Head>
<Titlebar
title={thread.content.title}
subtitle={`${thread.community.name} / ${thread.channel.name}`}
Expand Down

0 comments on commit e5ca1d8

Please sign in to comment.