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

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.web.ts CHANGED
@@ -1,21 +1,22 @@
1
1
  import { camelCaseObject, filterNonNullishKeys, is } from '@mj-studio/js-util';
2
2
  import { kFetch, kFetchFormUrlEncoded, kGlobalStorage, kRunWebAPI } from '@react-native-kakao/core';
3
- import * as querystring from 'querystring';
4
3
 
5
4
  import {
6
5
  type KakaoLoginToken,
7
6
  type KakaoScopeInfo,
8
- type KakaoServiceTerms,
9
7
  type KakaoShippingAddressResult,
10
- type KakaoUser,
8
+ type KakaoUser as KakaoUserProfile,
11
9
  type KakaoUserAPI,
12
10
  } from './index';
11
+ import { validateShippingAddressOptions } from './validateShippingAddressOptions';
13
12
 
14
13
  declare const Kakao: {
15
14
  Auth: {
16
15
  authorize: Function;
17
16
  logout: Function;
18
17
  setAccessToken: Function;
18
+ selectShippingAddress: () => Promise<{ address_id: number }>;
19
+ getAccessToken: () => string | null;
19
20
  };
20
21
  API: { request: Function };
21
22
  };
@@ -29,19 +30,11 @@ function isoToUnix(value?: string | number) {
29
30
  }
30
31
 
