Page MenuHomePhabricator

No OneTemporary

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

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)

Event Timeline