@react-native-kakao/user 3.1.0-next.0 → 3.1.0-next.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { is } from '@mj-studio/js-util';
2
- import { kAssert } from '@react-native-kakao/core';
2
+ import { kAssert, kCreateWebError } from '@react-native-kakao/core';
3
3
 
4
4
  import Native from './spec/NativeKakaoUser';
5
5
  import { validateShippingAddressOptions } from './validateShippingAddressOptions';
@@ -15,13 +15,28 @@ export interface KakaoLoginToken {
15
15
  refreshTokenExpiresIn: number;
16
16
  scopes: string[];
17
17
  }
18
+ export type KakaoCodeTokenOptions = {
19
+ code: string;
20
+ codeVerifier?: string;
21
+ redirectUri?: string;
22
+ web?: { clientSecret?: string };
23
+ };
24
+
25
+ const minActivityRequestCode = -2147483648;
26
+ const maxActivityRequestCode = 2147483647;
27
+
28
+ export type KakaoTalkLaunchMethod = 'UniversalLink' | 'CustomScheme';
29
+
30
+ const defaultTalkLaunchMethod: KakaoTalkLaunchMethod = 'UniversalLink';
31
+ const webOnlyErrorCode = 'Package-UnsupportedPlatform';
32
+
18
33
  export interface KakaoScopeInfo {
19
34
  id: string;
20
35
  agreed: boolean;
21
36
  displayName: string;
22
- revocable?: boolean;
37
+ revocable?: boolean | null;
23
38
  using: boolean;
24
- delegated?: boolean;
39
+ delegated?: boolean | null;
25
40
  type: string;
26
41
  }
