Skip to content

Commit

Permalink
feat(redux-store): move from mobile agent repo (#388)
Browse files Browse the repository at this point in the history
  • Loading branch information
TimoGlastra committed Jul 22, 2021
1 parent cdf2edd commit d84acc7
Show file tree
Hide file tree
Showing 26 changed files with 1,197 additions and 2 deletions.
13 changes: 13 additions & 0 deletions packages/redux-store/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { Config } from '@jest/types'

import base from '../../jest.config.base'

import packageJson from './package.json'

const config: Config.InitialOptions = {
...base,
name: packageJson.name,
displayName: packageJson.name,
}

export default config
34 changes: 34 additions & 0 deletions packages/redux-store/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "@aries-framework/redux-store",
"main": "build/index",
"types": "build/index",
"version": "0.0.0",
"files": [
"build"
],
"publishConfig": {
"access": "public"
},
"homepage": "https://github.com/hyperledger/aries-framework-javascript/tree/main/packages/redux-store",
"repository": {
"type": "git",
"url": "https://github.com/hyperledger/aries-framework-javascript",
"directory": "packages/redux-store"
},
"scripts": {
"build": "yarn run clean && yarn run compile",
"clean": "rimraf -rf ./build",
"compile": "tsc -p tsconfig.build.json",
"prepublishOnly": "yarn run build",
"test": "jest"
},
"dependencies": {
"@aries-framework/core": "*",
"@reduxjs/toolkit": "^1.6.0",
"react-redux": "^7.2.4"
},
"devDependencies": {
"rimraf": "~3.0.2",
"typescript": "~4.3.0"
}
}
23 changes: 23 additions & 0 deletions packages/redux-store/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export { initializeStore } from './store'

export { createAsyncAgentThunk, AgentThunkApiConfig } from './utils'

export {
agentSlice,
AgentThunks,
// Connections
connectionsSlice,
ConnectionThunks,
startConnectionsListener,
ConnectionsSelectors,
// Credentials
credentialsSlice,
CredentialsThunks,
startCredentialsListener,
CredentialsSelectors,
// Proofs
proofsSlice,
ProofsThunks,
startProofsListener,
ProofsSelectors,
} from './slices'
45 changes: 45 additions & 0 deletions packages/redux-store/src/slices/agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { AgentThunkApiConfig } from '../utils'
import type { SerializedError } from '@reduxjs/toolkit'

import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'

export interface AgentState {
isInitializing: boolean
isInitialized: boolean
error: null | SerializedError
}

const initialState: AgentState = {
isInitializing: false,
isInitialized: false,
error: null,
}

const AgentThunks = {
initializeAgent: createAsyncThunk<boolean, void, AgentThunkApiConfig>('agent/initialize', async (_, thunkApi) => {
await thunkApi.extra.agent.initialize()
return true
}),
}
const agentSlice = createSlice({
name: 'agent',
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(AgentThunks.initializeAgent.pending, (state) => {
state.isInitializing = true
})
.addCase(AgentThunks.initializeAgent.rejected, (state, action) => {
state.isInitializing = false
state.isInitialized = false
state.error = action.error
})
.addCase(AgentThunks.initializeAgent.fulfilled, (state) => {
state.isInitializing = false
state.isInitialized = true
})
},
})

export { agentSlice, AgentThunks }
28 changes: 28 additions & 0 deletions packages/redux-store/src/slices/connections/connectionsListener.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { Agent, ConnectionStateChangedEvent } from '@aries-framework/core'
import type { EnhancedStore } from '@reduxjs/toolkit'

import { ConnectionEventTypes } from '@aries-framework/core'

import { connectionsSlice } from './connectionsSlice'

/**
* Starts an EventListener that listens for ConnectionRecord state changes
* and updates the store accordingly.
*
* This function **must** be called if you're working with ConnectionRecords.
* If you don't, the store won't be updated.
*/
const startConnectionsListener = (agent: Agent, store: EnhancedStore) => {
const listener = (event: ConnectionStateChangedEvent) => {
const record = event.payload.connectionRecord
store.dispatch(connectionsSlice.actions.updateOrAdd(record))
}

agent.events.on(ConnectionEventTypes.ConnectionStateChanged, listener)

return () => {
agent.events.off(ConnectionEventTypes.ConnectionStateChanged, listener)
}
}

export { startConnectionsListener }
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import type { ConnectionsState } from './connectionsSlice'
import type { ConnectionState } from '@aries-framework/core'

interface PartialConnectionState {
connections: ConnectionsState
}