31
32
  const KakaoUser: KakaoUserAPI = {
33
+ hasToken: () => kRunWebAPI(() => Kakao.Auth.getAccessToken() != null),
32
34
  getAccessToken: () =>
33
35
  kRunWebAPI(async () => {
34
- const { id, expires_in, app_id } = (
35
- await kFetch<{
36
- id: number;
37
- expires_in: number;
38
- app_id: number;
39
- }>('https://kapi.kakao.com/v1/user/access_token_info', {
40
- headers: {
41
- ...kGlobalStorage.headers,
42
- },
43
- })
44
- ).body;
36
+ const { id, expires_in, app_id }: { id: number; expires_in: number; app_id: number } =
37
+ await Kakao.API.request({ url: '/v1/user/access_token_info' });
45
38
 
46
39
  return {
47
40
  id,
@@ -64,7 +57,7 @@ const KakaoUser: KakaoUserAPI = {
64
57
  body: filterNonNullishKeys({
65
58
  grant_type: 'authorization_code',
66
59
  client_id: kGlobalStorage.restApiKey,
67
- redirect_uri: encodeURIComponent(redirectUri),
60
+ redirect_uri: redirectUri,
68
61
  code,
69
62
  client_secret: clientSecret,
70
63
  }),
@@ -73,12 +66,8 @@ const KakaoUser: KakaoUserAPI = {
73
66
  return camelCaseObject(res) as any;
74
67
  }),
75
68
  setAccessTokenWeb: (token: string) => {
69
+ Kakao.Auth.setAccessToken(token);
76
70
  kGlobalStorage.accessToken = token;
77
-
78
- return new Promise((r) => {
79
- Kakao.Auth.setAccessToken(token);
80
- r(42);
81
- });
82
71
  },
83
72
  login: (params): Promise<KakaoLoginToken> =>
84
73
  kRunWebAPI(() => {
@@ -89,16 +78,28 @@ const KakaoUser: KakaoUserAPI = {
89
78
  filterNonNullishKeys({
90
79
  loginHint,
91
80
  nonce: params?.nonce ?? nonce,
92
- prompt: prompt?.join(','),
81
+ prompt: (
82
+ prompt ??
83
+ params?.prompts?.map((value) =>
84
+ value === 'SelectAccount' ? 'select_account' : value.toLowerCase(),
85
+ )
86
+ )?.join(','),
93
87
  redirectUri,
94
- scope: scope?.join(','),
95
- serviceTerms: serviceTerms?.join(','),
88
+ scope: (scope ?? params?.scopes)?.join(','),
89
+ serviceTerms: (serviceTerms ?? params?.serviceTerms)?.join(','),
96
90
  state,
97
- throughTalk,
91
+ throughTalk: throughTalk ?? (params?.useKakaoAccountLogin ? false : undefined),
98
92
  }),
99
93
  );
100
94
  }),
101
- logout: () => kRunWebAPI(() => Kakao.Auth.logout()),
95
+ logout: () =>
96
+ kRunWebAPI(async () => {
97
+ try {
98
+ await Kakao.Auth.logout();
99
+ } finally {
100
+ kGlobalStorage.clearAccessToken();
101
+ }
102
+ }),
102
103
  isLogined: async (): Promise<boolean> => {
103
104
  try {
104
105
  const { appId } = await KakaoUser.getAccessToken();
@@ -109,81 +110,186 @@ const KakaoUser: KakaoUserAPI = {
109
110
  }
110
111
  },
111
112
  isKakaoTalkLoginAvailable: async () => false,
112
- unlink: () => kRunWebAPI(() => Kakao.API.request({ url: '/v1/user/unlink' })),
113
+ unlink: () =>
114
+ kRunWebAPI(async () => {
115
+ await Kakao.API.request({ url: '/v1/user/unlink' });
116
+ Kakao.Auth.setAccessToken(null);
117
+ kGlobalStorage.clearAccessToken();
118
+ }),
113
119
  scopes: (scopes?: string[]) =>
114
120
  kRunWebAPI(async () => {
115
121
  const response = await Kakao.API.request({
116
- url: `/v2/user/scopes${scopes ? '?' + querystring.stringify({ scopes }) : ''}`,
122
+ url: '/v2/user/scopes',
123
+ data: scopes?.length ? { scopes } : undefined,
117
124
  });
118
125
 
119
126
  return camelCaseObject(response.scopes) as KakaoScopeInfo[];
120
127
  }),
121
128
  revokeScopes: (scopes: string[]) =>
122
- kRunWebAPI(() =>
123
- Kakao.API.request({
129
+ kRunWebAPI(async () => {
130
+ await Kakao.API.request({
124
131
  url: '/v2/user/revoke/scopes',
125
132
  data: {
126
133
  scopes,
127
134
  },
128
- }),
129
- ),
130
- serviceTerms: () =>
131
- kRunWebAPI(
132
- async () =>
133
- camelCaseObject(
134
- (
135
- await Kakao.API.request({
136
- url: '/v2/user/service_terms',
137
- })
138
- ).service_terms.map((v) => ({
139
- ...v,
140
- agreed_at: v.agreed_at ? isoToUnix(v.agreed_at) : null,
141
- })),
142
- ) as KakaoServiceTerms[],
143
- ),
144
- shippingAddresses: () =>
145
- kRunWebAPI(
146
- async () =>
147
- camelCaseObject(
148
- await Kakao.API.request({
149
- url: '/v1/user/shipping_address',
150
- }),
151
- ) as KakaoShippingAddressResult,
152
- ),
135
+ });
136
+ }),
137
+ serviceTerms: ({ result, tags } = {}) =>
138
+ kRunWebAPI(async () => {
139
+ const response: {
140
+ service_terms?:
141
+ | {
142
+ tag: string;
143
+ agreed_at?: string | null;
144
+ agreed: boolean;
145
+ required: boolean;
146
+ revocable: boolean;
147
+ }[]
148
+ | null;
149
+ } = await Kakao.API.request({
150
+ url: '/v2/user/service_terms',
151
+ data: filterNonNullishKeys({ result, tags: tags?.join(',') }),
152
+ });
153
+
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
+ }));
161
+ }),
162
+ revokeServiceTerms: (tags) =>
163
+ kRunWebAPI(async () => {
164
+ const response: {
165
+ revoked_service_terms?: { tag: string; agreed: boolean }[] | null;
166
+ } = await Kakao.API.request({
167
+ url: '/v2/user/revoke/service_terms',
168
+ data: { tags: tags.join(',') },
169
+ });
170
+
171
+ return response.revoked_service_terms ?? [];
172
+ }),
173
+ shippingAddresses: ({ addressId, fromUpdatedAt, pageSize } = {}) =>
174
+ kRunWebAPI(async () => {
175
+ validateShippingAddressOptions({ addressId, fromUpdatedAt, pageSize });
176
+
177
+ const query = new URLSearchParams(
178
+ Object.entries(
179
+ filterNonNullishKeys(
180
+ addressId != null
181
+ ? { address_id: addressId }
182
+ : { from_updated_at: fromUpdatedAt, page_size: pageSize },
183
+ ),
184
+ ).map(([key, value]) => [key, String(value)]),
185
+ );
186
+
187
+ const { body } = await kFetch<Record<string, unknown>>(
188
+ `https://kapi.kakao.com/v1/user/shipping_address?${query}`,
189
+ { headers: { Authorization: `Bearer ${Kakao.Auth.getAccessToken() ?? ''}` } },
190
+ );
191
+
192
+ const response = camelCaseObject(body) as {
193
+ userId?: number;
194
+ shippingAddressesNeedsAgreement?: boolean;
195
+ shippingAddresses?: (Omit<
196
+ KakaoShippingAddressResult['shippingAddresses'][number],
197
+ 'updatedAt'
198
+ > & {
199
+ updatedAt?: string | number;
200
+ })[];
201
+ };
202
+
203
+ return {
204
+ userId: response.userId,
205
+ needsAgreement: response.shippingAddressesNeedsAgreement,
206
+ shippingAddresses: (response.shippingAddresses ?? []).map((address) => ({
207
+ ...address,
208
+ updatedAt: isoToUnix(address.updatedAt),
209
+ })),
210
+ };
211
+ }),
212
+ selectShippingAddress: () =>
213
+ kRunWebAPI(async () => {
214
+ try {
215
+ const { address_id } = await Kakao.Auth.selectShippingAddress();
216
+
217
+ return address_id;
218
+ } catch (error) {
219
+ if (error != null && typeof error === 'object' && 'error_code' in error) {
220
+ throw {
221
+ code: String(error.error_code),
222
+ message: 'error_msg' in error ? String(error.error_msg) : undefined,
223
+ };
224
+ }
225
+
226
+ throw error;
227
+ }
228
+ }),
153
229
  me: () =>
154
230
  kRunWebAPI(async () => {
155
- const ret: any = camelCaseObject(await Kakao.API.request({ url: '/v2/user/me' }));
231
+ const ret = camelCaseObject(await Kakao.API.request({ url: '/v2/user/me' })) as {
232
+ id: number;
233
+ connectedAt?: string;
234
+ synchedAt?: string;
235
+ kakaoAccount?: Partial<KakaoUserProfile> & {
236
+ profile?: Partial<
237
+ Pick<KakaoUserProfile, 'nickname' | 'profileImageUrl' | 'thumbnailImageUrl'>
238
+ >;
239
+ };
240
+ properties?: { nickname?: string; profileImage?: string; thumbnailImage?: string };
241
+ };
156
242
  const kakaoAccount = ret.kakaoAccount ?? {};
157
243
  const properties = ret.properties ?? {};
244
+ const profile = kakaoAccount.profile ?? {};
158
245
 
159
246
  return {
160
- ...ret,
161
- ...properties,
162
- ...kakaoAccount,
247
+ id: ret.id,
163
248
  email: kakaoAccount.email,
164
- name: null,
165
- nickname: kakaoAccount.profile.nickname,
166
- profileImageUrl: properties.profileImage,
167
- thumbnailImageUrl: properties.thumbnailImage,
168
- phoneNumber: kakaoAccount.profile.phoneNumber,
169
- isEmailValid: ret.isEmailValid,
170
- isEmailVerified: ret.isEmailVerified,
249
+ name: kakaoAccount.name,
250
+ nickname: profile.nickname ?? properties.nickname,
251
+ profileImageUrl: profile.profileImageUrl ?? properties.profileImage,
252
+ thumbnailImageUrl: profile.thumbnailImageUrl ?? properties.thumbnailImage,
253
+ phoneNumber: kakaoAccount.phoneNumber,
254
+ isEmailValid: kakaoAccount.isEmailValid,
255
+ isEmailVerified: kakaoAccount.isEmailVerified,
256
+ ageRange: kakaoAccount.ageRange,
257
+ birthday: kakaoAccount.birthday,
258
+ birthdayType: kakaoAccount.birthdayType,
259
+ birthyear: kakaoAccount.birthyear,
260
+ gender: kakaoAccount.gender,
261
+ isKorean: kakaoAccount.isKorean,
262
+ ageRangeNeedsAgreement: kakaoAccount.ageRangeNeedsAgreement,
263
+ birthdayNeedsAgreement: kakaoAccount.birthdayNeedsAgreement,
264
+ birthyearNeedsAgreement: kakaoAccount.birthyearNeedsAgreement,
265
+ emailNeedsAgreement: kakaoAccount.emailNeedsAgreement,
266
+ genderNeedsAgreement: kakaoAccount.genderNeedsAgreement,
267
+ isKoreanNeedsAgreement: kakaoAccount.isKoreanNeedsAgreement,
268
+ phoneNumberNeedsAgreement: kakaoAccount.phoneNumberNeedsAgreement,
269
+ profileNeedsAgreement: kakaoAccount.profileNeedsAgreement,
270
+ nameNeedsAgreement: kakaoAccount.nameNeedsAgreement,
271
+ profileImageNeedsAgreement: kakaoAccount.profileImageNeedsAgreement,
272
+ profileNicknameNeedsAgreement: kakaoAccount.profileNicknameNeedsAgreement,
273
+ legalBirthDateNeedsAgreement: kakaoAccount.legalBirthDateNeedsAgreement,
171
274
  connectedAt: isoToUnix(ret.connectedAt),
172
275
  synchedAt: isoToUnix(ret.synchedAt),
173
- } as KakaoUser;
276
+ } as KakaoUserProfile;
174
277
  }),
175
278
  };
