Skip to content

Commit 9f4f51b

Browse files
committed
implement ts client
1 parent 8fc9950 commit 9f4f51b

File tree

1 file changed

+253
-0
lines changed

1 file changed

+253
-0
lines changed

frontend/src/grpc/client.ts

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
import * as grpcWeb from 'grpc-web'
2+
import { isTokenValid, refreshAccessToken } from '@/utils/api'
3+
import { TOKEN_REFRESH_THRESHOLD_MS } from '@/constants/time'
4+
import {
5+
TaskServiceDefinition,
6+
LabelServiceDefinition,
7+
UserServiceDefinition,
8+
TokenServiceDefinition,
9+
AuthServiceDefinition,
10+
} from './task_service'
11+
import type {
12+
TasksResponse,
13+
TaskResponse,
14+
TaskHistoryResponse,
15+
} from './task'
16+
import type {
17+
CreateTaskRequest,
18+
UpdateTaskRequest,
19+
GetTaskRequest,
20+
DeleteTaskRequest,
21+
CompleteTaskRequest,
22+
UncompleteTaskRequest,
23+
SkipTaskRequest,
24+
UpdateDueDateRequest,
25+
GetTaskHistoryRequest,
26+
GetCompletedTasksRequest,
27+
} from './task'
28+
import type { LabelsResponse, LabelResponse, CreateLabelRequest, UpdateLabelRequest, DeleteLabelRequest } from './label'
29+
import type {
30+
UserProfileResponse,
31+
AppTokensResponse,
32+
AppTokenResponse,
33+
AuthResponse,
34+
SignUpRequest,
35+
LoginRequest,
36+
RefreshTokenRequest,
37+
ResetPasswordRequest,
38+
UpdatePasswordRequest,
39+
ChangePasswordRequest,
40+
CreateAppTokenRequest,
41+
DeleteAppTokenRequest,
42+
UpdateNotificationSettingsRequest,
43+
} from './user'
44+
import type { Empty } from './common'
45+
46+
const host = import.meta.env.VITE_GRPC_HOST || import.meta.env.VITE_APP_API_URL
47+
48+
type MetadataProvider = () => Promise<grpcWeb.Metadata>
49+
50+
function createGrpcWebClient<TDef extends { fullName: string; methods: Record<string, any> }>(
51+
serviceDef: TDef,
52+
hostname: string,
53+
metadataProvider?: MetadataProvider,
54+
) {
55+
const client = new grpcWeb.GrpcWebClientBase({ format: 'text' })
56+
const methods: Record<string, any> = {}
57+
58+
for (const [methodKey, methodDef] of Object.entries(serviceDef.methods)) {
59+
const methodDescriptor = new grpcWeb.MethodDescriptor(
60+
`/${serviceDef.fullName}/${methodDef.name}`,
61+
grpcWeb.MethodType.UNARY,
62+
Object as any,
63+
Object as any,
64+
(req: any) => methodDef.requestType.encode(req).finish(),
65+
(bytes: Uint8Array) => methodDef.responseType.decode(bytes),
66+
)
67+
68+
methods[methodKey] = async (request: any) => {
69+
let metadata: grpcWeb.Metadata = {}
70+
if (metadataProvider) {
71+
metadata = await metadataProvider()
72+
}
73+
74+
return new Promise((resolve, reject) => {
75+
client.rpcCall(
76+
hostname + `/${serviceDef.fullName}/${methodDef.name}`,
77+
request,
78+
metadata,
79+
methodDescriptor,
80+
(err: grpcWeb.RpcError, response: any) => {
81+
if (err) reject(err)
82+
else resolve(response)
83+
},
84+
)
85+
})
86+
}
87+
}
88+
89+
return methods
90+
}
91+
92+
const isTokenNearExpiration = () => {
93+
const now = new Date()
94+
const expiration = localStorage.getItem('ca_expiration') || ''
95+
const expire = new Date(expiration)
96+
return now.getTime() + TOKEN_REFRESH_THRESHOLD_MS > expire.getTime()
97+
}
98+
99+
const authMetadataProvider: MetadataProvider = async () => {
100+
if (isTokenValid() && isTokenNearExpiration()) {
101+
await refreshAccessToken()
102+
}
103+
104+
const token = localStorage.getItem('ca_token')
105+
if (!token) {
106+
throw new Error('User is not authenticated')
107+
}
108+
109+
return {
110+
Authorization: `Bearer ${token}`,
111+
}
112+
}
113+
114+
const taskServiceClient = createGrpcWebClient(TaskServiceDefinition, host, authMetadataProvider)
115+
const labelServiceClient = createGrpcWebClient(LabelServiceDefinition, host, authMetadataProvider)
116+
const userServiceClient = createGrpcWebClient(UserServiceDefinition, host, authMetadataProvider)
117+
const tokenServiceClient = createGrpcWebClient(TokenServiceDefinition, host, authMetadataProvider)
118+
const authServiceClient = createGrpcWebClient(AuthServiceDefinition, host)
119+
120+
// ============== Task Service ==============
121+
122+
export async function getTasks(): Promise<TasksResponse> {
123+
const request: Empty = {}
124+
return taskServiceClient.getTasks(request)
125+
}
126+
127+
export async function getCompletedTasks(limit: number, page: number): Promise<TasksResponse> {
128+
const request: GetCompletedTasksRequest = { limit, page }
129+
return taskServiceClient.getCompletedTasks(request)
130+
}
131+
132+
export async function getTask(id: number): Promise<TaskResponse> {
133+
const request: GetTaskRequest = { id }
134+
return taskServiceClient.getTask(request)
135+
}
136+
137+
export async function createTask(task: CreateTaskRequest): Promise<TaskResponse> {
138+
return taskServiceClient.createTask(task)
139+
}
140+
141+
export async function updateTask(task: UpdateTaskRequest): Promise<TaskResponse> {
142+
return taskServiceClient.updateTask(task)
143+
}
144+
145+
export async function deleteTask(id: number): Promise<Empty> {
146+
const request: DeleteTaskRequest = { id }
147+
return taskServiceClient.deleteTask(request)
148+
}
149+
150+
export async function completeTask(id: number, endRecurrence: boolean = false): Promise<TaskResponse> {
151+
const request: CompleteTaskRequest = { id, endRecurrence }
152+
return taskServiceClient.completeTask(request)
153+
}
154+
155+
export async function uncompleteTask(id: number): Promise<TaskResponse> {
156+
const request: UncompleteTaskRequest = { id }
157+
return taskServiceClient.uncompleteTask(request)
158+
}
159+
160+
export async function skipTask(id: number): Promise<TaskResponse> {
161+
const request: SkipTaskRequest = { id }
162+
return taskServiceClient.skipTask(request)
163+
}
164+
165+
export async function updateDueDate(id: number, dueDate: number): Promise<TaskResponse> {
166+
const request: UpdateDueDateRequest = { id, dueDate }
167+
return taskServiceClient.updateDueDate(request)
168+
}
169+
170+
export async function getTaskHistory(id: number): Promise<TaskHistoryResponse> {
171+
const request: GetTaskHistoryRequest = { id }
172+
return taskServiceClient.getTaskHistory(request)
173+
}
174+
175+
// ============== Label Service ==============
176+
177+
export async function getLabels(): Promise<LabelsResponse> {
178+
const request: Empty = {}
179+
return labelServiceClient.getLabels(request)
180+
}
181+
182+
export async function createLabel(label: CreateLabelRequest): Promise<LabelResponse> {
183+
return labelServiceClient.createLabel(label)
184+
}
185+
186+
export async function updateLabel(label: UpdateLabelRequest): Promise<LabelResponse> {
187+
return labelServiceClient.updateLabel(label)
188+
}
189+
190+
export async function deleteLabel(id: number): Promise<Empty> {
191+
const request: DeleteLabelRequest = { id }
192+
return labelServiceClient.deleteLabel(request)
193+
}
194+
195+
// ============== User Service ==============
196+
197+
export async function getProfile(): Promise<UserProfileResponse> {
198+
const request: Empty = {}
199+
return userServiceClient.getProfile(request)
200+
}
201+
202+
export async function updateNotificationSettings(
203+
settings: UpdateNotificationSettingsRequest,
204+
): Promise<Empty> {
205+
return userServiceClient.updateNotificationSettings(settings)
206+
}
207+
208+
export async function changePassword(newPassword: string): Promise<Empty> {
209+
const request: ChangePasswordRequest = { newPassword }
210+
return userServiceClient.changePassword(request)
211+
}
212+
213+
// ============== Token Service ==============
214+
215+
export async function getAppTokens(): Promise<AppTokensResponse> {
216+
const request: Empty = {}
217+
return tokenServiceClient.getAppTokens(request)
218+
}
219+
220+
export async function createAppToken(token: CreateAppTokenRequest): Promise<AppTokenResponse> {
221+
return tokenServiceClient.createAppToken(token)
222+
}
223+
224+
export async function deleteAppToken(id: number): Promise<Empty> {
225+
const request: DeleteAppTokenRequest = { id }
226+
return tokenServiceClient.deleteAppToken(request)
227+
}
228+
229+
// ============== Auth Service (unauthenticated) ==============
230+
231+
export async function signUp(request: SignUpRequest): Promise<Empty> {
232+
return authServiceClient.signUp(request)
233+
}
234+
235+
export async function login(email: string, password: string): Promise<AuthResponse> {
236+
const request: LoginRequest = { email, password }
237+
return authServiceClient.login(request)
238+
}
239+
240+
export async function refreshToken(refreshTokenValue: string): Promise<AuthResponse> {
241+
const request: RefreshTokenRequest = { refreshToken: refreshTokenValue }
242+
return authServiceClient.refreshToken(request)
243+
}
244+
245+
export async function resetPassword(email: string): Promise<Empty> {
246+
const request: ResetPasswordRequest = { email }
247+
return authServiceClient.resetPassword(request)
248+
}
249+
250+
export async function updatePassword(code: string, newPassword: string): Promise<Empty> {
251+
const request: UpdatePasswordRequest = { code, newPassword }
252+
return authServiceClient.updatePassword(request)
253+
}

0 commit comments

Comments
 (0)