/**
* Namespace that holds all ConnectionRecord related selectors.
*/
const ConnectionsSelectors = {
/**
* Selector that retrieves the entire **connections** store object.
*/
connectionsStateSelector: (state: PartialConnectionState) => state.connections.connections,

/**
* Selector that retrieves all ConnectionRecords from the store.
*/
connectionRecordsSelector: (state: PartialConnectionState) => state.connections.connections.records,

/**
* Selector that retrieves all ConnectionRecords from the store with specified {@link ConnectionState}.
*/
connectionRecordsByStateSelector: (connectionState: ConnectionState) => (state: PartialConnectionState) =>
state.connections.connections.records.filter((record) => record.state === connectionState),

/**
* Selector that retrieves the entire **invitation** store object.
*/
invitationStateSelector: (state: PartialConnectionState) => state.connections.invitation,

/**
* Selector that fetches a ConnectionRecord by id from the state.
*/
connectionRecordByIdSelector: (connectionRecordId: string) => (state: PartialConnectionState) =>
state.connections.connections.records.find((x) => x.id === connectionRecordId),

/**
* Selector that fetches a ConnectionRecord by its verification key from the state.
*/
connectionRecordByVerkeySelector: (verkey: string) => (state: PartialConnectionState) =>
state.connections.connections.records.find((x) => x.verkey === verkey),

/**
* Selector that fetches a ConnectionRecord by their key from the state.
*/
connectionRecordByTheirKeySelector: (theirKey: string) => (state: PartialConnectionState) =>
state.connections.connections.records.find((x) => x.theirKey === theirKey),

/**
* Selector that fetches the InvitationMessage based on a ConnectionRecord id.
*/
invitationByConnectionRecordIdSelector: (connectionRecordId: string) => (state: PartialConnectionState) => {
const record = state.connections.connections.records.find((x) => x.id == connectionRecordId)

if (!record) {
return null
}
return record.invitation
},
}

export { ConnectionsSelectors }
124 changes: 124 additions & 0 deletions packages/redux-store/src/slices/connections/connectionsSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import type { ConnectionRecord, ConnectionInvitationMessage } from '@aries-framework/core'
import type { PayloadAction, SerializedError } from '@reduxjs/toolkit'

import { createSlice } from '@reduxjs/toolkit'

import { ConnectionThunks } from './connectionsThunks'

interface ConnectionsState {
connections: {
records: ConnectionRecord[]
isLoading: boolean
error: null | SerializedError
}
invitation: {
message: null | ConnectionInvitationMessage
connectionRecordId: null | string
isLoading: boolean
error: null | SerializedError
}
}

const initialState: ConnectionsState = {
connections: {
records: [],
isLoading: false,
error: null,
},
invitation: {
message: null,
connectionRecordId: null,
isLoading: false,
error: null,
},
}

const connectionsSlice = createSlice({
name: 'connections',
initialState,
reducers: {
updateOrAdd: (state, action: PayloadAction<ConnectionRecord>) => {
const index = state.connections.records.findIndex((record) => record.id == action.payload.id)

if (index == -1) {
// records doesn't exist, add it
state.connections.records.push(action.payload)
return state
}

// record does exist, update it
state.connections.records[index] = action.payload
return state
},
},
extraReducers: (builder) => {
builder
// fetchAllConnections
.addCase(ConnectionThunks.getAllConnections.pending, (state) => {
state.connections.isLoading = true
})
.addCase(ConnectionThunks.getAllConnections.rejected, (state, action) => {
state.connections.isLoading = false
state.connections.error = action.error
})
.addCase(ConnectionThunks.getAllConnections.fulfilled, (state, action) => {
state.connections.isLoading = false
state.connections.records = action.payload
})
// createConnection
.addCase(ConnectionThunks.createConnection.pending, (state) => {
state.invitation.isLoading = true
})
.addCase(ConnectionThunks.createConnection.rejected, (state, action) => {
state.invitation.isLoading = false
state.connections.error = action.error
})
.addCase(ConnectionThunks.createConnection.fulfilled, (state, action) => {
state.invitation.isLoading = false
state.invitation.message = action.payload.invitation
state.invitation.connectionRecordId = action.payload.connectionRecord.id
})
// receiveInvitation
.addCase(ConnectionThunks.receiveInvitation.pending, (state) => {
state.invitation.isLoading = true
})
.addCase(ConnectionThunks.receiveInvitation.rejected, (state, action) => {
state.invitation.isLoading = false
state.invitation.error = action.error
})
.addCase(ConnectionThunks.receiveInvitation.fulfilled, (state) => {
state.invitation.isLoading = false
})
// receiveInvitationFromUrl
.addCase(ConnectionThunks.receiveInvitationFromUrl.pending, (state) => {
state.invitation.isLoading = true
})
.addCase(ConnectionThunks.receiveInvitationFromUrl.rejected, (state, action) => {
state.invitation.isLoading = false
state.invitation.error = action.error
})
.addCase(ConnectionThunks.receiveInvitationFromUrl.fulfilled, (state) => {
state.invitation.isLoading = false
})
// acceptInvitation
.addCase(ConnectionThunks.acceptInvitation.pending, (state) => {
state.invitation.isLoading = true
})
.addCase(ConnectionThunks.acceptInvitation.rejected, (state, action) => {
state.invitation.isLoading = false
state.invitation.error = action.error
})
.addCase(ConnectionThunks.acceptInvitation.fulfilled, (state) => {
state.invitation.isLoading = false
})
.addCase(ConnectionThunks.deleteConnection.fulfilled, (state, action) => {
const connectionId = action.payload.id
const index = state.connections.records.findIndex((connectionRecord) => connectionRecord.id === connectionId)
state.connections.records.splice(index, 1)
})
},
})

export { connectionsSlice }

export type { ConnectionsState }
Loading

0 comments on commit d84acc7

Please sign in to comment.