[Session] Logging (#4476)
* Add session logging (console.log) * Hook it up for real * Send type separately
This commit is contained in:
parent
4bba59790a
commit
4c48a1f14b
3 changed files with 178 additions and 6 deletions
|
@ -19,6 +19,7 @@ import {
|
||||||
import {getInitialState, reducer} from './reducer'
|
import {getInitialState, reducer} from './reducer'
|
||||||
|
|
||||||
export {isSignupQueued} from './util'
|
export {isSignupQueued} from './util'
|
||||||
|
import {addSessionDebugLog} from './logging'
|
||||||
export type {SessionAccount} from '#/state/session/types'
|
export type {SessionAccount} from '#/state/session/types'
|
||||||
import {SessionApiContext, SessionStateContext} from '#/state/session/types'
|
import {SessionApiContext, SessionStateContext} from '#/state/session/types'
|
||||||
|
|
||||||
|
@ -40,9 +41,11 @@ const ApiContext = React.createContext<SessionApiContext>({
|
||||||
|
|
||||||
export function Provider({children}: React.PropsWithChildren<{}>) {
|
export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
const cancelPendingTask = useOneTaskAtATime()
|
const cancelPendingTask = useOneTaskAtATime()
|
||||||
const [state, dispatch] = React.useReducer(reducer, null, () =>
|
const [state, dispatch] = React.useReducer(reducer, null, () => {
|
||||||
getInitialState(persisted.get('session').accounts),
|
const initialState = getInitialState(persisted.get('session').accounts)
|
||||||
)
|
addSessionDebugLog({type: 'reducer:init', state: initialState})
|
||||||
|
return initialState
|
||||||
|
})
|
||||||
|
|
||||||
const onAgentSessionChange = React.useCallback(
|
const onAgentSessionChange = React.useCallback(
|
||||||
(agent: BskyAgent, accountDid: string, sessionEvent: AtpSessionEvent) => {
|
(agent: BskyAgent, accountDid: string, sessionEvent: AtpSessionEvent) => {
|
||||||
|
@ -63,6 +66,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
|
|
||||||
const createAccount = React.useCallback<SessionApiContext['createAccount']>(
|
const createAccount = React.useCallback<SessionApiContext['createAccount']>(
|
||||||
async params => {
|
async params => {
|
||||||
|
addSessionDebugLog({type: 'method:start', method: 'createAccount'})
|
||||||
const signal = cancelPendingTask()
|
const signal = cancelPendingTask()
|
||||||
track('Try Create Account')
|
track('Try Create Account')
|
||||||
logEvent('account:create:begin', {})
|
logEvent('account:create:begin', {})
|
||||||
|
@ -81,12 +85,14 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
})
|
})
|
||||||
track('Create Account')
|
track('Create Account')
|
||||||
logEvent('account:create:success', {})
|
logEvent('account:create:success', {})
|
||||||
|
addSessionDebugLog({type: 'method:end', method: 'createAccount', account})
|
||||||
},
|
},
|
||||||
[onAgentSessionChange, cancelPendingTask],
|
[onAgentSessionChange, cancelPendingTask],
|
||||||
)
|
)
|
||||||
|
|
||||||
const login = React.useCallback<SessionApiContext['login']>(
|
const login = React.useCallback<SessionApiContext['login']>(
|
||||||
async (params, logContext) => {
|
async (params, logContext) => {
|
||||||
|
addSessionDebugLog({type: 'method:start', method: 'login'})
|
||||||
const signal = cancelPendingTask()
|
const signal = cancelPendingTask()
|
||||||
const {agent, account} = await createAgentAndLogin(
|
const {agent, account} = await createAgentAndLogin(
|
||||||
params,
|
params,
|
||||||
|
@ -103,23 +109,31 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
})
|
})
|
||||||
track('Sign In', {resumedSession: false})
|
track('Sign In', {resumedSession: false})
|
||||||
logEvent('account:loggedIn', {logContext, withPassword: true})
|
logEvent('account:loggedIn', {logContext, withPassword: true})
|
||||||
|
addSessionDebugLog({type: 'method:end', method: 'login', account})
|
||||||
},
|
},
|
||||||
[onAgentSessionChange, cancelPendingTask],
|
[onAgentSessionChange, cancelPendingTask],
|
||||||
)
|
)
|
||||||
|
|
||||||
const logout = React.useCallback<SessionApiContext['logout']>(
|
const logout = React.useCallback<SessionApiContext['logout']>(
|
||||||
logContext => {
|
logContext => {
|
||||||
|
addSessionDebugLog({type: 'method:start', method: 'logout'})
|
||||||
cancelPendingTask()
|
cancelPendingTask()
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'logged-out',
|
type: 'logged-out',
|
||||||
})
|
})
|
||||||
logEvent('account:loggedOut', {logContext})
|
logEvent('account:loggedOut', {logContext})
|
||||||
|
addSessionDebugLog({type: 'method:end', method: 'logout'})
|
||||||
},
|
},
|
||||||
[cancelPendingTask],
|
[cancelPendingTask],
|
||||||
)
|
)
|
||||||
|
|
||||||
const resumeSession = React.useCallback<SessionApiContext['resumeSession']>(
|
const resumeSession = React.useCallback<SessionApiContext['resumeSession']>(
|
||||||
async storedAccount => {
|
async storedAccount => {
|
||||||
|
addSessionDebugLog({
|
||||||
|
type: 'method:start',
|
||||||
|
method: 'resumeSession',
|
||||||
|
account: storedAccount,
|
||||||
|
})
|
||||||
const signal = cancelPendingTask()
|
const signal = cancelPendingTask()
|
||||||
const {agent, account} = await createAgentAndResume(
|
const {agent, account} = await createAgentAndResume(
|
||||||
storedAccount,
|
storedAccount,
|
||||||
|
@ -134,17 +148,24 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
newAgent: agent,
|
newAgent: agent,
|
||||||
newAccount: account,
|
newAccount: account,
|
||||||
})
|
})
|
||||||
|
addSessionDebugLog({type: 'method:end', method: 'resumeSession', account})
|
||||||
},
|
},
|
||||||
[onAgentSessionChange, cancelPendingTask],
|
[onAgentSessionChange, cancelPendingTask],
|
||||||
)
|
)
|
||||||
|
|
||||||
const removeAccount = React.useCallback<SessionApiContext['removeAccount']>(
|
const removeAccount = React.useCallback<SessionApiContext['removeAccount']>(
|
||||||
account => {
|
account => {
|
||||||
|
addSessionDebugLog({
|
||||||
|
type: 'method:start',
|
||||||
|
method: 'removeAccount',
|
||||||
|
account,
|
||||||
|
})
|
||||||
cancelPendingTask()
|
cancelPendingTask()
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'removed-account',
|
type: 'removed-account',
|
||||||
accountDid: account.did,
|
accountDid: account.did,
|
||||||
})
|
})
|
||||||
|
addSessionDebugLog({type: 'method:end', method: 'removeAccount', account})
|
||||||
},
|
},
|
||||||
[cancelPendingTask],
|
[cancelPendingTask],
|
||||||
)
|
)
|
||||||
|
@ -152,18 +173,21 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (state.needsPersist) {
|
if (state.needsPersist) {
|
||||||
state.needsPersist = false
|
state.needsPersist = false
|
||||||
persisted.write('session', {
|
const persistedData = {
|
||||||
accounts: state.accounts,
|
accounts: state.accounts,
|
||||||
currentAccount: state.accounts.find(
|
currentAccount: state.accounts.find(
|
||||||
a => a.did === state.currentAgentState.did,
|
a => a.did === state.currentAgentState.did,
|
||||||
),
|
),
|
||||||
})
|
}
|
||||||
|
addSessionDebugLog({type: 'persisted:broadcast', data: persistedData})
|
||||||
|
persisted.write('session', persistedData)
|
||||||
}
|
}
|
||||||
}, [state])
|
}, [state])
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
return persisted.onUpdate(() => {
|
return persisted.onUpdate(() => {
|
||||||
const synced = persisted.get('session')
|
const synced = persisted.get('session')
|
||||||
|
addSessionDebugLog({type: 'persisted:receive', data: synced})
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'synced-accounts',
|
type: 'synced-accounts',
|
||||||
syncedAccounts: synced.accounts,
|
syncedAccounts: synced.accounts,
|
||||||
|
@ -177,7 +201,14 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
resumeSession(syncedAccount)
|
resumeSession(syncedAccount)
|
||||||
} else {
|
} else {
|
||||||
const agent = state.currentAgentState.agent as BskyAgent
|
const agent = state.currentAgentState.agent as BskyAgent
|
||||||
|
const prevSession = agent.session
|
||||||
agent.session = sessionAccountToSession(syncedAccount)
|
agent.session = sessionAccountToSession(syncedAccount)
|
||||||
|
addSessionDebugLog({
|
||||||
|
type: 'agent:patch',
|
||||||
|
agent,
|
||||||
|
prevSession,
|
||||||
|
nextSession: agent.session,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -215,6 +246,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
// Read the previous value and immediately advance the pointer.
|
// Read the previous value and immediately advance the pointer.
|
||||||
const prevAgent = currentAgentRef.current
|
const prevAgent = currentAgentRef.current
|
||||||
currentAgentRef.current = agent
|
currentAgentRef.current = agent
|
||||||
|
addSessionDebugLog({type: 'agent:switch', prevAgent, nextAgent: agent})
|
||||||
// We never reuse agents so let's fully neutralize the previous one.
|
// We never reuse agents so let's fully neutralize the previous one.
|
||||||
// This ensures it won't try to consume any refresh tokens.
|
// This ensures it won't try to consume any refresh tokens.
|
||||||
prevAgent.session = undefined
|
prevAgent.session = undefined
|
||||||
|
|
137
src/state/session/logging.ts
Normal file
137
src/state/session/logging.ts
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
import {AtpSessionData} from '@atproto/api'
|
||||||
|
import {sha256} from 'js-sha256'
|
||||||
|
import {Statsig} from 'statsig-react-native-expo'
|
||||||
|
|
||||||
|
import {Schema} from '../persisted'
|
||||||
|
import {Action, State} from './reducer'
|
||||||
|
import {SessionAccount} from './types'
|
||||||
|
|
||||||
|
type Reducer = (state: State, action: Action) => State
|
||||||
|
|
||||||
|
type Log =
|
||||||
|
| {
|
||||||
|
type: 'reducer:init'
|
||||||
|
state: State
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'reducer:call'
|
||||||
|
action: Action
|
||||||
|
prevState: State
|
||||||
|
nextState: State
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'method:start'
|
||||||
|
method:
|
||||||
|
| 'createAccount'
|
||||||
|
| 'login'
|
||||||
|
| 'logout'
|
||||||
|
| 'resumeSession'
|
||||||
|
| 'removeAccount'
|
||||||
|
account?: SessionAccount
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'method:end'
|
||||||
|
method:
|
||||||
|
| 'createAccount'
|
||||||
|
| 'login'
|
||||||
|
| 'logout'
|
||||||
|
| 'resumeSession'
|
||||||
|
| 'removeAccount'
|
||||||
|
account?: SessionAccount
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'persisted:broadcast'
|
||||||
|
data: Schema['session']
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'persisted:receive'
|
||||||
|
data: Schema['session']
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'agent:switch'
|
||||||
|
prevAgent: object
|
||||||
|
nextAgent: object
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'agent:patch'
|
||||||
|
agent: object
|
||||||
|
prevSession: AtpSessionData | undefined
|
||||||
|
nextSession: AtpSessionData
|
||||||
|
}
|
||||||
|
|
||||||
|
export function wrapSessionReducerForLogging(reducer: Reducer): Reducer {
|
||||||
|
return function loggingWrapper(prevState: State, action: Action): State {
|
||||||
|
const nextState = reducer(prevState, action)
|
||||||
|
addSessionDebugLog({type: 'reducer:call', prevState, action, nextState})
|
||||||
|
return nextState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let nextMessageIndex = 0
|
||||||
|
const MAX_SLICE_LENGTH = 1000
|
||||||
|
|
||||||
|
export function addSessionDebugLog(log: Log) {
|
||||||
|
try {
|
||||||
|
if (!Statsig.initializeCalled() || !Statsig.getStableID()) {
|
||||||
|
// Drop these logs for now.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!Statsig.checkGate('debug_session')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const messageIndex = nextMessageIndex++
|
||||||
|
const {type, ...content} = log
|
||||||
|
let payload = JSON.stringify(content, replacer)
|
||||||
|
|
||||||
|
let nextSliceIndex = 0
|
||||||
|
while (payload.length > 0) {
|
||||||
|
const sliceIndex = nextSliceIndex++
|
||||||
|
const slice = payload.slice(0, MAX_SLICE_LENGTH)
|
||||||
|
payload = payload.slice(MAX_SLICE_LENGTH)
|
||||||
|
Statsig.logEvent('session:debug', null, {
|
||||||
|
realmId,
|
||||||
|
messageIndex: String(messageIndex),
|
||||||
|
messageType: type,
|
||||||
|
sliceIndex: String(sliceIndex),
|
||||||
|
slice,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let agentIds = new WeakMap<object, string>()
|
||||||
|
let realmId = Math.random().toString(36).slice(2)
|
||||||
|
let nextAgentId = 1
|
||||||
|
|
||||||
|
function getAgentId(agent: object) {
|
||||||
|
let id = agentIds.get(agent)
|
||||||
|
if (id === undefined) {
|
||||||
|
id = realmId + '::' + nextAgentId++
|
||||||
|
agentIds.set(agent, id)
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
function replacer(key: string, value: unknown) {
|
||||||
|
if (typeof value === 'object' && value != null && 'api' in value) {
|
||||||
|
return getAgentId(value)
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
key === 'service' ||
|
||||||
|
key === 'email' ||
|
||||||
|
key === 'emailConfirmed' ||
|
||||||
|
key === 'emailAuthFactor' ||
|
||||||
|
key === 'pdsUrl'
|
||||||
|
) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
typeof value === 'string' &&
|
||||||
|
(key === 'refreshJwt' || key === 'accessJwt')
|
||||||
|
) {
|
||||||
|
return sha256(value)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import {AtpSessionEvent} from '@atproto/api'
|
import {AtpSessionEvent} from '@atproto/api'
|
||||||
|
|
||||||
import {createPublicAgent} from './agent'
|
import {createPublicAgent} from './agent'
|
||||||
|
import {wrapSessionReducerForLogging} from './logging'
|
||||||
import {SessionAccount} from './types'
|
import {SessionAccount} from './types'
|
||||||
|
|
||||||
// A hack so that the reducer can't read anything from the agent.
|
// A hack so that the reducer can't read anything from the agent.
|
||||||
|
@ -64,7 +65,7 @@ export function getInitialState(persistedAccounts: SessionAccount[]): State {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function reducer(state: State, action: Action): State {
|
let reducer = (state: State, action: Action): State => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'received-agent-event': {
|
case 'received-agent-event': {
|
||||||
const {agent, accountDid, refreshedAccount, sessionEvent} = action
|
const {agent, accountDid, refreshedAccount, sessionEvent} = action
|
||||||
|
@ -166,3 +167,5 @@ export function reducer(state: State, action: Action): State {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
reducer = wrapSessionReducerForLogging(reducer)
|
||||||
|
export {reducer}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue