Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F3333735
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
28 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/lib/selectors/ping-selectors.js b/lib/selectors/ping-selectors.js
index f20efc201..3e70213ce 100644
--- a/lib/selectors/ping-selectors.js
+++ b/lib/selectors/ping-selectors.js
@@ -1,123 +1,128 @@
// @flow
import type { BaseAppState } from '../types/redux-types';
import type { CalendarQuery, RawEntryInfo } from '../types/entry-types';
import type { CurrentUserInfo } from '../types/user-types';
import type { PingStartingPayload, PingActionInput } from '../types/ping-types';
import type { RawThreadInfo } from '../types/thread-types';
import {
serverRequestTypes,
type ServerRequest,
type ThreadPollPushInconsistencyClientResponse,
} from '../types/request-types';
import { createSelector } from 'reselect';
import { nextSessionID } from './session-selectors';
import { currentCalendarQuery } from './nav-selectors';
import { getConfig } from '../utils/config';
const pingStartingPayload = createSelector(
(state: BaseAppState<*>) => !!(state.currentUserInfo &&
!state.currentUserInfo.anonymous && true),
nextSessionID,
currentCalendarQuery,
(
loggedIn: bool,
nextSessionID: () => ?string,
currentCalendarQuery: () => CalendarQuery,
): () => PingStartingPayload => {
return () => {
const calendarQuery = currentCalendarQuery();
const sessionID = nextSessionID();
const time = Date.now();
if (sessionID) {
return { loggedIn, calendarQuery, time, newSessionID: sessionID };
} else {
return { loggedIn, calendarQuery, time };
}
};
},
);
let initialPlatformDetailsSentAsOf = null;
// This gets generated and passed in to the action function, which then passes
// it on in the PING_SUCCESS payload
const pingActionInput = createSelector(
(state: BaseAppState<*>) => state.threadStore.threadInfos,
(state: BaseAppState<*>) => state.entryStore.entryInfos,
(state: BaseAppState<*>) => state.currentUserInfo,
(state: BaseAppState<*>) => state.messageStore.currentAsOf,
(state: BaseAppState<*>) => state.updatesCurrentAsOf,
(state: BaseAppState<*>) => state.activeServerRequests,
(state: BaseAppState<*>) => state.deviceToken,
(state: BaseAppState<*>) => state.threadStore.inconsistencyResponses,
(state: BaseAppState<*>) => state.pingTimestamps.lastSuccess,
(
threadInfos: {[id: string]: RawThreadInfo},
entryInfos: {[id: string]: RawEntryInfo},
currentUserInfo: ?CurrentUserInfo,
messagesCurrentAsOf: number,
updatesCurrentAsOf: number,
activeServerRequests: $ReadOnlyArray<ServerRequest>,
deviceToken: ?string,
inconsistencyResponses:
$ReadOnlyArray<ThreadPollPushInconsistencyClientResponse>,
lastSuccess: number,
): (startingPayload: PingStartingPayload) => PingActionInput => {
const clientResponses = [...inconsistencyResponses];
- let serverRequestedPlatformDetails = false;
+ const serverRequestedPlatformDetails = activeServerRequests.some(
+ request => request.type === serverRequestTypes.PLATFORM_DETAILS,
+ );
for (let serverRequest of activeServerRequests) {
- if (serverRequest.type === serverRequestTypes.PLATFORM) {
+ if (
+ serverRequest.type === serverRequestTypes.PLATFORM &&
+ !serverRequestedPlatformDetails
+ ) {
clientResponses.push({
type: serverRequestTypes.PLATFORM,
platform: getConfig().platformDetails.platform,
});
} else if (
serverRequest.type === serverRequestTypes.DEVICE_TOKEN &&
deviceToken !== null && deviceToken !== undefined
) {
clientResponses.push({
type: serverRequestTypes.DEVICE_TOKEN,
deviceToken,
});
} else if (serverRequest.type === serverRequestTypes.PLATFORM_DETAILS) {
- serverRequestedPlatformDetails = true;
clientResponses.push({
type: serverRequestTypes.PLATFORM_DETAILS,
platformDetails: getConfig().platformDetails,
});
}
}
// Whenever the app starts up, it's possible that the version has updated.
// We always communicate the PlatformDetails in that case.
if (
- !serverRequestedPlatformDetails &&
- (initialPlatformDetailsSentAsOf === null ||
- initialPlatformDetailsSentAsOf === lastSuccess)
+ initialPlatformDetailsSentAsOf === null ||
+ initialPlatformDetailsSentAsOf === lastSuccess
) {
initialPlatformDetailsSentAsOf = lastSuccess;
- clientResponses.push({
- type: serverRequestTypes.PLATFORM_DETAILS,
- platformDetails: getConfig().platformDetails,
- });
+ if (!serverRequestedPlatformDetails) {
+ clientResponses.push({
+ type: serverRequestTypes.PLATFORM_DETAILS,
+ platformDetails: getConfig().platformDetails,
+ });
+ }
}
return (startingPayload: PingStartingPayload) => ({
loggedIn: startingPayload.loggedIn,
calendarQuery: startingPayload.calendarQuery,
messagesCurrentAsOf,
updatesCurrentAsOf,
prevState: {
threadInfos,
entryInfos,
currentUserInfo,
},
clientResponses,
});
},
);
export {
pingStartingPayload,
pingActionInput,
};
diff --git a/server/src/responders/ping-responders.js b/server/src/responders/ping-responders.js
index f6524e80f..8437be136 100644
--- a/server/src/responders/ping-responders.js
+++ b/server/src/responders/ping-responders.js
@@ -1,268 +1,276 @@
// @flow
import type { PingRequest, PingResponse } from 'lib/types/ping-types';
import { defaultNumberPerThread } from 'lib/types/message-types';
import type { Viewer } from '../session/viewer';
import {
serverRequestTypes,
type ThreadPollPushInconsistencyClientResponse,
} from 'lib/types/request-types';
import { isDeviceType, assertDeviceType } from 'lib/types/device-types';
import { reportTypes } from 'lib/types/report-types';
import t from 'tcomb';
import invariant from 'invariant';
import { ServerError } from 'lib/utils/errors';
import { mostRecentMessageTimestamp } from 'lib/shared/message-utils';
import { mostRecentUpdateTimestamp } from 'lib/shared/update-utils';
import {
validateInput,
tShape,
tPlatform,
tPlatformDetails,
} from '../utils/validation-utils';
import {
entryQueryInputValidator,
normalizeCalendarQuery,
verifyCalendarQueryThreadIDs,
} from './entry-responders';
import { fetchMessageInfosSince } from '../fetchers/message-fetchers';
import { fetchThreadInfos } from '../fetchers/thread-fetchers';
import { fetchEntryInfos } from '../fetchers/entry-fetchers';
import { updateActivityTime } from '../updaters/activity-updaters';
import { fetchCurrentUserInfo } from '../fetchers/user-fetchers';
import { fetchUpdateInfos } from '../fetchers/update-fetchers';
import {
recordDeliveredUpdate,
setCookiePlatform,
setCookiePlatformDetails,
} from '../session/cookies';
import { deviceTokenUpdater } from '../updaters/device-token-updaters';
import createReport from '../creators/report-creator';
const pingRequestInputValidator = tShape({
calendarQuery: entryQueryInputValidator,
lastPing: t.maybe(t.Number), // deprecated
messagesCurrentAsOf: t.maybe(t.Number),
updatesCurrentAsOf: t.maybe(t.Number),
watchedIDs: t.list(t.String),
clientResponses: t.maybe(t.list(t.union([
tShape({
type: t.irreducible(
'serverRequestTypes.PLATFORM',
x => x === serverRequestTypes.PLATFORM,
),
platform: tPlatform,
}),
tShape({
type: t.irreducible(
'serverRequestTypes.DEVICE_TOKEN',
x => x === serverRequestTypes.DEVICE_TOKEN,
),
deviceToken: t.String,
}),
tShape({
type: t.irreducible(
'serverRequestTypes.THREAD_POLL_PUSH_INCONSISTENCY',
x => x === serverRequestTypes.THREAD_POLL_PUSH_INCONSISTENCY,
),
platformDetails: tPlatformDetails,
beforeAction: t.Object,
action: t.Object,
pollResult: t.Object,
pushResult: t.Object,
}),
tShape({
type: t.irreducible(
'serverRequestTypes.PLATFORM_DETAILS',
x => x === serverRequestTypes.PLATFORM_DETAILS,
),
platformDetails: tPlatformDetails,
}),
]))),
});
async function pingResponder(
viewer: Viewer,
input: any,
): Promise<PingResponse> {
const request: PingRequest = input;
request.calendarQuery = normalizeCalendarQuery(request.calendarQuery);
validateInput(pingRequestInputValidator, request);
let clientMessagesCurrentAsOf;
if (
request.messagesCurrentAsOf !== null &&
request.messagesCurrentAsOf !== undefined
) {
clientMessagesCurrentAsOf = request.messagesCurrentAsOf;
} else if (request.lastPing !== null && request.lastPing !== undefined) {
clientMessagesCurrentAsOf = request.lastPing;
}
if (
clientMessagesCurrentAsOf === null ||
clientMessagesCurrentAsOf === undefined
) {
throw new ServerError('invalid_parameters');
}
await verifyCalendarQueryThreadIDs(request.calendarQuery);
const threadCursors = {};
for (let watchedThreadID of request.watchedIDs) {
threadCursors[watchedThreadID] = null;
}
const threadSelectionCriteria = { threadCursors, joinedThreads: true };
- const clientResponsePromises = [];
let viewerMissingPlatform = !viewer.platform;
const platformDetails = viewer.platformDetails;
let viewerMissingPlatformDetails = !platformDetails ||
(isDeviceType(viewer.platform) &&
(platformDetails.codeVersion === null ||
platformDetails.codeVersion === undefined ||
platformDetails.stateVersion === null ||
platformDetails.stateVersion === undefined));
let viewerMissingDeviceToken =
isDeviceType(viewer.platform) && viewer.loggedIn && !viewer.deviceToken;
- if (request.clientResponses) {
- for (let clientResponse of request.clientResponses) {
- if (clientResponse.type === serverRequestTypes.PLATFORM) {
+
+ const { clientResponses } = request;
+ const clientResponsePromises = [];
+ if (clientResponses) {
+ const clientSentPlatformDetails = clientResponses.some(
+ response => response.type === serverRequestTypes.PLATFORM_DETAILS,
+ );
+ for (let clientResponse of clientResponses) {
+ if (
+ clientResponse.type === serverRequestTypes.PLATFORM &&
+ !clientSentPlatformDetails
+ ) {
clientResponsePromises.push(setCookiePlatform(
viewer.cookieID,
clientResponse.platform,
));
viewerMissingPlatform = false;
if (!isDeviceType(clientResponse.platform)) {
viewerMissingPlatformDetails = false;
}
} else if (clientResponse.type === serverRequestTypes.DEVICE_TOKEN) {
clientResponsePromises.push(deviceTokenUpdater(
viewer,
{
deviceToken: clientResponse.deviceToken,
deviceType: assertDeviceType(viewer.platform),
},
));
viewerMissingDeviceToken = false;
} else if (
clientResponse.type ===
serverRequestTypes.THREAD_POLL_PUSH_INCONSISTENCY
) {
clientResponsePromises.push(recordThreadPollPushInconsistency(
viewer,
clientResponse,
));
} else if (clientResponse.type === serverRequestTypes.PLATFORM_DETAILS) {
clientResponsePromises.push(setCookiePlatformDetails(
viewer.cookieID,
clientResponse.platformDetails,
));
viewerMissingPlatform = false;
viewerMissingPlatformDetails = false;
}
}
}
const oldUpdatesCurrentAsOf = request.updatesCurrentAsOf;
const [
messagesResult,
threadsResult,
entriesResult,
currentUserInfo,
] = await Promise.all([
fetchMessageInfosSince(
viewer,
threadSelectionCriteria,
clientMessagesCurrentAsOf,
defaultNumberPerThread,
),
fetchThreadInfos(viewer),
fetchEntryInfos(viewer, request.calendarQuery),
fetchCurrentUserInfo(viewer),
clientResponsePromises.length > 0
? Promise.all(clientResponsePromises)
: null,
]);
let updatesResult = null;
if (oldUpdatesCurrentAsOf !== null && oldUpdatesCurrentAsOf !== undefined) {
const { updateInfos } = await fetchUpdateInfos(
viewer,
oldUpdatesCurrentAsOf,
{ ...threadsResult, calendarQuery: request.calendarQuery },
);
const newUpdatesCurrentAsOf = mostRecentUpdateTimestamp(
[...updateInfos],
oldUpdatesCurrentAsOf,
);
updatesResult = {
newUpdates: updateInfos,
currentAsOf: newUpdatesCurrentAsOf,
};
}
const timestampUpdatePromises = [ updateActivityTime(viewer) ];
if (updatesResult && updatesResult.newUpdates.length > 0) {
timestampUpdatePromises.push(
recordDeliveredUpdate(viewer.cookieID, updatesResult.currentAsOf),
);
}
await Promise.all(timestampUpdatePromises);
const userInfos: any = Object.values({
...messagesResult.userInfos,
...entriesResult.userInfos,
...threadsResult.userInfos,
});
const serverRequests = [];
if (viewerMissingPlatform) {
serverRequests.push({ type: serverRequestTypes.PLATFORM });
}
if (viewerMissingPlatformDetails) {
serverRequests.push({ type: serverRequestTypes.PLATFORM_DETAILS });
}
if (viewerMissingDeviceToken) {
serverRequests.push({ type: serverRequestTypes.DEVICE_TOKEN });
}
const messagesCurrentAsOf = mostRecentMessageTimestamp(
messagesResult.rawMessageInfos,
clientMessagesCurrentAsOf,
);
const response: PingResponse = {
threadInfos: threadsResult.threadInfos,
currentUserInfo,
rawMessageInfos: messagesResult.rawMessageInfos,
truncationStatuses: messagesResult.truncationStatuses,
messagesCurrentAsOf,
serverTime: messagesCurrentAsOf,
rawEntryInfos: entriesResult.rawEntryInfos,
userInfos,
serverRequests,
};
if (updatesResult) {
response.updatesResult = updatesResult;
}
return response;
}
async function recordThreadPollPushInconsistency(
viewer: Viewer,
response: ThreadPollPushInconsistencyClientResponse,
): Promise<void> {
const { type, ...rest } = response;
const reportCreationRequest = {
...rest,
type: reportTypes.THREAD_POLL_PUSH_INCONSISTENCY,
};
await createReport(viewer, reportCreationRequest);
}
export {
pingResponder,
};
diff --git a/server/src/session/cookies.js b/server/src/session/cookies.js
index 1bb0a5246..671c1a648 100644
--- a/server/src/session/cookies.js
+++ b/server/src/session/cookies.js
@@ -1,469 +1,487 @@
// @flow
import type { $Response, $Request } from 'express';
import type { UserInfo, CurrentUserInfo } from 'lib/types/user-types';
import type { RawThreadInfo } from 'lib/types/thread-types';
import type { ViewerData, AnonymousViewerData, UserViewerData } from './viewer';
import type { Platform, PlatformDetails } from 'lib/types/device-types';
import bcrypt from 'twin-bcrypt';
import url from 'url';
import crypto from 'crypto';
import { ServerError } from 'lib/utils/errors';
import { dbQuery, SQL } from '../database';
import { Viewer } from './viewer';
import { fetchThreadInfos } from '../fetchers/thread-fetchers';
import urlFacts from '../../facts/url';
import createIDs from '../creators/id-creator';
import { assertSecureRequest } from '../utils/security-utils';
import { deleteCookie } from '../deleters/cookie-deleters';
import { handleAsyncPromise } from '../responders/handlers';
const { baseDomain, basePath, https } = urlFacts;
const cookieLifetime = 30*24*60*60*1000; // in milliseconds
const cookieSource = Object.freeze({
BODY: 0,
HEADER: 1,
GENERATED: 2,
});
export type CookieSource = $Values<typeof cookieSource>;
const cookieType = Object.freeze({
USER: "user",
ANONYMOUS: "anonymous",
});
type CookieType = $Values<typeof cookieType>;
function cookieIsExpired(lastUpdate: number) {
return lastUpdate + cookieLifetime <= Date.now();
}
type FetchViewerResult =
| {| type: "valid", viewer: Viewer |}
| {| type: "nonexistant", cookieName: ?string, source: CookieSource |}
| {|
type: "invalidated",
cookieName: string,
cookieID: string,
source: CookieSource,
platformDetails: ?PlatformDetails,
|};
async function fetchUserViewer(
cookie: string,
source: CookieSource,
): Promise<FetchViewerResult> {
const [ cookieID, cookiePassword ] = cookie.split(':');
if (!cookieID || !cookiePassword) {
return { type: "nonexistant", cookieName: cookieType.USER, source };
}
const query = SQL`
SELECT hash, user, last_used, platform, device_token, versions
FROM cookies
WHERE id = ${cookieID} AND user IS NOT NULL
`;
const [ result ] = await dbQuery(query);
if (result.length === 0) {
return { type: "nonexistant", cookieName: cookieType.USER, source };
}
const cookieRow = result[0];
let platformDetails = null;
if (cookieRow.platform && cookieRow.versions) {
platformDetails = {
platform: cookieRow.platform,
codeVersion: cookieRow.versions.codeVersion,
stateVersion: cookieRow.versions.stateVersion,
};
} else if (cookieRow.platform) {
platformDetails = { platform: cookieRow.platform };
}
if (
!bcrypt.compareSync(cookiePassword, cookieRow.hash) ||
cookieIsExpired(cookieRow.last_used)
) {
return {
type: "invalidated",
cookieName: cookieType.USER,
cookieID,
source,
platformDetails,
};
}
const userID = cookieRow.user.toString();
const viewer = new Viewer(
{
loggedIn: true,
id: userID,
platformDetails,
deviceToken: cookieRow.device_token,
userID,
cookieID,
cookiePassword,
},
source,
);
return { type: "valid", viewer };
}
async function fetchAnonymousViewer(
cookie: string,
source: CookieSource,
): Promise<FetchViewerResult> {
const [ cookieID, cookiePassword ] = cookie.split(':');
if (!cookieID || !cookiePassword) {
return { type: "nonexistant", cookieName: cookieType.ANONYMOUS, source };
}
const query = SQL`
SELECT last_used, hash, platform, device_token, versions
FROM cookies
WHERE id = ${cookieID} AND user IS NULL
`;
const [ result ] = await dbQuery(query);
if (result.length === 0) {
return { type: "nonexistant", cookieName: cookieType.ANONYMOUS, source };
}
const cookieRow = result[0];
let platformDetails = null;
if (cookieRow.platform && cookieRow.versions) {
platformDetails = {
platform: cookieRow.platform,
codeVersion: cookieRow.versions.codeVersion,
stateVersion: cookieRow.versions.stateVersion,
};
} else if (cookieRow.platform) {
platformDetails = { platform: cookieRow.platform };
}
if (
!bcrypt.compareSync(cookiePassword, cookieRow.hash) ||
cookieIsExpired(cookieRow.last_used)
) {
return {
type: "invalidated",
cookieName: cookieType.ANONYMOUS,
cookieID,
source,
platformDetails,
};
}
const viewer = new Viewer(
{
loggedIn: false,
id: cookieID,
platformDetails,
deviceToken: cookieRow.device_token,
cookieID,
cookiePassword,
},
source,
);
return { type: "valid", viewer };
}
// This function is meant to consume a cookie that has already been processed.
// That means it doesn't have any logic to handle an invalid cookie, and it
// doesn't update the cookie's last_used timestamp.
async function fetchViewerFromCookieData(
cookieData: {[cookieName: string]: string},
): Promise<?FetchViewerResult> {
if (cookieData.user) {
return await fetchUserViewer(cookieData.user, cookieSource.HEADER);
} else if (cookieData.anonymous) {
return await fetchAnonymousViewer(cookieData.anonymous, cookieSource.HEADER);
}
return null;
}
async function fetchViewerFromRequestBody(
req: $Request,
): Promise<?FetchViewerResult> {
const body = (req.body: any);
const cookiePair = body.cookie;
if (cookiePair === null) {
return { type: "nonexistant", cookieName: null, source: cookieSource.BODY };
}
if (!cookiePair || !(typeof cookiePair === "string")) {
return null;
}
const [ type, cookie ] = cookiePair.split("=");
if (type === cookieType.USER && cookie) {
return await fetchUserViewer(cookie, cookieSource.BODY);
} else if (type === cookieType.ANONYMOUS && cookie) {
return await fetchAnonymousViewer(cookie, cookieSource.BODY);
}
return null;
}
async function fetchViewerForJSONRequest(req: $Request): Promise<Viewer> {
assertSecureRequest(req);
let result = await fetchViewerFromRequestBody(req);
if (!result) {
result = await fetchViewerFromCookieData(req.cookies);
}
return await handleFetchViewerResult(result);
}
const webPlatformDetails = { platform: "web" };
async function fetchViewerForHomeRequest(req: $Request): Promise<Viewer> {
assertSecureRequest(req);
const result = await fetchViewerFromCookieData(req.cookies);
return await handleFetchViewerResult(result, webPlatformDetails);
}
async function handleFetchViewerResult(
result: ?FetchViewerResult,
inputPlatformDetails?: PlatformDetails,
) {
if (result && result.type === "valid") {
return result.viewer;
}
let platformDetails = inputPlatformDetails;
if (!platformDetails && result && result.type === "invalidated") {
platformDetails = result.platformDetails;
}
const [ anonymousViewerData ] = await Promise.all([
createNewAnonymousCookie(platformDetails),
result && result.type === "invalidated"
? deleteCookie(result.cookieID)
: null,
]);
const source = result ? result.source : cookieSource.GENERATED;
const viewer = new Viewer(anonymousViewerData, source);
if (result) {
viewer.cookieChanged = true;
// If cookieName is falsey, that means it's not an actual invalidation. It
// means there was a null cookie specified in the request body, which tells
// us that the client wants the new cookie specified in the result body.
if (result.cookieName) {
viewer.cookieInvalidated = true;
viewer.initialCookieName = result.cookieName;
}
}
return viewer;
}
type CookieChange = {|
threadInfos: {[id: string]: RawThreadInfo},
userInfos: $ReadOnlyArray<UserInfo>,
cookieInvalidated: bool,
currentUserInfo?: CurrentUserInfo,
cookie?: string,
|};
const domainAsURL = new url.URL(baseDomain);
const cookieOptions = {
domain: domainAsURL.hostname,
path: basePath,
httpOnly: true,
secure: https,
};
async function addCookieChangeInfoToResult(
viewer: Viewer,
res: $Response,
result: Object,
) {
const { threadInfos, userInfos } = await fetchThreadInfos(viewer);
const userInfosArray: any = Object.values(userInfos);
const cookieChange: CookieChange = {
threadInfos,
userInfos: userInfosArray,
cookieInvalidated: viewer.cookieInvalidated,
};
if (viewer.cookieInvalidated) {
cookieChange.currentUserInfo = {
id: viewer.cookieID,
anonymous: true,
};
}
if (viewer.initializationSource === cookieSource.BODY) {
cookieChange.cookie = viewer.cookiePairString;
} else {
res.cookie(
viewer.cookieName,
viewer.cookieString,
{
...cookieOptions,
maxAge: cookieLifetime,
},
);
if (viewer.cookieName !== viewer.initialCookieName) {
res.clearCookie(viewer.initialCookieName, cookieOptions);
}
}
result.cookieChange = cookieChange;
}
const defaultPlatformDetails = {};
async function createNewAnonymousCookie(
platformDetails: ?PlatformDetails,
): Promise<AnonymousViewerData> {
const time = Date.now();
const cookiePassword = crypto.randomBytes(32).toString('hex');
const cookieHash = bcrypt.hashSync(cookiePassword);
const [ id ] = await createIDs("cookies", 1);
const { platform, ...versions } = (platformDetails || defaultPlatformDetails);
- const cookieRow = [id, cookieHash, null, platform, time, time, 0, versions];
+ const versionsString = Object.keys(versions).length > 0
+ ? JSON.stringify(versions)
+ : null;
+ const cookieRow = [
+ id,
+ cookieHash,
+ null,
+ platform,
+ time,
+ time,
+ 0,
+ versionsString,
+ ];
const query = SQL`
INSERT INTO cookies(id, hash, user, platform, creation_time, last_used,
last_update, versions)
VALUES ${[cookieRow]}
`;
await dbQuery(query);
return {
loggedIn: false,
id,
platformDetails,
deviceToken: null,
cookieID: id,
cookiePassword,
insertionTime: time,
};
}
async function createNewUserCookie(
userID: string,
initialLastUpdate: number,
platformDetails: ?PlatformDetails,
): Promise<UserViewerData> {
const time = Date.now();
const cookiePassword = crypto.randomBytes(32).toString('hex');
const cookieHash = bcrypt.hashSync(cookiePassword);
const [ cookieID ] = await createIDs("cookies", 1);
const { platform, ...versions } = (platformDetails || defaultPlatformDetails);
+ const versionsString = Object.keys(versions).length > 0
+ ? JSON.stringify(versions)
+ : null;
const cookieRow = [
cookieID,
cookieHash,
userID,
platform,
time,
time,
initialLastUpdate,
- versions,
+ versionsString,
];
const query = SQL`
INSERT INTO cookies(id, hash, user, platform, creation_time, last_used,
last_update, versions)
VALUES ${[cookieRow]}
`;
await dbQuery(query);
return {
loggedIn: true,
id: userID,
platformDetails,
deviceToken: null,
userID,
cookieID,
cookiePassword,
insertionTime: time,
};
}
async function extendCookieLifespan(cookieID: string) {
const time = Date.now();
const query = SQL`
UPDATE cookies SET last_used = ${time} WHERE id = ${cookieID}
`;
await dbQuery(query);
}
async function recordDeliveredUpdate(
cookieID: string,
mostRecentUpdateTimestamp: number,
) {
const query = SQL`
UPDATE cookies
SET last_update = ${mostRecentUpdateTimestamp}
WHERE id = ${cookieID}
`;
await dbQuery(query);
}
async function addCookieToJSONResponse(
viewer: Viewer,
res: $Response,
result: Object,
) {
if (viewer.cookieChanged) {
await addCookieChangeInfoToResult(viewer, res, result);
return;
}
if (!viewer.getData().insertionTime) {
handleAsyncPromise(extendCookieLifespan(viewer.cookieID));
}
if (viewer.initializationSource !== cookieSource.BODY) {
res.cookie(
viewer.cookieName,
viewer.cookieString,
{
...cookieOptions,
maxAge: cookieLifetime,
},
);
}
}
function addCookieToHomeResponse(viewer: Viewer, res: $Response) {
if (!viewer.getData().insertionTime) {
handleAsyncPromise(extendCookieLifespan(viewer.cookieID));
}
res.cookie(
viewer.cookieName,
viewer.cookieString,
{
...cookieOptions,
maxAge: cookieLifetime,
},
);
if (viewer.cookieName !== viewer.initialCookieName) {
res.clearCookie(viewer.initialCookieName, cookieOptions);
}
}
async function setCookiePlatform(
cookieID: string,
platform: Platform,
): Promise<void> {
const query = SQL`
UPDATE cookies
SET platform = ${platform}
WHERE id = ${cookieID}
`;
await dbQuery(query);
}
async function setCookiePlatformDetails(
cookieID: string,
platformDetails: PlatformDetails,
): Promise<void> {
const { platform, ...versions } = platformDetails;
+ const versionsString = Object.keys(versions).length > 0
+ ? JSON.stringify(versions)
+ : null;
const query = SQL`
UPDATE cookies
- SET platform = ${platform}, versions = ${versions}
+ SET platform = ${platform}, versions = ${versionsString}
WHERE id = ${cookieID}
`;
await dbQuery(query);
}
export {
cookieLifetime,
cookieType,
fetchViewerForJSONRequest,
fetchViewerForHomeRequest,
createNewAnonymousCookie,
createNewUserCookie,
recordDeliveredUpdate,
addCookieToJSONResponse,
addCookieToHomeResponse,
setCookiePlatform,
setCookiePlatformDetails,
};
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Nov 23, 5:08 AM (1 d, 19 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2560040
Default Alt Text
(28 KB)
Attached To
Mode
rCOMM Comm
Attached
Detach File
Event Timeline
Log In to Comment