@apps-in-toss/native-modules 0.0.0-dev.1752049503789 → 1.0.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/dist/bridges-meta.json +36 -13
- package/dist/index.cjs +256 -120
- package/dist/index.d.cts +697 -229
- package/dist/index.d.ts +697 -229
- package/dist/index.js +233 -103
- package/package.json +7 -8
- package/src/AppsInTossModule/constants.ts +6 -0
- package/src/AppsInTossModule/native-event-emitter/appsInTossEvent.ts +13 -0
- package/src/AppsInTossModule/native-event-emitter/contactsViral.ts +140 -0
- package/src/AppsInTossModule/native-event-emitter/event-plugins/EntryMessageExitedEvent.ts +10 -0
- package/src/AppsInTossModule/native-event-emitter/event-plugins/UpdateLocationEvent.ts +60 -0
- package/src/AppsInTossModule/native-event-emitter/index.ts +5 -0
- package/src/AppsInTossModule/native-event-emitter/internal/AppBridgeCallbackEvent.ts +45 -0
- package/src/AppsInTossModule/native-event-emitter/internal/VisibilityChangedByTransparentServiceWebEvent.ts +50 -0
- package/src/AppsInTossModule/native-event-emitter/internal/appBridge.spec.ts +135 -0
- package/src/AppsInTossModule/native-event-emitter/internal/appBridge.ts +79 -0
- package/src/AppsInTossModule/native-event-emitter/internal/onVisibilityChangedByTransparentServiceWeb.ts +20 -0
- package/src/AppsInTossModule/native-event-emitter/nativeEventEmitter.ts +35 -0
- package/src/AppsInTossModule/native-event-emitter/startUpdateLocation.ts +98 -0
- package/src/AppsInTossModule/native-event-emitter/types.ts +4 -0
- package/src/AppsInTossModule/native-modules/AppsInTossModule.ts +89 -0
- package/src/AppsInTossModule/native-modules/ads/googleAdMob.ts +690 -0
- package/src/AppsInTossModule/native-modules/ads/types.ts +106 -0
- package/src/AppsInTossModule/native-modules/appLogin.ts +29 -0
- package/src/AppsInTossModule/native-modules/checkoutPayment.ts +80 -0
- package/src/AppsInTossModule/native-modules/eventLog.spec.ts +300 -0
- package/src/AppsInTossModule/native-modules/eventLog.ts +77 -0
- package/src/AppsInTossModule/native-modules/fetchAlbumPhotos.ts +88 -0
- package/src/AppsInTossModule/native-modules/fetchContacts.ts +121 -0
- package/src/AppsInTossModule/native-modules/getClipboardText.ts +47 -0
- package/src/AppsInTossModule/native-modules/getCurrentLocation.ts +65 -0
- package/src/AppsInTossModule/native-modules/getDeviceId.ts +33 -0
- package/src/AppsInTossModule/native-modules/getGameCenterGameProfile.ts +68 -0
- package/src/AppsInTossModule/native-modules/getOperationalEnvironment.ts +37 -0
- package/src/AppsInTossModule/native-modules/getPermission.ts +58 -0
- package/src/AppsInTossModule/native-modules/getTossAppVersion.ts +33 -0
- package/src/AppsInTossModule/native-modules/getTossShareLink.ts +39 -0
- package/src/AppsInTossModule/native-modules/iap.ts +213 -0
- package/src/AppsInTossModule/native-modules/index.ts +86 -0
- package/src/AppsInTossModule/native-modules/isMinVersionSupported.spec.ts +190 -0
- package/src/AppsInTossModule/native-modules/isMinVersionSupported.ts +68 -0
- package/src/AppsInTossModule/native-modules/openCamera.ts +81 -0
- package/src/AppsInTossModule/native-modules/openGameCenterLeaderboard.ts +44 -0
- package/src/AppsInTossModule/native-modules/openPermissionDialog.ts +54 -0
- package/src/AppsInTossModule/native-modules/requestPermission.ts +63 -0
- package/src/AppsInTossModule/native-modules/saveBase64Data.ts +57 -0
- package/src/AppsInTossModule/native-modules/setClipboardText.ts +39 -0
- package/src/AppsInTossModule/native-modules/setDeviceOrientation.ts +74 -0
- package/src/AppsInTossModule/native-modules/storage.ts +100 -0
- package/src/AppsInTossModule/native-modules/submitGameCenterLeaderBoardScore.ts +74 -0
- package/src/AppsInTossModule/native-modules/tossCore.ts +29 -0
- package/src/BedrockModule/native-modules/core/BedrockCoreModule.ts +8 -0
- package/src/BedrockModule/native-modules/index.ts +4 -0
- package/src/BedrockModule/native-modules/natives/BedrockModule.ts +20 -0
- package/src/BedrockModule/native-modules/natives/closeView.ts +25 -0
- package/src/BedrockModule/native-modules/natives/generateHapticFeedback/index.ts +27 -0
- package/src/BedrockModule/native-modules/natives/generateHapticFeedback/types.ts +38 -0
- package/src/BedrockModule/native-modules/natives/getLocale.ts +46 -0
- package/src/BedrockModule/native-modules/natives/getNetworkStatus/index.ts +59 -0
- package/src/BedrockModule/native-modules/natives/getNetworkStatus/types.ts +1 -0
- package/src/BedrockModule/native-modules/natives/getPlatformOS.ts +37 -0
- package/src/BedrockModule/native-modules/natives/getSchemeUri.ts +27 -0
- package/src/BedrockModule/native-modules/natives/index.ts +11 -0
- package/src/BedrockModule/native-modules/natives/openURL.ts +40 -0
- package/src/BedrockModule/native-modules/natives/setIosSwipeGestureEnabled.ts +43 -0
- package/src/BedrockModule/native-modules/natives/setScreenAwakeMode.ts +66 -0
- package/src/BedrockModule/native-modules/natives/setSecureScreen.ts +31 -0
- package/src/BedrockModule/native-modules/natives/share.ts +36 -0
- package/src/async-bridges.ts +3 -0
- package/src/event-bridges.ts +2 -0
- package/src/index.ts +16 -0
- package/src/types.ts +108 -0
- package/src/utils/compareVersion.spec.ts +176 -0
- package/src/utils/compareVersion.ts +104 -0
- package/src/utils/generateUUID.ts +5 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { GraniteEventDefinition } from '@granite-js/react-native';
|
|
2
|
+
import { Accuracy, Location } from '../../../types';
|
|
3
|
+
import { AppsInTossModuleInstance } from '../../native-modules/AppsInTossModule';
|
|
4
|
+
import { requestPermission } from '../../native-modules/requestPermission';
|
|
5
|
+
import { nativeEventEmitter } from '../nativeEventEmitter';
|
|
6
|
+
|
|
7
|
+
export interface StartUpdateLocationOptions {
|
|
8
|
+
/**
|
|
9
|
+
* 위치 정확도를 설정해요.
|
|
10
|
+
*/
|
|
11
|
+
accuracy: Accuracy;
|
|
12
|
+
/**
|
|
13
|
+
* 위치 업데이트 주기를 밀리초(ms) 단위로 설정해요.
|
|
14
|
+
*/
|
|
15
|
+
timeInterval: number;
|
|
16
|
+
/**
|
|
17
|
+
* 위치 변경 거리를 미터(m) 단위로 설정해요.
|
|
18
|
+
*/
|
|
19
|
+
distanceInterval: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class UpdateLocationEvent extends GraniteEventDefinition<StartUpdateLocationOptions, Location> {
|
|
23
|
+
name = 'updateLocationEvent' as const;
|
|
24
|
+
|
|
25
|
+
subscriptionCount = 0;
|
|
26
|
+
|
|
27
|
+
ref = {
|
|
28
|
+
remove: () => {},
|
|
29
|
+
};
|
|
30
|
+
remove() {
|
|
31
|
+
if (--this.subscriptionCount === 0) {
|
|
32
|
+
AppsInTossModuleInstance.stopUpdateLocation({});
|
|
33
|
+
}
|
|
34
|
+
this.ref.remove();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
listener(
|
|
38
|
+
options: StartUpdateLocationOptions,
|
|
39
|
+
onEvent: (response: Location) => void,
|
|
40
|
+
onError: (error: unknown) => void
|
|
41
|
+
): void {
|
|
42
|
+
requestPermission({ name: 'geolocation', access: 'access' })
|
|
43
|
+
.then((permissionStatus) => {
|
|
44
|
+
if (permissionStatus === 'denied') {
|
|
45
|
+
onError(new Error('위치 권한이 거부되었어요.'));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// @internal
|
|
50
|
+
void AppsInTossModuleInstance.startUpdateLocation(options).catch(onError);
|
|
51
|
+
const subscription = nativeEventEmitter.addListener('updateLocation', onEvent);
|
|
52
|
+
|
|
53
|
+
this.ref = {
|
|
54
|
+
remove: () => subscription?.remove(),
|
|
55
|
+
};
|
|
56
|
+
this.subscriptionCount++;
|
|
57
|
+
})
|
|
58
|
+
.catch(onError);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/naming-convention */
|
|
2
|
+
import { GraniteEventDefinition } from '@granite-js/react-native';
|
|
3
|
+
import { EmitterSubscription, NativeEventEmitter } from 'react-native';
|
|
4
|
+
import { INTERNAL__appBridgeHandler } from './appBridge';
|
|
5
|
+
import { nativeEventEmitter } from '../nativeEventEmitter';
|
|
6
|
+
|
|
7
|
+
export interface AppBridgeCallbackResult {
|
|
8
|
+
name: string;
|
|
9
|
+
params?: any;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const UNSAFE__nativeEventEmitter = nativeEventEmitter as unknown as NativeEventEmitter;
|
|
13
|
+
|
|
14
|
+
export class AppBridgeCallbackEvent extends GraniteEventDefinition<void, AppBridgeCallbackResult> {
|
|
15
|
+
private static INTERNAL__appBridgeSubscription?: EmitterSubscription;
|
|
16
|
+
|
|
17
|
+
name = 'appBridgeCallbackEvent' as const;
|
|
18
|
+
|
|
19
|
+
constructor() {
|
|
20
|
+
super();
|
|
21
|
+
this.registerAppBridgeCallbackEventListener();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
remove() {}
|
|
25
|
+
listener() {}
|
|
26
|
+
|
|
27
|
+
private registerAppBridgeCallbackEventListener() {
|
|
28
|
+
if (AppBridgeCallbackEvent.INTERNAL__appBridgeSubscription != null) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
AppBridgeCallbackEvent.INTERNAL__appBridgeSubscription = UNSAFE__nativeEventEmitter.addListener(
|
|
33
|
+
'appBridgeCallback',
|
|
34
|
+
this.ensureInvokeAppBridgeCallback
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private ensureInvokeAppBridgeCallback(result: AppBridgeCallbackResult) {
|
|
39
|
+
if (typeof result === 'object' && typeof result.name === 'string') {
|
|
40
|
+
INTERNAL__appBridgeHandler.invokeAppBridgeCallback(result.name, result.params);
|
|
41
|
+
} else {
|
|
42
|
+
console.warn('Invalid app bridge callback result:', result);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { GraniteEventDefinition } from '@granite-js/react-native';
|
|
2
|
+
import type { EmitterSubscription } from 'react-native';
|
|
3
|
+
import { nativeEventEmitter } from '../nativeEventEmitter';
|
|
4
|
+
|
|
5
|
+
export interface VisibilityChangedByTransparentServiceWebOptions {
|
|
6
|
+
callbackId: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface VisibilityChangedByTransparentServiceWebResult {
|
|
10
|
+
callbackId: string;
|
|
11
|
+
isVisible: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class VisibilityChangedByTransparentServiceWebEvent extends GraniteEventDefinition<
|
|
15
|
+
VisibilityChangedByTransparentServiceWebOptions,
|
|
16
|
+
boolean
|
|
17
|
+
> {
|
|
18
|
+
name = 'onVisibilityChangedByTransparentServiceWeb' as const;
|
|
19
|
+
|
|
20
|
+
subscription: EmitterSubscription | null = null;
|
|
21
|
+
|
|
22
|
+
remove() {
|
|
23
|
+
this.subscription?.remove();
|
|
24
|
+
this.subscription = null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
listener(
|
|
28
|
+
options: VisibilityChangedByTransparentServiceWebOptions,
|
|
29
|
+
onEvent: (isVisible: boolean) => void,
|
|
30
|
+
onError: (error: unknown) => void
|
|
31
|
+
) {
|
|
32
|
+
const subscription = nativeEventEmitter.addListener('visibilityChangedByTransparentServiceWeb', (params) => {
|
|
33
|
+
if (this.isVisibilityChangedByTransparentServiceWebResult(params)) {
|
|
34
|
+
if (params.callbackId === options.callbackId) {
|
|
35
|
+
onEvent(params.isVisible);
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
onError(new Error('Invalid visibility changed by transparent service web result'));
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
this.subscription = subscription;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private isVisibilityChangedByTransparentServiceWebResult(
|
|
46
|
+
params: any
|
|
47
|
+
): params is VisibilityChangedByTransparentServiceWebResult {
|
|
48
|
+
return typeof params === 'object' && typeof params.callbackId === 'string' && typeof params.isVisible === 'boolean';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest';
|
|
2
|
+
import { INTERNAL__appBridgeHandler } from './appBridge.js';
|
|
3
|
+
|
|
4
|
+
const mocks = vi.hoisted(() => {
|
|
5
|
+
const mockedNativeMethodName = 'mockedNativeMethod';
|
|
6
|
+
const mockedReturnValue = new Date().getTime();
|
|
7
|
+
const mockedNativeMethod = vi.fn().mockImplementation(async () => ({ value: mockedReturnValue }));
|
|
8
|
+
|
|
9
|
+
return { mockedNativeMethodName, mockedReturnValue, mockedNativeMethod };
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
vi.mock('react-native', () => {
|
|
13
|
+
return {
|
|
14
|
+
NativeModules: {
|
|
15
|
+
AppsInTossModule: {
|
|
16
|
+
[mocks.mockedNativeMethodName]: mocks.mockedNativeMethod,
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('appBridge', () => {
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
vi.clearAllMocks();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('invokeAppBridgeMethod', () => {
|
|
28
|
+
it('should register callbacks', async () => {
|
|
29
|
+
await new Promise((resolve, reject) => {
|
|
30
|
+
INTERNAL__appBridgeHandler.invokeAppBridgeMethod(
|
|
31
|
+
mocks.mockedNativeMethodName,
|
|
32
|
+
{},
|
|
33
|
+
{
|
|
34
|
+
onSuccess: resolve,
|
|
35
|
+
onError: reject,
|
|
36
|
+
onFoo: () => {},
|
|
37
|
+
onBar: () => {},
|
|
38
|
+
onBaz: () => {},
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// `onSuccess` and `onError` aren't registered as callbacks
|
|
44
|
+
// because they are used as the resolve and reject of the promise.
|
|
45
|
+
//
|
|
46
|
+
// `onFoo`, `onBar`, `onBaz` are registered as callbacks (length = 3)
|
|
47
|
+
expect(INTERNAL__appBridgeHandler.getCallbackIds()).toHaveLength(3);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should invoke the native method with the correct parameters', async () => {
|
|
51
|
+
const task = new Promise((resolve, reject) => {
|
|
52
|
+
INTERNAL__appBridgeHandler.invokeAppBridgeMethod(
|
|
53
|
+
mocks.mockedNativeMethodName,
|
|
54
|
+
{
|
|
55
|
+
value_1: 1,
|
|
56
|
+
value_2: 2,
|
|
57
|
+
value_3: 3,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
onSuccess: resolve,
|
|
61
|
+
onError: reject,
|
|
62
|
+
onFoo: () => {},
|
|
63
|
+
onBar: () => {},
|
|
64
|
+
onBaz: () => {},
|
|
65
|
+
}
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
await expect(task).resolves.toEqual({ value: mocks.mockedReturnValue });
|
|
70
|
+
expect(mocks.mockedNativeMethod).toHaveBeenCalledTimes(1);
|
|
71
|
+
expect(mocks.mockedNativeMethod).toHaveBeenCalledWith({
|
|
72
|
+
params: {
|
|
73
|
+
value_1: 1,
|
|
74
|
+
value_2: 2,
|
|
75
|
+
value_3: 3,
|
|
76
|
+
},
|
|
77
|
+
callbacks: {
|
|
78
|
+
// Callbacks are replaced with their unique IDs
|
|
79
|
+
onFoo: expect.any(String),
|
|
80
|
+
onBar: expect.any(String),
|
|
81
|
+
onBaz: expect.any(String),
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('when the native method is rejected, the error callback is called', async () => {
|
|
87
|
+
mocks.mockedNativeMethod.mockImplementationOnce(() => Promise.reject(new Error('Mocked error')));
|
|
88
|
+
|
|
89
|
+
const task = new Promise((resolve, reject) => {
|
|
90
|
+
INTERNAL__appBridgeHandler.invokeAppBridgeMethod(
|
|
91
|
+
mocks.mockedNativeMethodName,
|
|
92
|
+
{},
|
|
93
|
+
{
|
|
94
|
+
onSuccess: resolve,
|
|
95
|
+
onError: reject,
|
|
96
|
+
}
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
await expect(task).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: Mocked error]`);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('invokeAppBridgeCallback', () => {
|
|
105
|
+
const CALLBACK_NAME = 'onTest';
|
|
106
|
+
let mockedCallback: Mock;
|
|
107
|
+
|
|
108
|
+
// Register a callback before invoking the callback by its ID.
|
|
109
|
+
beforeEach(() => {
|
|
110
|
+
mockedCallback = vi.fn();
|
|
111
|
+
|
|
112
|
+
return new Promise<void>((resolve, reject) => {
|
|
113
|
+
INTERNAL__appBridgeHandler.invokeAppBridgeMethod(
|
|
114
|
+
mocks.mockedNativeMethodName,
|
|
115
|
+
{},
|
|
116
|
+
{
|
|
117
|
+
onSuccess: resolve,
|
|
118
|
+
onError: reject,
|
|
119
|
+
[CALLBACK_NAME]: mockedCallback,
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should invoke the callback with the correct parameters', async () => {
|
|
126
|
+
const callbackId = INTERNAL__appBridgeHandler.getCallbackIds().pop()!;
|
|
127
|
+
const mockedValue = new Date().getTime();
|
|
128
|
+
|
|
129
|
+
INTERNAL__appBridgeHandler.invokeAppBridgeCallback(callbackId, { value: mockedValue });
|
|
130
|
+
|
|
131
|
+
expect(mockedCallback).toHaveBeenCalledTimes(1);
|
|
132
|
+
expect(mockedCallback).toHaveBeenCalledWith({ value: mockedValue });
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/naming-convention */
|
|
2
|
+
import { generateUUID } from '../../../utils/generateUUID';
|
|
3
|
+
import { AppsInTossModuleInstance } from '../../native-modules/AppsInTossModule';
|
|
4
|
+
|
|
5
|
+
export interface AppBridgeCompatCallbacks<Result> {
|
|
6
|
+
onSuccess: (result: Result) => void;
|
|
7
|
+
onError: (reason: unknown) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
type AppBridgeCallback = (...args: any[]) => void;
|
|
11
|
+
type AppBridgeCallbackId = string;
|
|
12
|
+
|
|
13
|
+
const INTERNAL__callbacks = new Map<AppBridgeCallbackId, AppBridgeCallback>();
|
|
14
|
+
|
|
15
|
+
function invokeAppBridgeCallback(id: string, ...args: any[]): boolean {
|
|
16
|
+
const callback = INTERNAL__callbacks.get(id);
|
|
17
|
+
|
|
18
|
+
callback?.call(null, ...args);
|
|
19
|
+
|
|
20
|
+
return Boolean(callback);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function invokeAppBridgeMethod<Result = any, Params = any>(
|
|
24
|
+
methodName: string,
|
|
25
|
+
params: Params,
|
|
26
|
+
callbacks: AppBridgeCompatCallbacks<Result> & Record<string, AppBridgeCallback>
|
|
27
|
+
) {
|
|
28
|
+
const { onSuccess, onError, ...appBridgeCallbacks } = callbacks;
|
|
29
|
+
const { callbackMap, unregisterAll } = registerCallbacks(appBridgeCallbacks);
|
|
30
|
+
|
|
31
|
+
const promise = AppsInTossModuleInstance[methodName]({
|
|
32
|
+
params,
|
|
33
|
+
callbacks: callbackMap,
|
|
34
|
+
}) as Promise<Result>;
|
|
35
|
+
|
|
36
|
+
void promise.then(onSuccess).catch(onError);
|
|
37
|
+
|
|
38
|
+
return unregisterAll;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function registerCallbacks(callbacks: Record<string, AppBridgeCallback>) {
|
|
42
|
+
const callbackMap: Record<string, AppBridgeCallbackId> = {};
|
|
43
|
+
|
|
44
|
+
for (const [callbackName, callback] of Object.entries(callbacks)) {
|
|
45
|
+
const id = registerCallback(callback, callbackName);
|
|
46
|
+
callbackMap[callbackName] = id;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const unregisterAll = () => {
|
|
50
|
+
Object.values(callbackMap).forEach(unregisterCallback);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return { callbackMap, unregisterAll };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function registerCallback(callback: AppBridgeCallback, name = 'unnamed') {
|
|
57
|
+
const uniqueId = generateUUID();
|
|
58
|
+
const callbackId = `${uniqueId}__${name}`;
|
|
59
|
+
|
|
60
|
+
INTERNAL__callbacks.set(callbackId, callback);
|
|
61
|
+
|
|
62
|
+
return callbackId;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function unregisterCallback(id: string) {
|
|
66
|
+
INTERNAL__callbacks.delete(id);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function getCallbackIds() {
|
|
70
|
+
return Array.from(INTERNAL__callbacks.keys());
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const INTERNAL__appBridgeHandler = {
|
|
74
|
+
invokeAppBridgeCallback,
|
|
75
|
+
invokeAppBridgeMethod,
|
|
76
|
+
registerCallback,
|
|
77
|
+
unregisterCallback,
|
|
78
|
+
getCallbackIds,
|
|
79
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { EmitterSubscription } from 'react-native';
|
|
2
|
+
import { appsInTossEvent } from '../appsInTossEvent';
|
|
3
|
+
import type { EventEmitterSchema } from '../types';
|
|
4
|
+
|
|
5
|
+
export interface OnVisibilityChangedByTransparentServiceWebSubscription extends EmitterSubscription {
|
|
6
|
+
remove: () => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type OnVisibilityChangedByTransparentServiceWebEventEmitter = EventEmitterSchema<
|
|
10
|
+
'visibilityChangedByTransparentServiceWeb',
|
|
11
|
+
[boolean]
|
|
12
|
+
>;
|
|
13
|
+
|
|
14
|
+
export function onVisibilityChangedByTransparentServiceWeb(eventParams: {
|
|
15
|
+
options: { callbackId: string };
|
|
16
|
+
onEvent: (isVisible: boolean) => void;
|
|
17
|
+
onError: (error: unknown) => void;
|
|
18
|
+
}): () => void {
|
|
19
|
+
return appsInTossEvent.addEventListener('onVisibilityChangedByTransparentServiceWeb', eventParams);
|
|
20
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { NativeEventEmitter, type EmitterSubscription } from 'react-native';
|
|
2
|
+
import type { OnVisibilityChangedByTransparentServiceWebEventEmitter } from './internal/onVisibilityChangedByTransparentServiceWeb';
|
|
3
|
+
import type { UpdateLocationEventEmitter } from './startUpdateLocation';
|
|
4
|
+
import type { EventEmitterSchema } from './types';
|
|
5
|
+
import { AppsInTossModuleInstance } from '../native-modules/AppsInTossModule';
|
|
6
|
+
|
|
7
|
+
type EventEmitters = UpdateLocationEventEmitter | OnVisibilityChangedByTransparentServiceWebEventEmitter;
|
|
8
|
+
|
|
9
|
+
type MapOf<T> = T extends EventEmitterSchema<infer K, any> ? { [key in K]: T } : never;
|
|
10
|
+
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
|
|
11
|
+
type EventEmittersMap = UnionToIntersection<MapOf<EventEmitters>>;
|
|
12
|
+
type EventKeys = keyof EventEmittersMap;
|
|
13
|
+
type ParamOf<K extends EventKeys> = EventEmittersMap[K]['params'];
|
|
14
|
+
/**
|
|
15
|
+
* @interface AppsInTossEventEmitter
|
|
16
|
+
* @description
|
|
17
|
+
* 네이티브 플랫폼에서 발생하는 이벤트들을 처리하는 NativeEventEmitter를 App In Toss 프레임워크에서 사용하는 형태에 맞게 정의한 인터페이스에요.
|
|
18
|
+
* @property {(event: EventKeys, callback: (...params: ParamOf<E>) => void) => EmitterSubscription} addListener - 이벤트 리스너를 추가하는 함수
|
|
19
|
+
* @property {(subscription: EmitterSubscription) => void} removeSubscription - 이벤트 구독을 제거하는 함수
|
|
20
|
+
*/
|
|
21
|
+
interface AppsInTossEventEmitter {
|
|
22
|
+
addListener<Event extends EventKeys>(
|
|
23
|
+
event: Event,
|
|
24
|
+
callback: (...params: ParamOf<Event>) => void
|
|
25
|
+
): EmitterSubscription;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @kind constant
|
|
30
|
+
* @name nativeEventEmitter
|
|
31
|
+
* @description
|
|
32
|
+
* App In Toss 프레임워크에서 제공하는 react-native의 NativeEventEmitter instance에요.
|
|
33
|
+
* @type {AppsInTossEventEmitter}
|
|
34
|
+
*/
|
|
35
|
+
export const nativeEventEmitter = new NativeEventEmitter(AppsInTossModuleInstance) as unknown as AppsInTossEventEmitter;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { EmitterSubscription } from 'react-native';
|
|
2
|
+
import { appsInTossEvent } from './appsInTossEvent';
|
|
3
|
+
import type { EventEmitterSchema } from './types';
|
|
4
|
+
import type { Accuracy, Location } from '../../types';
|
|
5
|
+
|
|
6
|
+
export interface StartUpdateLocationOptions {
|
|
7
|
+
/**
|
|
8
|
+
* 위치 정확도를 설정해요.
|
|
9
|
+
*/
|
|
10
|
+
accuracy: Accuracy;
|
|
11
|
+
/**
|
|
12
|
+
* 위치 업데이트 주기를 밀리초(ms) 단위로 설정해요.
|
|
13
|
+
*/
|
|
14
|
+
timeInterval: number;
|
|
15
|
+
/**
|
|
16
|
+
* 위치 변경 거리를 미터(m) 단위로 설정해요.
|
|
17
|
+
*/
|
|
18
|
+
distanceInterval: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface StartUpdateLocationSubscription extends EmitterSubscription {
|
|
22
|
+
remove: () => Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @name UpdateLocationEventEmitter
|
|
27
|
+
* @kind typedef
|
|
28
|
+
* @description
|
|
29
|
+
* 디바이스의 위치 정보 변경을 감지해요
|
|
30
|
+
*/
|
|
31
|
+
export type UpdateLocationEventEmitter = EventEmitterSchema<'updateLocation', [Location]>;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @public
|
|
35
|
+
* @category 위치 정보
|
|
36
|
+
* @name startUpdateLocation
|
|
37
|
+
* @description 디바이스의 위치 정보를 지속적으로 감지하고, 위치가 변경되면 콜백을 실행하는 함수예요. 콜백 함수를 등록하면 위치가 변경될 때마다 자동으로 호출돼요.
|
|
38
|
+
* 실시간 위치 추적이 필요한 기능을 구현할 때 사용할 수 있어요. 예를 들어 지도 앱에서 사용자의 현재 위치를 실시간으로 업데이트할 때, 운동 앱에서 사용자의 이동 거리를 기록할 때 등이에요.
|
|
39
|
+
* 위치 업데이트 주기와 정확도를 조정해 배터리 소모를 최소화하면서도 필요한 정보를 얻을 수 있어요.
|
|
40
|
+
*
|
|
41
|
+
* @param {StartUpdateLocationOptions} options - 위치 정보 감지에 필요한 설정 객체에요.
|
|
42
|
+
* @param {number} [options.accuracy] 위치 정확도를 설정해요.
|
|
43
|
+
* @param {number} [options.timeInterval] 위치 정보를 업데이트하는 최소 주기로, 단위는 밀리초(ms)예요. 이 값은 위치 업데이트가 발생하는 가장 짧은 간격을 설정하지만, 시스템이나 환경의 영향을 받아 지정한 주기보다 더 긴 간격으로 업데이트될 수 있어요.
|
|
44
|
+
* @param {number} [options.distanceInterval] 위치 변경 거리를 미터(m) 단위로 설정해요.
|
|
45
|
+
* @param {(location: Location) => void} [options.callback] 위치 정보가 변경될 때 호출되는 콜백 함수예요. 자세한 내용은 [Location](/react-native/reference/native-modules/Types/Location.html)을 참고해주세요.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ### 위치 정보 변경 감지하기
|
|
49
|
+
*
|
|
50
|
+
* ```tsx
|
|
51
|
+
* import React, { useState, useEffect } from 'react';
|
|
52
|
+
* import { View, Text, Button } from 'react-native';
|
|
53
|
+
* import { startUpdateLocation } from '@apps-in-toss/framework';
|
|
54
|
+
*
|
|
55
|
+
* // 위치 정보 변경 감지하기
|
|
56
|
+
* function LocationWatcher() {
|
|
57
|
+
* const [location, setLocation] = useState(null);
|
|
58
|
+
*
|
|
59
|
+
* useEffect(() => {
|
|
60
|
+
* return startUpdateLocation({
|
|
61
|
+
* options: {
|
|
62
|
+
* accuracy: Accuracy.Balanced,
|
|
63
|
+
* timeInterval: 3000,
|
|
64
|
+
* distanceInterval: 10,
|
|
65
|
+
* },
|
|
66
|
+
* onEvent: (location) => {
|
|
67
|
+
* setLocation(location);
|
|
68
|
+
* },
|
|
69
|
+
* onError: (error) => {
|
|
70
|
+
* console.error('위치 정보를 가져오는데 실패했어요:', error);
|
|
71
|
+
* },
|
|
72
|
+
* });
|
|
73
|
+
* }, []);
|
|
74
|
+
*
|
|
75
|
+
* if (location == null) {
|
|
76
|
+
* return <Text>위치 정보를 가져오는 중이에요...</Text>;
|
|
77
|
+
* }
|
|
78
|
+
*
|
|
79
|
+
* return (
|
|
80
|
+
* <View>
|
|
81
|
+
* <Text>위도: {location.coords.latitude}</Text>
|
|
82
|
+
* <Text>경도: {location.coords.longitude}</Text>
|
|
83
|
+
* <Text>위치 정확도: {location.coords.accuracy}m</Text>
|
|
84
|
+
* <Text>높이: {location.coords.altitude}m</Text>
|
|
85
|
+
* <Text>고도 정확도: {location.coords.altitudeAccuracy}m</Text>
|
|
86
|
+
* <Text>방향: {location.coords.heading}°</Text>
|
|
87
|
+
* </View>
|
|
88
|
+
* );
|
|
89
|
+
* }
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
export function startUpdateLocation(eventParams: {
|
|
93
|
+
onEvent: (response: Location) => void;
|
|
94
|
+
onError: (error: unknown) => void;
|
|
95
|
+
options: StartUpdateLocationOptions;
|
|
96
|
+
}): () => void {
|
|
97
|
+
return appsInTossEvent.addEventListener('updateLocationEvent', eventParams);
|
|
98
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { TurboModuleRegistry, type TurboModule as __TurboModule } from 'react-native';
|
|
2
|
+
import type { CheckoutPaymentOptions, CheckoutPaymentResult } from './checkoutPayment';
|
|
3
|
+
import type { FetchAlbumPhotosOptions } from './fetchAlbumPhotos';
|
|
4
|
+
import type { ContactEntity } from './fetchContacts';
|
|
5
|
+
import type { GetCurrentLocationOptions } from './getCurrentLocation';
|
|
6
|
+
import type { GameCenterGameProfileResponse } from './getGameCenterGameProfile';
|
|
7
|
+
import type {
|
|
8
|
+
IapCreateOneTimePurchaseOrderOptions,
|
|
9
|
+
IapCreateOneTimePurchaseOrderResult,
|
|
10
|
+
IapProductListItem,
|
|
11
|
+
} from './iap';
|
|
12
|
+
import type { OpenCameraOptions } from './openCamera';
|
|
13
|
+
import type { SaveBase64DataParams } from './saveBase64Data';
|
|
14
|
+
import type { SubmitGameCenterLeaderBoardScoreResponse } from './submitGameCenterLeaderBoardScore';
|
|
15
|
+
import type { ImageResponse, Location, PermissionAccess, PermissionName, PermissionStatus } from '../../types';
|
|
16
|
+
import type { ContactsViralParams } from '../native-event-emitter/contactsViral';
|
|
17
|
+
|
|
18
|
+
type CompatiblePlaceholderArgument = object;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* TurboModule 타입 별칭 사용하는 이유?
|
|
22
|
+
* React Native Codegen 에 의해 코드젠 되는 것이 아니라 추후 내부 모듈 체계에 의해 처리될 것이기 때문에 RN Codegen에 본 파일을 코드젠 하지 않도록 함
|
|
23
|
+
* (코드젠 내부에서 "extends TurboModule" 문자열을 찾기 때문에 패턴에 매칭되지 않도록 함)
|
|
24
|
+
*/
|
|
25
|
+
interface Spec extends __TurboModule {
|
|
26
|
+
groupId: string;
|
|
27
|
+
operationalEnvironment: 'sandbox' | 'toss';
|
|
28
|
+
tossAppVersion: string;
|
|
29
|
+
deviceId: string;
|
|
30
|
+
getWebBundleURL: (arg: CompatiblePlaceholderArgument) => { url: string };
|
|
31
|
+
getClipboardText: (arg: CompatiblePlaceholderArgument) => Promise<string>;
|
|
32
|
+
setClipboardText: (option: { text: string }) => Promise<void>;
|
|
33
|
+
getPermission: (permission: { name: PermissionName; access: PermissionAccess }) => Promise<PermissionStatus>;
|
|
34
|
+
openPermissionDialog: (permission: {
|
|
35
|
+
name: PermissionName;
|
|
36
|
+
access: PermissionAccess;
|
|
37
|
+
}) => Promise<Exclude<PermissionStatus, 'notDetermined'>>;
|
|
38
|
+
fetchContacts: ({
|
|
39
|
+
size,
|
|
40
|
+
offset,
|
|
41
|
+
query,
|
|
42
|
+
}: {
|
|
43
|
+
size: number;
|
|
44
|
+
offset: number;
|
|
45
|
+
query?: {
|
|
46
|
+
contains?: string;
|
|
47
|
+
};
|
|
48
|
+
}) => Promise<{
|
|
49
|
+
result: ContactEntity[];
|
|
50
|
+
nextOffset: number | undefined;
|
|
51
|
+
done: boolean;
|
|
52
|
+
}>;
|
|
53
|
+
fetchAlbumPhotos: (options: FetchAlbumPhotosOptions) => Promise<ImageResponse[]>;
|
|
54
|
+
getCurrentLocation: (options: GetCurrentLocationOptions) => Promise<Location>;
|
|
55
|
+
openCamera: (options: OpenCameraOptions) => Promise<ImageResponse>;
|
|
56
|
+
appLogin: (
|
|
57
|
+
arg: CompatiblePlaceholderArgument
|
|
58
|
+
) => Promise<{ authorizationCode: string; referrer: 'DEFAULT' | 'SANDBOX' }>;
|
|
59
|
+
checkoutPayment: (options: { params: CheckoutPaymentOptions }) => Promise<CheckoutPaymentResult>;
|
|
60
|
+
|
|
61
|
+
/** Storage */
|
|
62
|
+
getStorageItem: (params: { key: string }) => Promise<string | null>;
|
|
63
|
+
setStorageItem: (params: { key: string; value: string }) => Promise<void>;
|
|
64
|
+
removeStorageItem: (params: { key: string }) => Promise<void>;
|
|
65
|
+
clearStorage: (arg: CompatiblePlaceholderArgument) => Promise<void>;
|
|
66
|
+
eventLog: (params: {
|
|
67
|
+
log_name: string;
|
|
68
|
+
log_type: 'debug' | 'info' | 'warn' | 'error' | 'screen' | 'impression' | 'click';
|
|
69
|
+
params: Record<string, string>;
|
|
70
|
+
}) => Promise<void>;
|
|
71
|
+
getTossShareLink: (params: object) => Promise<{ shareLink: string }>;
|
|
72
|
+
setDeviceOrientation: (options: { type: 'portrait' | 'landscape' }) => Promise<void>;
|
|
73
|
+
saveBase64Data: (params: SaveBase64DataParams) => Promise<void>;
|
|
74
|
+
|
|
75
|
+
/** IAP */
|
|
76
|
+
iapCreateOneTimePurchaseOrder: (
|
|
77
|
+
params: IapCreateOneTimePurchaseOrderOptions
|
|
78
|
+
) => Promise<IapCreateOneTimePurchaseOrderResult>;
|
|
79
|
+
iapGetProductItemList: (arg: CompatiblePlaceholderArgument) => Promise<{ products: IapProductListItem[] }>;
|
|
80
|
+
getGameCenterGameProfile: (params: CompatiblePlaceholderArgument) => Promise<GameCenterGameProfileResponse>;
|
|
81
|
+
submitGameCenterLeaderBoardScore: (params: { score: string }) => Promise<SubmitGameCenterLeaderBoardScoreResponse>;
|
|
82
|
+
|
|
83
|
+
contactsViral: (params: ContactsViralParams) => () => void;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const Module = TurboModuleRegistry.getEnforcing<Spec>('AppsInTossModule');
|
|
87
|
+
|
|
88
|
+
export const AppsInTossModuleInstance = Module as any;
|
|
89
|
+
export const AppsInTossModule = Module;
|