27
42
  export interface KakaoServiceTerms {
@@ -30,6 +45,7 @@ export interface KakaoServiceTerms {
30
45
  agreed: boolean;
31
46
  required: boolean;
32
47
  revocable: boolean;
48
+ agreedBy?: 'KAUTH' | 'KAPI' | 'unknown';
33
49
  }
34
50
  export type KakaoRevokedServiceTerms = {
35
51
  tag: string;
@@ -61,34 +77,45 @@ export interface KakaoShippingAddress {
61
77
  }
62
78
  export interface KakaoUser {
63
79
  id: number;
64
- email: string;
80
+ properties: Record<string, string>;
81
+ hasSignedUp?: boolean | null;
82
+ uuid?: string | null;
83
+ groupUserToken?: string | null;
84
+ email?: string | null;
65
85
  name?: string | null;
66
- nickname: string;
67
- profileImageUrl: string;
68
- thumbnailImageUrl: string;
86
+ nickname?: string | null;
87
+ profileImageUrl?: string | null;
88
+ thumbnailImageUrl?: string | null;
89
+ isDefaultImage?: boolean | null;
69
90
  phoneNumber?: string | null;
70
91
  ageRange?: string | null;
71
92
  birthday?: string | null;
72
93
  birthdayType?: string | null;
94
+ isLeapMonth?: boolean | null;
73
95
  birthyear?: string | null;
74
96
  gender?: string | null;
75
- isEmailValid: boolean;
76
- isEmailVerified: boolean;
77
- isKorean?: boolean;
78
- ageRangeNeedsAgreement?: boolean;
79
- birthdayNeedsAgreement?: boolean;
80
- birthyearNeedsAgreement?: boolean;
81
- emailNeedsAgreement?: boolean;
82
- genderNeedsAgreement?: boolean;
83
- isKoreanNeedsAgreement?: boolean;
84
- phoneNumberNeedsAgreement?: boolean;
85
- profileNeedsAgreement?: boolean;
86
- nameNeedsAgreement?: boolean;
87
- profileImageNeedsAgreement?: boolean;
88
- profileNicknameNeedsAgreement?: boolean;
89
- legalBirthDateNeedsAgreement?: boolean;
90
- connectedAt?: number;
91
- synchedAt?: number;
97
+ isEmailValid?: boolean | null;
98
+ isEmailVerified?: boolean | null;
99
+ isKorean?: boolean | null;
100
+ ageRangeNeedsAgreement?: boolean | null;
101
+ birthdayNeedsAgreement?: boolean | null;
102
+ birthyearNeedsAgreement?: boolean | null;
103
+ emailNeedsAgreement?: boolean | null;
104
+ genderNeedsAgreement?: boolean | null;
105
+ isKoreanNeedsAgreement?: boolean | null;
106
+ phoneNumberNeedsAgreement?: boolean | null;
107
+ profileNeedsAgreement?: boolean | null;
108
+ nameNeedsAgreement?: boolean | null;
109
+ profileImageNeedsAgreement?: boolean | null;
110
+ profileNicknameNeedsAgreement?: boolean | null;
111
+ legalName?: string | null;
112
+ legalNameNeedsAgreement?: boolean | null;
113
+ legalBirthDate?: string | null;
114
+ legalBirthDateNeedsAgreement?: boolean | null;
115
+ legalGender?: 'female' | 'male' | null;
116
+ legalGenderNeedsAgreement?: boolean | null;
117
+ connectedAt?: number | null;
118
+ synchedAt?: number | null;
92
119
  }
93
120
  export interface KakaoAccessTokenInfo {
94
121
  id?: number;
@@ -101,12 +128,20 @@ export function login({
101
128
  useKakaoAccountLogin,
102
129
  scopes,
103
130
  nonce,
131
+ loginHint,
132
+ channelPublicIds,
133
+ android,
134
+ ios,
104
135
  }: {
105
136
  serviceTerms?: string[];
106
137
  prompts?: ('Login' | 'Create' | 'Cert' | 'SelectAccount')[];
107
138
  scopes?: string[];
108
139
  useKakaoAccountLogin?: boolean;
109
140
  nonce?: string;
141
+ loginHint?: string;
142
+ channelPublicIds?: string[];
143
+ android?: { requestCode?: number };
144
+ ios?: { launchMethod?: KakaoTalkLaunchMethod };
110
145
  web?: {
111
146
  redirectUri: string;
112
147
  scope?: string[];
@@ -118,31 +153,133 @@ export function login({
118
153
  nonce?: string;
119
154
  };
120
155
  } = {}): Promise<KakaoLoginToken> {
156
+ const requestCode = android?.requestCode;
157
+ kAssert(
158
+ requestCode == null ||
159
+ (Number.isInteger(requestCode) &&
160
+ requestCode >= minActivityRequestCode &&
161
+ requestCode <= maxActivityRequestCode),
162
+ `[login] android.requestCode must be an integer from ${minActivityRequestCode} to ${maxActivityRequestCode}`,
163
+ );
164
+
121
165
  kAssert(
122
166
  !useKakaoAccountLogin ? !is.notEmptyArray(prompts) && !is.notEmptyArray(scopes) : true,
123
167
  '[login] `prompts` and `scopes` cannot be passed if useKakaoAccountLogin is false.',
124
168
  );
125
169
 
126
170
  kAssert(
127
- is.notEmptyArray(scopes) ? !is.notEmptyArray(prompts) && !is.notEmptyArray(serviceTerms) : true,
128
- '[login] `scopes` cannot be passed with `prompts` or `serviceTerms`',
171
+ is.notEmptyArray(scopes)
172
+ ? !is.notEmptyArray(prompts) &&
173
+ !is.notEmptyArray(serviceTerms) &&
174
+ !is.notEmptyArray(channelPublicIds)
175
+ : true,
176
+ '[login] `scopes` cannot be passed with `prompts`, `serviceTerms`, or `channelPublicIds`',
177
+ );
178
+
179
+ kAssert(
180
+ loginHint == null ||
181
+ (!is.notEmptyArray(scopes) &&
182
+ !is.notEmptyArray(serviceTerms) &&
183
+ !is.notEmptyArray(channelPublicIds)),
184
+ '[login] `loginHint` cannot be passed with `scopes`, `serviceTerms`, or `channelPublicIds` on native platforms.',
129
185
  );
130
186
 
131
187
  return Native.login(
132
188
  serviceTerms ?? [],
133
189
  prompts ?? [],
134
190
  useKakaoAccountLogin ?? false,
191
+ ios?.launchMethod ?? defaultTalkLaunchMethod,
135
192
  scopes ?? [],
136
193
  nonce,
194
+ loginHint,
195
+ channelPublicIds ?? [],
196
+ requestCode,
137
197
  );
138
198
  }
139
199
 
140
- export function logout() {
141
- return Native.logout();
200
+ export async function signup({ properties }: { properties?: Record<string, string> } = {}) {
201
+ await Native.signup(properties);
142
202
  }
143
203
 
144
- export function unlink() {
145
- return Native.unlink();
204
+ export function loginWithKakao({
205
+ uiMode = 'auto',
206
+ prompts,
207
+ loginHint,
208
+ nonce,
209
+ channelPublicIds,
210
+ serviceTerms,
211
+ ios,
212
+ }: {
213
+ uiMode?: 'auto' | 'light' | 'dark';
214
+ prompts?: ('Login' | 'Create' | 'Cert' | 'SelectAccount')[];
215
+ loginHint?: string;
216
+ nonce?: string;
217
+ channelPublicIds?: string[];
218
+ serviceTerms?: string[];
219
+ ios?: {
220
+ orientation?: 'auto' | 'portrait' | 'landscape';
221
+ launchMethod?: KakaoTalkLaunchMethod;
222
+ };
223
+ } = {}): Promise<KakaoLoginToken> {
224
+ return Native.loginWithKakao(
225
+ uiMode,
226
+ ios?.orientation ?? 'auto',
227
+ ios?.launchMethod ?? defaultTalkLaunchMethod,
228
+ prompts ?? [],
229
+ loginHint,
230
+ nonce,
231
+ channelPublicIds ?? [],
232
+ serviceTerms ?? [],
233
+ );
234
+ }
235
+
236
+ export function refreshToken({
237
+ token,
238
+ ios,
239
+ }: {
240
+ token?: KakaoLoginToken;
241
+ ios?: { loggingEnabled?: boolean };
242
+ } = {}): Promise<KakaoLoginToken> {
243
+ kAssert(
244
+ ios?.loggingEnabled == null || typeof ios.loggingEnabled === 'boolean',
245
+ '[refreshToken] ios.loggingEnabled must be a boolean',
246
+ );
247
+
248
+ if (token !== undefined) {
249
+ kAssert(
250
+ token != null &&
251
+ is.string(token.accessToken) &&
252
+ token.accessToken.length > 0 &&
253
+ is.string(token.refreshToken) &&
254
+ token.refreshToken.length > 0,
255
+ '[refreshToken] token must contain non-empty accessToken and refreshToken strings',
256
+ );
257
+
258
+ kAssert(
259
+ [token.accessTokenExpiresAt, token.refreshTokenExpiresAt].every(
260
+ (value) => Number.isFinite(value) && !Number.isNaN(new Date(value * 1000).getTime()),
261
+ ) && [token.accessTokenExpiresIn, token.refreshTokenExpiresIn].every(Number.isFinite),
262
+ '[refreshToken] token expiry timestamps must be valid Unix seconds and lifetimes must be finite',
263
+ );
264
+
265
+ kAssert(
266
+ (token.tokenType == null || is.string(token.tokenType)) &&
267
+ (token.idToken == null || is.string(token.idToken)) &&
268
+ Array.isArray(token.scopes) &&
269
+ token.scopes.every((scope) => is.string(scope)),
270
+ '[refreshToken] tokenType and idToken must be strings when present, and scopes must be strings',
271
+ );
272
+ }
273
+
274
+ return Native.refreshToken(token, ios?.loggingEnabled ?? true);
275
+ }
276
+
277
+ export async function logout() {
278
+ await Native.logout();
279
+ }
280
+
281
+ export async function unlink() {
282
+ await Native.unlink();
146
283
  }
147
284
 
148
285
  export function isLogined() {
@@ -159,7 +296,11 @@ export function scopes(scopes?: string[]): Promise<KakaoScopeInfo[]> {
159
296
  return Native.scopes(scopes);
160
297
  }
161
298
 
162
- export function revokeScopes(scopes: string[]): Promise<void> {
299
+ export async function revokeScopes(scopes: string[]): Promise<void> {
300
+ await Native.revokeScopes(scopes);
301
+ }
302
+
303
+ export function revokeScopesWithResult(scopes: string[]): Promise<KakaoScopeInfo[]> {
163
304
  return Native.revokeScopes(scopes);
164
305
  }
165
306
 
@@ -195,21 +336,49 @@ export function selectShippingAddress(): Promise<number> {
195
336
  return Native.selectShippingAddress();
196
337
  }
197
338
 
198
- export function me(): Promise<KakaoUser> {
199
- return Native.me();
339
+ export function me({
340
+ propertyKeys,
341
+ secureResource = true,
342
+ }: { propertyKeys?: string[]; secureResource?: boolean } = {}): Promise<KakaoUser> {
343
+ return Native.me(propertyKeys, secureResource);
344
+ }
345
+
346
+ export async function issueAccessTokenWithCode({
347
+ code,
348
+ codeVerifier,
349
+ redirectUri,
350
+ }: KakaoCodeTokenOptions): Promise<KakaoLoginToken> {
351
+ kAssert(is.string(code) && code.length > 0, '[issueAccessTokenWithCode] code is required');
352
+
353
+ return Native.issueAccessTokenWithCode(code, codeVerifier, redirectUri);
200
354
  }
201
355
 
202
356
  export function getAccessToken(): Promise<KakaoAccessTokenInfo> {
203
357
  return Native.getAccessToken();
204
358
  }
205
359
 
360
+ export async function updateProfile({
361
+ properties,
362
+ }: {
363
+ properties: Record<string, string>;
364
+ }): Promise<void> {
365
+ await Native.updateProfile(properties);
366
+ }
367
+
206
368
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
207
- export function setAccessTokenWeb(token: string) {}
369
+ export function setAccessTokenWeb(token: string): void {
370
+ kCreateWebError({
371
+ code: webOnlyErrorCode,
372
+ message: '[setAccessTokenWeb] This API is only supported on web.',
373
+ });
374
+ }
375
+
208
376
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
209
377
  export async function issueAccessTokenWithCodeWeb(params: {
210
378
  redirectUri: string;
211
379
  clientSecret?: string;
212
380
  code: string;
381
+ codeVerifier?: string;
213
382
  }): Promise<{
214
383
  tokenType: string;
215
384
  accessToken: string;
@@ -219,11 +388,18 @@ export async function issueAccessTokenWithCodeWeb(params: {
219
388
  refreshTokenExpiresIn: number;
220
389
  scope?: string;
221
390
  }> {
222
- return {} as any;
391
+ kCreateWebError({
392
+ code: webOnlyErrorCode,
393
+ message: '[issueAccessTokenWithCodeWeb] This API is only supported on web.',
394
+ });
223
395
  }
224
396
 
225
397
  const KakaoUser = {
226
398
  login,
399
+ loginWithKakao,
400
+ refreshToken,
401
+ issueAccessTokenWithCode,
402
+ signup,
227
403
  logout,
228
404
  unlink,
229
405
  isLogined,
@@ -231,11 +407,13 @@ const KakaoUser = {
231
407
  isKakaoTalkLoginAvailable,
232
408
  scopes,
233
409
  revokeScopes,
410
+ revokeScopesWithResult,
234
411
  serviceTerms,
235
412
  revokeServiceTerms,
236
413
  shippingAddresses,
237
414
  selectShippingAddress,
238
415
  me,
416
+ updateProfile,
239
417
  getAccessToken,
240
418
  setAccessTokenWeb,
241
419
  issueAccessTokenWithCodeWeb,
package/src/index.web.ts CHANGED
@@ -1,9 +1,17 @@
1
1
  import { camelCaseObject, filterNonNullishKeys, is } from '@mj-studio/js-util';
2
- import { kFetch, kFetchFormUrlEncoded, kGlobalStorage, kRunWebAPI } from '@react-native-kakao/core';
2
+ import {
3
+ kAssert,
4
+ kCreateWebError,
5
+ kFetch,
6
+ kFetchFormUrlEncoded,
7
+ kGlobalStorage,
8
+ kRunWebAPI,
9
+ } from '@react-native-kakao/core';
3
10
 
4
11
  import {
5
12
  type KakaoLoginToken,
6
13
  type KakaoScopeInfo,
14
+ type KakaoServiceTerms,
7
15
  type KakaoShippingAddressResult,
8
16
  type KakaoUser as KakaoUserProfile,
9
17
  type KakaoUserAPI,
@@ -11,6 +19,7 @@ import {
11
19
  import { validateShippingAddressOptions } from './validateShippingAddressOptions';
12
20
 
13
21
  declare const Kakao: {
22
+ isInitialized: () => boolean;
14
23
  Auth: {
15
24
  authorize: Function;
16
25
  logout: Function;
@@ -30,6 +39,21 @@ function isoToUnix(value?: string | number) {
30
39
  }
31
40
 
32
41
  const KakaoUser: KakaoUserAPI = {
42
+ loginWithKakao: () =>
43
+ kRunWebAPI(() => {
44
+ throw {
45
+ code: 'Package-UnsupportedPlatform',
46
+ message:
47
+ 'loginWithKakao is supported on Android and iOS. Use login with web.redirectUri on web.',
48
+ };
49
+ }),
50
+ refreshToken: () =>
51
+ kRunWebAPI(() => {
52
+ throw {
53
+ code: 'Package-UnsupportedPlatform',
54
+ message: 'The Kakao JavaScript SDK does not support refreshToken.',
55
+ };
56
+ }),
33
57
  hasToken: () => kRunWebAPI(() => Kakao.Auth.getAccessToken() != null),
34
58
  getAccessToken: () =>
35
59
  kRunWebAPI(async () => {
@@ -42,7 +66,62 @@ const KakaoUser: KakaoUserAPI = {
42
66
  appId: app_id,
43
67
  };
44
68
  }),
45
- issueAccessTokenWithCodeWeb: ({ clientSecret, code, redirectUri }) =>
69
+ issueAccessTokenWithCode: ({ code, codeVerifier, redirectUri, web }) =>
70
+ kRunWebAPI(async () => {
71
+ kAssert(
72
+ is.string(code) && code.length > 0 && is.string(redirectUri) && redirectUri.length > 0,
73
+ '[issueAccessTokenWithCode] code and redirectUri are required on web',
74
+ );
75
+
76
+ if (typeof Kakao === 'undefined' || !Kakao.isInitialized()) {
77
+ kCreateWebError({
78
+ code: 'Package-SDKNotInitialized',
79
+ message: 'Initialize the Kakao SDK before exchanging an authorization code.',
80
+ });
81
+ }
82
+
83
+ const token = await KakaoUser.issueAccessTokenWithCodeWeb({
84
+ code,
85
+ codeVerifier,
86
+ redirectUri: redirectUri!,
87
+ clientSecret: web?.clientSecret,
88
+ });
89
+
90
+ if (
91
+ !is.string(token.accessToken) ||
92
+ !token.accessToken ||
93
+ !is.string(token.refreshToken) ||
94
+ !token.refreshToken ||
95
+ !is.string(token.tokenType) ||
96
+ (token.idToken != null && !is.string(token.idToken)) ||
97
+ !Number.isFinite(token.expiresIn) ||
98
+ token.expiresIn < 0 ||
99
+ !Number.isFinite(token.refreshTokenExpiresIn) ||
100
+ token.refreshTokenExpiresIn < 0 ||
101
+ (token.scope != null && !is.string(token.scope))
102
+ ) {
103
+ kCreateWebError({
104
+ code: 'Package-APIResponseNotFound',
105
+ message: 'issueAccessTokenWithCode returned invalid token data.',
106
+ });
107
+ }
108
+
109
+ const issuedAt = Math.floor(Date.now() / 1000);
110
+ KakaoUser.setAccessTokenWeb(token.accessToken);
111
+
112
+ return {
113
+ accessToken: token.accessToken,
114
+ refreshToken: token.refreshToken,
115
+ tokenType: token.tokenType,
116
+ idToken: token.idToken,
117
+ accessTokenExpiresIn: token.expiresIn,
118
+ refreshTokenExpiresIn: token.refreshTokenExpiresIn,
119
+ accessTokenExpiresAt: issuedAt + token.expiresIn,
120
+ refreshTokenExpiresAt: issuedAt + token.refreshTokenExpiresIn,
121
+ scopes: token.scope?.split(' ').filter(Boolean) ?? [],
122
+ };
123
+ }),
124
+ issueAccessTokenWithCodeWeb: ({ clientSecret, code, codeVerifier, redirectUri }) =>
46
125
  kRunWebAPI(async () => {
47
126
  const res = await kFetchFormUrlEncoded<{
48
127
  token_type: string;
@@ -59,6 +138,7 @@ const KakaoUser: KakaoUserAPI = {
59
138
  client_id: kGlobalStorage.restApiKey,
60
139
  redirect_uri: redirectUri,
61
140
  code,
141
+ code_verifier: codeVerifier,
62
142
  client_secret: clientSecret,
63
143
  }),
64
144
  }).then((r) => r.body);
@@ -73,10 +153,12 @@ const KakaoUser: KakaoUserAPI = {
73
153
  kRunWebAPI(() => {
74
154
  const { loginHint, nonce, prompt, redirectUri, scope, state, throughTalk, serviceTerms } =
75
155
  params?.web ?? {};
156
+ const channelPublicIds = params?.channelPublicIds;
76
157
 
77
158
  return Kakao.Auth.authorize(
78
159
  filterNonNullishKeys({
79
- loginHint,
160
+ loginHint: params?.loginHint ?? loginHint,
161
+ channelPublicId: channelPublicIds?.length ? channelPublicIds.join(',') : undefined,
80
162
  nonce: params?.nonce ?? nonce,
81
163
  prompt: (
82
164
  prompt ??
@@ -92,6 +174,13 @@ const KakaoUser: KakaoUserAPI = {
92
174
  }),
93
175
  );
94
176
  }),
177
+ signup: ({ properties } = {}) =>
178
+ kRunWebAPI(async () => {
179
+ await Kakao.API.request({
180
+ url: '/v1/user/signup',
181
+ data: filterNonNullishKeys({ properties }),
182
+ });
183
+ }),
95
184
  logout: () =>
96
185
  kRunWebAPI(async () => {
97
186
  try {
@@ -123,17 +212,12 @@ const KakaoUser: KakaoUserAPI = {
123
212
  data: scopes?.length ? { scopes } : undefined,
124
213
  });
125
214
 
126
- return camelCaseObject(response.scopes) as KakaoScopeInfo[];
127
- }),
128
- revokeScopes: (scopes: string[]) =>
129
- kRunWebAPI(async () => {
130
- await Kakao.API.request({
131
- url: '/v2/user/revoke/scopes',
132
- data: {
133
- scopes,
134
- },
135
- });
215
+ return camelCaseObject(response.scopes ?? []) as KakaoScopeInfo[];
136
216
  }),
217
+ revokeScopes: async (scopes: string[]) => {
218
+ await revokeScopesWithResult(scopes);
219
+ },
220
+ revokeScopesWithResult,
137
221
  serviceTerms: ({ result, tags } = {}) =>
138
222
  kRunWebAPI(async () => {
139
223
  const response: {
@@ -144,6 +228,7 @@ const KakaoUser: KakaoUserAPI = {
144
228
  agreed: boolean;
145
229
  required: boolean;
146
230
  revocable: boolean;
231
+ agreed_by?: string | null;
147
232
  }[]
148
233
  | null;
149
234
  } = await Kakao.API.request({
@@ -151,13 +236,22 @@ const KakaoUser: KakaoUserAPI = {
151
236
  data: filterNonNullishKeys({ result, tags: tags?.join(',') }),
152
237
  });
153
238
 
154
- return (response.service_terms ?? []).map((term) => ({
155
- tag: term.tag,
156
- agreedAt: term.agreed_at ? isoToUnix(term.agreed_at) : undefined,
157
- agreed: term.agreed,
158
- required: term.required,
159
- revocable: term.revocable,
160
- }));
239
+ return (response.service_terms ?? []).map(
240
+ (term): KakaoServiceTerms =>
241
+ filterNonNullishKeys({
242
+ tag: term.tag,
243
+ agreedAt: term.agreed_at ? isoToUnix(term.agreed_at) : undefined,
244
+ agreed: term.agreed,
245
+ required: term.required,
246
+ revocable: term.revocable,
247
+ agreedBy:
248
+ term.agreed_by == null
249
+ ? undefined
250
+ : term.agreed_by === 'KAUTH' || term.agreed_by === 'KAPI'
251
+ ? term.agreed_by
252
+ : 'unknown',
253
+ }),
254
+ );
161
255
  }),
162
256
  revokeServiceTerms: (tags) =>
163
257
  kRunWebAPI(async () => {
@@ -226,15 +320,33 @@ const KakaoUser: KakaoUserAPI = {
226
320
  throw error;
227
321
  }
228
322
  }),
229
- me: () =>
323
+ updateProfile: ({ properties }) =>
230
324
  kRunWebAPI(async () => {
231
- const ret = camelCaseObject(await Kakao.API.request({ url: '/v2/user/me' })) as {
325
+ await Kakao.API.request({ url: '/v1/user/update_profile', data: { properties } });
326
+ }),
327
+ me: ({ propertyKeys, secureResource = true } = {}) =>
328
+ kRunWebAPI(async () => {
329
+ const response: { properties?: Record<string, string> } = await Kakao.API.request({
330
+ url: '/v2/user/me',
331
+ data: filterNonNullishKeys({
332
+ property_keys: propertyKeys,
333
+ secure_resource: secureResource,
334
+ }),
335
+ });
336
+
337
+ const ret = camelCaseObject(response) as {
232
338
  id: number;
339
+ hasSignedUp?: boolean | null;
340
+ forPartner?: { uuid?: string };
341
+ groupUserToken?: string;
233
342
  connectedAt?: string;
234
343
  synchedAt?: string;
235
344
  kakaoAccount?: Partial<KakaoUserProfile> & {
236
345
  profile?: Partial<
237
- Pick<KakaoUserProfile, 'nickname' | 'profileImageUrl' | 'thumbnailImageUrl'>
346
+ Pick<
347
+ KakaoUserProfile,
348
+ 'nickname' | 'profileImageUrl' | 'thumbnailImageUrl' | 'isDefaultImage'
349
+ >
238
350
  >;
239
351
  };
240
352
  properties?: { nickname?: string; profileImage?: string; thumbnailImage?: string };
@@ -242,9 +354,22 @@ const KakaoUser: KakaoUserAPI = {
242
354
  const kakaoAccount = ret.kakaoAccount ?? {};
243
355
  const properties = ret.properties ?? {};
244
356
  const profile = kakaoAccount.profile ?? {};
357
+ const legalGender = kakaoAccount.legalGender;
245
358
 
246
359
  return {
247
360
  id: ret.id,
361
+ properties: response.properties ?? {},
362
+ hasSignedUp: ret.hasSignedUp ?? null,
363
+ uuid: ret.forPartner?.uuid ?? null,
364
+ groupUserToken: ret.groupUserToken ?? null,
365
+ isDefaultImage: profile.isDefaultImage ?? null,
366
+ isLeapMonth: kakaoAccount.isLeapMonth ?? null,
367
+ legalName: kakaoAccount.legalName ?? null,
368
+ legalNameNeedsAgreement: kakaoAccount.legalNameNeedsAgreement ?? null,
369
+ legalBirthDate: kakaoAccount.legalBirthDate ?? null,
370
+ legalBirthDateNeedsAgreement: kakaoAccount.legalBirthDateNeedsAgreement,
371
+ legalGender: legalGender === 'female' || legalGender === 'male' ? legalGender : null,
372
+ legalGenderNeedsAgreement: kakaoAccount.legalGenderNeedsAgreement ?? null,
248
373
  email: kakaoAccount.email,
249
374
  name: kakaoAccount.name,
250
375
  nickname: profile.nickname ?? properties.nickname,
@@ -270,20 +395,35 @@ const KakaoUser: KakaoUserAPI = {
270
395
  nameNeedsAgreement: kakaoAccount.nameNeedsAgreement,
271
396
  profileImageNeedsAgreement: kakaoAccount.profileImageNeedsAgreement,
272
397
  profileNicknameNeedsAgreement: kakaoAccount.profileNicknameNeedsAgreement,
273
- legalBirthDateNeedsAgreement: kakaoAccount.legalBirthDateNeedsAgreement,
274
398
  connectedAt: isoToUnix(ret.connectedAt),
275
399
  synchedAt: isoToUnix(ret.synchedAt),
276
400
  } as KakaoUserProfile;
277
401
  }),
278
402
  };
279
403
 
404
+ export function revokeScopesWithResult(scopes: string[]): Promise<KakaoScopeInfo[]> {
405
+ return kRunWebAPI(async () => {
406
+ const response = await Kakao.API.request({
407
+ url: '/v2/user/revoke/scopes',
408
+ data: { scopes },
409
+ });
410
+
411
+ return camelCaseObject(response.scopes ?? []) as KakaoScopeInfo[];
412
+ });
413
+ }
414
+
280
415
  export const {
416
+ loginWithKakao,
417
+ refreshToken,
418
+ issueAccessTokenWithCode,
281
419
  isKakaoTalkLoginAvailable,
282
420
  isLogined,
283
421
  hasToken,
284
422
  login,
423
+ signup,
285
424
  logout,
286
425
  me,
426
+ updateProfile,
287
427
  revokeScopes,
288
428
  scopes,
289
429
  serviceTerms,