176
279
 
177
280
  export const {
178
281
  isKakaoTalkLoginAvailable,
179
282
  isLogined,
283
+ hasToken,
180
284
  login,
181
285
  logout,
182
286
  me,
183
287
  revokeScopes,
184
288
  scopes,
185
289
  serviceTerms,
290
+ revokeServiceTerms,
186
291
  shippingAddresses,
292
+ selectShippingAddress,
187
293
  unlink,
188
294
  setAccessTokenWeb,
189
295
  issueAccessTokenWithCodeWeb,
@@ -4,6 +4,7 @@ import { TurboModuleRegistry } from 'react-native';
4
4
  import type {
5
5
  KakaoAccessTokenInfo,
6
6
  KakaoLoginToken,
7
+ KakaoRevokedServiceTerms,
7
8
  KakaoScopeInfo,
8
9
  KakaoServiceTerms,
9
10
  KakaoShippingAddressResult,
@@ -22,10 +23,17 @@ export interface Spec extends TurboModule {
22
23
  logout(): Promise<void>;
23
24
  unlink(): Promise<void>;
24
25
  isLogined(): Promise<boolean>;
26
+ hasToken(): Promise<boolean>;
25
27
  scopes(scopes?: string[]): Promise<KakaoScopeInfo[]>;
26
28
  revokeScopes(scopes: string[]): Promise<void>;
27
- serviceTerms(): Promise<KakaoServiceTerms[]>;
28
- shippingAddresses(): Promise<KakaoShippingAddressResult>;
29
+ serviceTerms(result?: string, tags?: string[]): Promise<KakaoServiceTerms[]>;
30
+ revokeServiceTerms(tags: string[]): Promise<KakaoRevokedServiceTerms[]>;
31
+ shippingAddresses(
32
+ addressId?: number,
33
+ fromUpdatedAt?: number,
34
+ pageSize?: number,
35
+ ): Promise<KakaoShippingAddressResult>;
36
+ selectShippingAddress(): Promise<number>;
29
37
  me(): Promise<KakaoUser>;
30
38
  getAccessToken(): Promise<KakaoAccessTokenInfo>;
31
39
  }
@@ -0,0 +1,30 @@
1
+ import { kAssert } from '@react-native-kakao/core';
2
+
3
+ export function validateShippingAddressOptions({
4
+ addressId,
5
+ fromUpdatedAt,
6
+ pageSize,
7
+ }: {
8
+ addressId?: number;
9
+ fromUpdatedAt?: number;
10
+ pageSize?: number;
11
+ }) {
12
+ if (addressId != null) {
13
+ kAssert(
14
+ Number.isSafeInteger(addressId),
15
+ '[shippingAddresses] addressId must be a safe integer',
16
+ );
17
+
18
+ return;
19
+ }
20
+
21
+ kAssert(
22
+ fromUpdatedAt == null || (Number.isSafeInteger(fromUpdatedAt) && fromUpdatedAt >= 0),
23
+ '[shippingAddresses] fromUpdatedAt must be a non-negative safe integer in Unix seconds',
24
+ );
25
+
26
+ kAssert(
27
+ pageSize == null || (Number.isInteger(pageSize) && pageSize >= 2 && pageSize <= 2147483647),
28
+ '[shippingAddresses] pageSize must be an integer between 2 and 2147483647',
29
+ );
30
+ }