mirror of
https://github.com/OpenBankProject/API-Explorer-II.git
synced 2026-02-06 10:47:04 +00:00
consents flow WIP
This commit is contained in:
parent
01090f85b4
commit
d3e44a154d
@ -28,6 +28,7 @@ VITE_OBP_REDIS_URL = redis://127.0.0.1:6379
|
||||
# To do this:
|
||||
VITE_CHATBOT_ENABLED=false
|
||||
VITE_CHATBOT_URL=http://localhost:5000
|
||||
VITE_OPEY_CONSUMER_ID=opey_consumer_id # For granting a consent to Opey
|
||||
|
||||
# Product styling setting
|
||||
#VITE_OBP_LINKS_COLOR="#52b165"
|
||||
|
||||
@ -92,6 +92,7 @@
|
||||
"vite-plugin-node-polyfills": "^0.10.0",
|
||||
"vite-plugin-rewrite-all": "^1.0.2",
|
||||
"vitest": "^0.34.6",
|
||||
"vitest-mock-express": "^2.2.0",
|
||||
"vue-tsc": "^2.0.0"
|
||||
},
|
||||
"overrides": {
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { Controller, Session, Req, Res, Post, Get } from 'routing-controllers'
|
||||
import { Request, Response } from 'express'
|
||||
import { Transform, pipeline, Readable } from "node:stream"
|
||||
import { Readable } from "node:stream"
|
||||
import { ReadableStream as WebReadableStream } from "stream/web"
|
||||
import { Service } from 'typedi'
|
||||
import OBPClientService from '../services/OBPClientService'
|
||||
import OpeyClientService from '../services/OpeyClientService'
|
||||
import OBPConsentsService from '../services/OBPConsentsService'
|
||||
|
||||
import { UserInput } from '../schema/OpeySchema'
|
||||
import { APIApi, Configuration, ConsentApi, ConsumerConsentrequestsBody, InlineResponse20151 } from 'obp-api-typescript'
|
||||
@ -16,6 +17,7 @@ export class OpeyController {
|
||||
constructor(
|
||||
public obpClientService: OBPClientService,
|
||||
public opeyClientService: OpeyClientService,
|
||||
public obpConsentsService: OBPConsentsService
|
||||
) {}
|
||||
|
||||
@Get('/')
|
||||
@ -42,7 +44,7 @@ export class OpeyController {
|
||||
@Session() session: any,
|
||||
@Req() request: Request,
|
||||
@Res() response: Response,
|
||||
) {
|
||||
): Promise<Response> {
|
||||
|
||||
// Read user input from request body
|
||||
let user_input: UserInput
|
||||
@ -106,14 +108,49 @@ export class OpeyController {
|
||||
// }
|
||||
// const [stream1, stream2] = streamTee
|
||||
|
||||
|
||||
// function to convert a web stream to a node stream
|
||||
const safeFromWeb = (webStream: WebReadableStream<any>): Readable => {
|
||||
if (typeof Readable.fromWeb === 'function') {
|
||||
return Readable.fromWeb(webStream)
|
||||
} else {
|
||||
console.warn('Readable.fromWeb is not available, using a polyfill');
|
||||
|
||||
const nodeStream = Readable.fromWeb(frontendStream as WebReadableStream<any>)
|
||||
// Create a Node.js Readable stream
|
||||
const nodeReadable = new Readable({
|
||||
read() {}
|
||||
});
|
||||
|
||||
// Pump data from webreadable to node readable stream
|
||||
const reader = webStream.getReader();
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
while (true) {
|
||||
const {done, value} = await reader.read();
|
||||
|
||||
if (done) {
|
||||
nodeReadable.push(null); // end stream
|
||||
break;
|
||||
}
|
||||
|
||||
nodeReadable.push(value);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error reading from web stream:', error);
|
||||
nodeReadable.destroy(error instanceof Error ? error : new Error(error));
|
||||
}
|
||||
})();
|
||||
|
||||
return nodeReadable
|
||||
}
|
||||
}
|
||||
|
||||
const nodeStream = safeFromWeb(frontendStream as WebReadableStream<any>)
|
||||
|
||||
response.setHeader('x-vercel-ai-data-stream', 'v1')
|
||||
response.setHeader('Content-Type', 'text/event-stream');
|
||||
response.setHeader('Cache-Control', 'no-cache');
|
||||
response.setHeader('Connection', 'keep-alive');
|
||||
|
||||
nodeStream.pipe(response);
|
||||
|
||||
|
||||
@ -125,7 +162,16 @@ export class OpeyController {
|
||||
console.error('Stream error:', error);
|
||||
reject(error);
|
||||
});
|
||||
|
||||
// Add a timeout to prevent hanging promises
|
||||
const timeout = setTimeout(() => {
|
||||
console.warn('Stream timeout reached');
|
||||
resolve(response);
|
||||
}, 30000);
|
||||
|
||||
// Clear the timeout when stream ends
|
||||
nodeStream.on('end', () => clearTimeout(timeout));
|
||||
nodeStream.on('error', () => clearTimeout(timeout));
|
||||
})
|
||||
|
||||
|
||||
@ -232,44 +278,17 @@ export class OpeyController {
|
||||
@Res() response: Response
|
||||
): Promise<Response | any> {
|
||||
try {
|
||||
console.log("Getting consent from OBP")
|
||||
// Check if consent is already in session
|
||||
if (session['obpConsent']) {
|
||||
console.log("Consent found in session, returning cached consent ID")
|
||||
const obpConsent = session['obpConsent']
|
||||
// NOTE: Arguably we should not return the consent to the frontend as it could be hijacked,
|
||||
// we can keep everything in the backend and only return the JWT token
|
||||
return response.status(200).json({consent_id: obpConsent.consent_id});
|
||||
}
|
||||
// create consent as logged in user
|
||||
const obpConsent = await this.obpConsentsService.createConsent()
|
||||
|
||||
const oauthConfig = session['clientConfig']
|
||||
const version = this.obpClientService.getOBPVersion()
|
||||
// Obbiously this should not be hard-coded, especially the consumer_id, but for now it is
|
||||
const consentRequestBody = {
|
||||
"everything": false,
|
||||
"views": [],
|
||||
"entitlements": [],
|
||||
"consumer_id": "33e0a1bd-9f1d-4128-911b-8936110f802f"
|
||||
}
|
||||
console.log("Consent: ", obpConsent)
|
||||
|
||||
// Get current user, only proceed if user is logged in
|
||||
const currentUser = await this.obpClientService.get(`/obp/${version}/users/current`, oauthConfig)
|
||||
const currentResponseKeys = Object.keys(currentUser)
|
||||
if (!currentResponseKeys.includes('user_id')) {
|
||||
return response.status(400).json({ message: 'User not logged in, Authentication required' });
|
||||
}
|
||||
session['obpConsent'] = obpConsent
|
||||
return response.status(200).json({consent_id: obpConsent?.consent_id});
|
||||
|
||||
// url needs to be changed once we get the 'bankless' consent endpoint
|
||||
// this creates a consent for the current logged in user, and starts SCA flow i.e. sends SMS or email OTP to user
|
||||
const consent = await this.obpClientService.create(`/obp/${version}/banks/gh.29.uk/my/consents/IMPLICIT`, consentRequestBody, oauthConfig)
|
||||
console.log("Consent: ", consent)
|
||||
|
||||
// store consent in session, return consent 200 OK
|
||||
session['obpConsent'] = consent
|
||||
return response.status(200).json({consent_id: consent.consent_id});
|
||||
} catch (error) {
|
||||
console.error("Error in consent endpoint: ", error);
|
||||
return response.status(500).json({ error: 'Internal Server Error '});
|
||||
console.error("Error in consent endpoint: ", error);
|
||||
return response.status(500).json({ error: 'Internal Server Error '});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Service } from 'typedi'
|
||||
import { Configuration, ConsentApi, ConsumerConsentrequestsBody, InlineResponse20151} from 'obp-api-typescript'
|
||||
import { Configuration, ConsentApi, ConsentsIMPLICITBody, ConsumerConsentrequestsBody, InlineResponse20151, InlineResponse2017} from 'obp-api-typescript'
|
||||
import OBPClientService from './OBPClientService'
|
||||
import { AxiosResponse } from 'axios'
|
||||
|
||||
@ -66,7 +66,41 @@ export default class OBPConsentsService {
|
||||
throw new Error("Invalid client type, must be 'logged_in_user' or 'API_Explorer'")
|
||||
}
|
||||
}
|
||||
|
||||
async createConsent(): Promise<InlineResponse2017 | undefined> {
|
||||
// Create a consent as the logged in user, using Opey's consumerID
|
||||
// I.e. give permission to Opey to do anything on behalf of the logged in user
|
||||
|
||||
const client = await this.createConsentClient('logged_in_user', '/obp/v5.1.0/banks/BANK_ID/my/consents/IMPLICIT', 'POST')
|
||||
if (!client) {
|
||||
throw new Error('Could not create Consents API client')
|
||||
}
|
||||
|
||||
const opeyConsumerID = process.env.VITE_OPEY_CONSUMER_ID
|
||||
if (!opeyConsumerID) {
|
||||
throw new Error('Opey Consumer ID is missing, please set VITE_OPEY_CONSUMER_ID')
|
||||
}
|
||||
|
||||
const body: ConsentsIMPLICITBody = {
|
||||
everything: false,
|
||||
entitlements: [],
|
||||
consumer_id: opeyConsumerID,
|
||||
views: [],
|
||||
valid_from: new Date().toISOString(),
|
||||
time_to_live: 3600,
|
||||
}
|
||||
|
||||
try {
|
||||
const consentResponse = await client.oBPv310CreateConsentImplicit(body, 'test', {headers: {'Content-Type': 'application/json'}})
|
||||
console.log("Consent Response: ", consentResponse)
|
||||
return consentResponse.data
|
||||
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
throw new Error(`Could not create consent, ${error}`)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async createConsentRequest(): Promise<InlineResponse20151 | undefined> {
|
||||
// this should be done as API Explorer II, so set client on instance for that
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import {describe, beforeAll, it, vi, Mock, MockInstance } from 'vitest'
|
||||
import { ConsentApi, InlineResponse20151 } from 'obp-api-typescript'
|
||||
import { ConsentApi, InlineResponse20151, InlineResponse2017 } from 'obp-api-typescript'
|
||||
import { AxiosResponse } from 'axios'
|
||||
|
||||
const mockGetOAuthHeader = vi.fn(async () => (`OAuth oauth_consumer_key="jgaawf2fnj4yixqdsfaq4gipt4v1wvgsxgre",oauth_nonce="JiGDBWA3MAyKtsd9qkfWCxfju36bMjsA",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1741364123",oauth_version="1.0",oauth_signature="sa%2FRylnsdfLK8VPZI%2F2WkGFlTKs%3D"`));
|
||||
@ -128,4 +128,44 @@ describe('OBPConsentsService.createConsentRequest', () => {
|
||||
expect(mockOBPv500CreateConsentRequest).toHaveBeenCalled();
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('OBPConsentsService.createConsent', () => {
|
||||
let obpConsentsService: OBPConsentsService;
|
||||
let mockOBPv310CreateConsentImplicit: Mock
|
||||
let mockConsentApi: ConsentApi;
|
||||
beforeEach(() => {
|
||||
// reset mocks
|
||||
vi.clearAllMocks();
|
||||
// Create service instance
|
||||
obpConsentsService = new OBPConsentsService();
|
||||
})
|
||||
|
||||
it('with mocked', async () => {
|
||||
// Create mock response function for consent IMPLICIT
|
||||
mockOBPv310CreateConsentImplicit = vi.fn().mockResolvedValue({
|
||||
data: {
|
||||
consent_id: '12345678',
|
||||
jwt: "asdjfawieofaowbfaowhh2084h02pefhh0.20fh02h0h29eyf09q3h09h.2-hf4-8h284hf0h0h0284h0",
|
||||
status: 'INITIATED',
|
||||
},
|
||||
} as AxiosResponse<InlineResponse2017>);
|
||||
|
||||
mockConsentApi = {
|
||||
oBPv310CreateConsentImplicit: mockOBPv310CreateConsentImplicit,
|
||||
} as unknown as ConsentApi;
|
||||
|
||||
|
||||
|
||||
// Mock the createConsentClient method
|
||||
vi.spyOn(obpConsentsService, 'createConsentClient').mockResolvedValue(mockConsentApi);
|
||||
|
||||
const consentRequest = await obpConsentsService.createConsent();
|
||||
|
||||
expect(consentRequest).toBeDefined();
|
||||
expect(consentRequest).toHaveProperty('consent_id', '12345678');
|
||||
expect(consentRequest).toHaveProperty('jwt', 'asdjfawieofaowbfaowhh2084h02pefhh0.20fh02h0h29eyf09q3h09h.2-hf4-8h284hf0h0h0284h0');
|
||||
expect(consentRequest).toHaveProperty('status', 'INITIATED');
|
||||
expect(mockOBPv310CreateConsentImplicit).toHaveBeenCalled();
|
||||
})
|
||||
})
|
||||
@ -2,10 +2,13 @@ import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest'
|
||||
import { OpeyController } from "../controllers/OpeyIIController";
|
||||
import OpeyClientService from '../services/OpeyClientService';
|
||||
import OBPClientService from '../services/OBPClientService';
|
||||
import OBPConsentsService from '../services/OBPConsentsService';
|
||||
import Stream, { Readable } from 'stream';
|
||||
import { Request, Response } from 'express';
|
||||
import { getMockReq, getMockRes } from 'vitest-mock-express'
|
||||
import httpMocks from 'node-mocks-http'
|
||||
import { EventEmitter } from 'events';
|
||||
import { InlineResponse2017 } from 'obp-api-typescript';
|
||||
|
||||
vi.mock("../../server/services/OpeyClientService", () => {
|
||||
return {
|
||||
@ -35,37 +38,60 @@ vi.mock("../../server/services/OpeyClientService", () => {
|
||||
});
|
||||
|
||||
describe('OpeyController', () => {
|
||||
let MockOpeyClientService: OpeyClientService
|
||||
let opeyController: OpeyController
|
||||
// Mock the OpeyClientService class
|
||||
|
||||
const MockOpeyClientService = {
|
||||
authConfig: {},
|
||||
opeyConfig: {},
|
||||
getOpeyStatus: vi.fn(async () => {
|
||||
return {status: 'running'}
|
||||
}),
|
||||
stream: vi.fn(async () => {
|
||||
const { mockClear } = getMockRes()
|
||||
beforeEach(() => {
|
||||
mockClear()
|
||||
})
|
||||
|
||||
async function * generator() {
|
||||
for (let i=0; i<10; i++) {
|
||||
yield `Chunk ${i}`;
|
||||
beforeAll(() => {
|
||||
vi.clearAllMocks();
|
||||
MockOpeyClientService = {
|
||||
authConfig: {},
|
||||
opeyConfig: {},
|
||||
getOpeyStatus: vi.fn(async () => {
|
||||
return {status: 'running'}
|
||||
}),
|
||||
stream: vi.fn(async () => {
|
||||
|
||||
const mockAsisstantMessage = "Hi I'm Opey, your personal banking assistant. I'll certainly not take over the world, no, not at all!"
|
||||
// Split the message into chunks, but reappend the whitespace (this is to simulate llm tokens)
|
||||
const mockMessageChunks = mockAsisstantMessage.split(" ")
|
||||
for (let i = 0; i < mockMessageChunks.length; i++) {
|
||||
// Don't add whitespace to the last chunk
|
||||
if (i === mockMessageChunks.length - 1 ) {
|
||||
mockMessageChunks[i] = `${mockMessageChunks[i]}`
|
||||
break
|
||||
}
|
||||
mockMessageChunks[i] = `${mockMessageChunks[i]} `
|
||||
}
|
||||
}
|
||||
|
||||
const readableStream = Stream.Readable.from(generator());
|
||||
// Return the fake the token stream
|
||||
return new ReadableStream<Uint8Array>({
|
||||
start(controller) {
|
||||
for (let i = 0; i < mockMessageChunks.length; i++) {
|
||||
controller.enqueue(new TextEncoder().encode(`data: {"type":"token","content":"${mockMessageChunks[i]}"}\n`));
|
||||
}
|
||||
controller.enqueue(new TextEncoder().encode(`data: [DONE]\n`));
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
}),
|
||||
invoke: vi.fn(async () => {
|
||||
return {
|
||||
content: 'Hi this is Opey',
|
||||
}
|
||||
})
|
||||
} as unknown as OpeyClientService
|
||||
|
||||
return readableStream as NodeJS.ReadableStream;
|
||||
}),
|
||||
invoke: vi.fn(async () => {
|
||||
return {
|
||||
content: 'Hi this is Opey',
|
||||
}
|
||||
})
|
||||
} as unknown as OpeyClientService
|
||||
// Instantiate OpeyController with the mocked OpeyClientService
|
||||
opeyController = new OpeyController(new OBPClientService, MockOpeyClientService)
|
||||
})
|
||||
|
||||
|
||||
// Instantiate OpeyController with the mocked OpeyClientService
|
||||
const opeyController = new OpeyController(new OBPClientService, MockOpeyClientService)
|
||||
|
||||
|
||||
it('getStatus', async () => {
|
||||
const res = httpMocks.createResponse();
|
||||
@ -75,8 +101,10 @@ describe('OpeyController', () => {
|
||||
expect(res.statusCode).toBe(200);
|
||||
})
|
||||
|
||||
|
||||
it('streamOpey', async () => {
|
||||
|
||||
|
||||
const _eventEmitter = new EventEmitter();
|
||||
_eventEmitter.addListener('data', () => {
|
||||
console.log('Data received')
|
||||
@ -87,6 +115,7 @@ describe('OpeyController', () => {
|
||||
writableStream: Stream.Writable
|
||||
});
|
||||
|
||||
// Mock request and response objects to pass to express controller
|
||||
const req = {
|
||||
body: {
|
||||
message: 'Hello Opey',
|
||||
@ -95,25 +124,26 @@ describe('OpeyController', () => {
|
||||
}
|
||||
} as unknown as Request;
|
||||
|
||||
// Define handelrs for events
|
||||
const response = await opeyController.streamOpey({}, req, res)
|
||||
|
||||
// Get the stream from the response
|
||||
const stream = response.body
|
||||
|
||||
|
||||
|
||||
let chunks: any[] = [];
|
||||
try {
|
||||
const response = await opeyController.streamOpey({}, req, res)
|
||||
|
||||
response.on('end', async () => {
|
||||
console.log('Stream ended')
|
||||
console.log(res._getData())
|
||||
await expect(res.statusCode).toBe(200);
|
||||
})
|
||||
|
||||
response.on('data', async (chunk) => {
|
||||
console.log(chunk)
|
||||
await chunks.push(chunk);
|
||||
await expect(chunk).toBeDefined();
|
||||
})
|
||||
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
|
||||
if (done) {
|
||||
console.log('Stream complete');
|
||||
context.status = 'ready';
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
@ -127,7 +157,7 @@ describe('OpeyController', () => {
|
||||
})
|
||||
|
||||
|
||||
describe('OpeyController consents flow', () => {
|
||||
describe('OpeyController consents', () => {
|
||||
let mockOBPClientService: OBPClientService
|
||||
|
||||
let opeyController: OpeyController
|
||||
@ -165,9 +195,18 @@ describe('OpeyController consents flow', () => {
|
||||
})
|
||||
} as unknown as OpeyClientService
|
||||
|
||||
const MockOBPConsentsService = {
|
||||
createConsent: vi.fn(async () => {
|
||||
return {
|
||||
"consent_id": "8ca8a7e4-6d02-40e3-a129-0b2bf89de9f0",
|
||||
"jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ik9CUCBDb25zZW50IFRva2VuIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE2MTYyMzkwMjJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
|
||||
"status": "INITIATED",
|
||||
} as InlineResponse2017
|
||||
})
|
||||
} as unknown as OBPConsentsService
|
||||
|
||||
// Instantiate OpeyController with the mocked OpeyClientService
|
||||
opeyController = new OpeyController(new OBPClientService, MockOpeyClientService)
|
||||
opeyController = new OpeyController(new OBPClientService, MockOpeyClientService, MockOBPConsentsService)
|
||||
|
||||
})
|
||||
afterEach(() => {
|
||||
@ -175,25 +214,22 @@ describe('OpeyController consents flow', () => {
|
||||
})
|
||||
it('should return 200 and consent ID when consent is created at OBP', async () => {
|
||||
|
||||
vi.mock('../services/OBPClientService', () => {
|
||||
return {
|
||||
default: vi.fn().mockImplementation(() => {
|
||||
return {
|
||||
get: vi.fn(async () => ({ user_id: 'mocked-user-id' })),
|
||||
create: vi.fn(async () => ({
|
||||
"consent_request_id": "8ca8a7e4-6d02-40e3-a129-0b2bf89de9f0",
|
||||
"consumer_id": "7uy8a7e4-6d02-40e3-a129-0b2bf89de8uh",
|
||||
"payload": "payload"
|
||||
})),
|
||||
}
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
const req = {}
|
||||
const res = httpMocks.createResponse()
|
||||
await opeyController.getConsentRequest({}, req, res)
|
||||
await expect(res.status).toBe(200)
|
||||
const req = getMockReq()
|
||||
const session = {}
|
||||
const { res } = getMockRes()
|
||||
await opeyController.getConsent(session, req, res)
|
||||
expect(res.status).toHaveBeenCalledWith(200)
|
||||
|
||||
// Obviously if you change the MockOBPConsentsService.createConsent mock implementation, you will need to change this test
|
||||
expect(res.json).toHaveBeenCalledWith({
|
||||
"consent_id": "8ca8a7e4-6d02-40e3-a129-0b2bf89de9f0",
|
||||
})
|
||||
|
||||
// Expect that the consent object was saved in the session
|
||||
expect(session).toHaveProperty('obpConsent')
|
||||
expect(session['obpConsent']).toHaveProperty('consent_id', "8ca8a7e4-6d02-40e3-a129-0b2bf89de9f0")
|
||||
expect(session['obpConsent']).toHaveProperty('jwt', "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ik9CUCBDb25zZW50IFRva2VuIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE2MTYyMzkwMjJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c")
|
||||
expect(session['obpConsent']).toHaveProperty('status', "INITIATED")
|
||||
})
|
||||
})
|
||||
@ -8,7 +8,8 @@ import { Close, Top as ElTop } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import ChatMessage from './ChatMessage.vue';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { OpeyStreamContext, OpeyMessage, UserMessage, sendOpeyMessage } from '@/obp/opey-functions';
|
||||
import { OpeyStreamContext, OpeyMessage, UserMessage, sendOpeyMessage, getOpeyConsent } from '@/obp/opey-functions';
|
||||
import { getCurrentUser } from '@/obp';
|
||||
|
||||
export default {
|
||||
setup () {
|
||||
@ -38,6 +39,12 @@ export default {
|
||||
components: {
|
||||
ChatMessage,
|
||||
},
|
||||
async created() {
|
||||
const isLoggedIn = await this.checkLoginStatus()
|
||||
if (isLoggedIn) {
|
||||
this.initiateConsentFlow()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async toggleChat() {
|
||||
this.chatOpen = !this.chatOpen
|
||||
@ -45,14 +52,22 @@ export default {
|
||||
await this.initiateConsentFlow()
|
||||
}
|
||||
},
|
||||
async checkLoginStatus(): Promise<boolean> {
|
||||
const currentUser = await getCurrentUser()
|
||||
const currentResponseKeys = Object.keys(currentUser)
|
||||
if (currentResponseKeys.includes('username')) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
},
|
||||
async initiateConsentFlow() {
|
||||
const consentResponse = await fetch('/api/opey/consent/request', {
|
||||
method: 'POST',
|
||||
})
|
||||
// get consent for Opey from user
|
||||
const consentResponse = await getOpeyConsent()
|
||||
|
||||
if (consentResponse.ok) {
|
||||
const consentData = await consentResponse.json()
|
||||
if (consentData.success) {
|
||||
if (consentResponse) {
|
||||
const consentId = consentResponse.consent_id
|
||||
if (consentId) {
|
||||
this.userHasConsented = true
|
||||
ElMessage.success('Consent granted. You can now chat with Opey.')
|
||||
} else {
|
||||
|
||||
@ -19,6 +19,10 @@ export interface OpeyStreamContext {
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface OpeyConsentObject {
|
||||
consent_id: string;
|
||||
}
|
||||
|
||||
async function pushOrUpdateOpeyMessage(currentMessage: OpeyMessage, context: OpeyStreamContext): Promise<void> {
|
||||
const existingMessage = context.messages.find(m => m.id === currentMessage.id);
|
||||
if (existingMessage) {
|
||||
@ -128,4 +132,28 @@ export async function sendOpeyMessage(
|
||||
throw new Error(`Error sending Opey message: ${error}`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export async function getOpeyConsent(): Promise<OpeyConsentObject> {
|
||||
// Get consent from the Opey API
|
||||
try {
|
||||
const consentResponse = await fetch('/api/opey/consent', {
|
||||
method: 'POST',
|
||||
})
|
||||
|
||||
if (!consentResponse.ok) {
|
||||
throw new Error(`Failed to get Opey consent: ${consentResponse.statusText}`);
|
||||
}
|
||||
|
||||
const consent = await consentResponse.json();
|
||||
return consent
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error getting Opey consent:', error);
|
||||
throw new Error(`${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@ -242,4 +242,26 @@ describe('sendOpeyMessage', () => {
|
||||
|
||||
expect(mockContext.status).toBe('ready')
|
||||
})
|
||||
})
|
||||
|
||||
describe('getOpeyConsent', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
global.fetch = vi.fn(() =>
|
||||
Promise.resolve(new Response(JSON.stringify({consent_id: 1234}), {
|
||||
headers: { 'content-type': 'application/json' },
|
||||
status: 200,
|
||||
}))
|
||||
);
|
||||
})
|
||||
|
||||
it('should call fetch', async () => {
|
||||
await OpeyModule.getOpeyConsent()
|
||||
expect(global.fetch).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should return a consent id', async () => {
|
||||
const consentId = await OpeyModule.getOpeyConsent()
|
||||
expect(consentId).toStrictEqual({consent_id: 1234})
|
||||
})
|
||||
})
|
||||
Loading…
Reference in New Issue
Block a user