Skip to content

Commit bbf5cc4

Browse files
committed
WIP - Handle geolocation requests
1 parent 2c068ff commit bbf5cc4

File tree

8 files changed

+298
-113
lines changed

8 files changed

+298
-113
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,8 @@ function App() {
144144
}
145145
```
146146

147-
See [Usage with the Storefront API](#usage-with-the-storefront-api) below on how
148-
to get a checkout URL to pass to the kit.
147+
See [usage with the Storefront API](#usage-with-the-storefront-api) below for details on how
148+
to obtain a checkout URL to pass to the kit.
149149

150150
> [!NOTE]
151151
> The recommended usage of the library is through a

modules/@shopify/checkout-sheet-kit/android/src/main/java/com/shopify/reactnative/checkoutsheetkit/CustomCheckoutEventProcessor.java

Lines changed: 59 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -43,22 +43,58 @@ of this software and associated documentation files (the "Software"), to deal
4343

4444
public class CustomCheckoutEventProcessor extends DefaultCheckoutEventProcessor {
4545
private final ReactApplicationContext reactContext;
46-
4746
private final ObjectMapper mapper = new ObjectMapper();
4847

48+
// Geolocation-specific variables
49+
50+
private String geolocationOrigin;
51+
private GeolocationPermissions.Callback geolocationCallback;
52+
private boolean retainGeolocationForFutureRequests = true;
53+
4954
public CustomCheckoutEventProcessor(Context context, ReactApplicationContext reactContext) {
5055
super(context);
51-
5256
this.reactContext = reactContext;
5357
}
5458

59+
// Public methods
60+
61+
public void invokeGeolocationCallback(boolean allow) {
62+
if (geolocationCallback != null) {
63+
geolocationCallback.invoke(geolocationOrigin, allow, retainGeolocationForFutureRequests);
64+
geolocationCallback = null;
65+
}
66+
}
67+
68+
// Lifecycle events
69+
70+
/**
71+
* This method is called when the checkout sheet webpage requests geolocation
72+
* permissions.
73+
*
74+
* Since the app needs to request permissions first before granting, we store
75+
* the callback and origin in memory and emit a "geolocationRequest" event to
76+
* the app. The app will then request the necessary geolocation permissions
77+
* and invoke the native callback with the result.
78+
*
79+
* @param origin - The origin of the request
80+
* @param callback - The callback to invoke when the app requests permissions
81+
*/
5582
@Override
56-
public void onGeolocationPermissionsShowPrompt(@NonNull String origin, @NonNull GeolocationPermissions.Callback callback) {
83+
public void onGeolocationPermissionsShowPrompt(@NonNull String origin,
84+
@NonNull GeolocationPermissions.Callback callback) {
85+
86+
// Store the callback and origin in memory. The kit will wait for the app to
87+
// request permissions first before granting.
88+
this.geolocationCallback = callback;
89+
this.geolocationOrigin = origin;
90+
91+
// Emit a "geolocationRequest" event to the app.
5792
try {
58-
String data = mapper.writeValueAsString(origin);
59-
sendEventWithStringData("geolocation_permissions_prompt", data);
93+
Map<String, Object> event = new HashMap();
94+
event.put("origin", origin.toString());
95+
sendEventWithStringData("geolocationRequest", mapper.writeValueAsString(event));
6096
} catch (IOException e) {
61-
Log.e("ShopifyCheckoutSheetKit", "Error processing onGeolocationPermissionsShowPrompt", e);
97+
Log.e("ShopifyCheckoutSheetKit", "Error emitting \"geolocationRequest\" event", e);
6298
}
6399
}
64100

@@ -67,16 +103,6 @@ public void onGeolocationPermissionsHidePrompt() {
67103
super.onGeolocationPermissionsHidePrompt();
68104
}
69105

70-
@Override
71-
public void onCheckoutCompleted(@NonNull CheckoutCompletedEvent event) {
72-
try {
73-
String data = mapper.writeValueAsString(event);
74-
sendEventWithStringData("completed", data);
75-
} catch (IOException e) {
76-
Log.e("ShopifyCheckoutSheetKit", "Error processing completed event", e);
77-
}
78-
}
79-
80106
@Override
81107
public void onWebPixelEvent(@NonNull PixelEvent event) {
82108
try {
@@ -97,6 +123,23 @@ public void onCheckoutFailed(CheckoutException checkoutError) {
97123
}
98124
}
99125

126+
@Override
127+
public void onCheckoutCanceled() {
128+
sendEvent("close", null);
129+
}
130+
131+
@Override
132+
public void onCheckoutCompleted(@NonNull CheckoutCompletedEvent event) {
133+
try {
134+
String data = mapper.writeValueAsString(event);
135+
sendEventWithStringData("completed", data);
136+
} catch (IOException e) {
137+
Log.e("ShopifyCheckoutSheetKit", "Error processing completed event", e);
138+
}
139+
}
140+
141+
// Private
142+
100143
private Map<String, Object> populateErrorDetails(CheckoutException checkoutError) {
101144
Map<String, Object> errorMap = new HashMap();
102145
errorMap.put("__typename", getErrorTypeName(checkoutError));
@@ -127,11 +170,6 @@ private String getErrorTypeName(CheckoutException error) {
127170
}
128171
}
129172

130-
@Override
131-
public void onCheckoutCanceled() {
132-
sendEvent("close", null);
133-
}
134-
135173
private void sendEvent(String eventName, @Nullable WritableNativeMap params) {
136174
reactContext
137175
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)

modules/@shopify/checkout-sheet-kit/android/src/main/java/com/shopify/reactnative/checkoutsheetkit/ShopifyCheckoutSheetKitModule.java

Lines changed: 51 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,57 @@ public void invalidateCache() {
117117
ShopifyCheckoutSheetKit.invalidate();
118118
}
119119

120+
@ReactMethod
121+
public void getConfig(Promise promise) {
122+
WritableNativeMap resultConfig = new WritableNativeMap();
123+
124+
resultConfig.putBoolean("preloading", checkoutConfig.getPreloading().getEnabled());
125+
resultConfig.putString("colorScheme", colorSchemeToString(checkoutConfig.getColorScheme()));
126+
127+
promise.resolve(resultConfig);
128+
}
129+
130+
@ReactMethod
131+
public void setConfig(ReadableMap config) {
132+
Context context = getReactApplicationContext();
133+
134+
ShopifyCheckoutSheetKit.configure(configuration -> {
135+
if (config.hasKey("preloading")) {
136+
configuration.setPreloading(new Preloading(config.getBoolean("preloading")));
137+
}
138+
139+
if (config.hasKey("colorScheme")) {
140+
ColorScheme colorScheme = getColorScheme(Objects.requireNonNull(config.getString("colorScheme")));
141+
ReadableMap colorsConfig = config.hasKey("colors") ? config.getMap("colors") : null;
142+
ReadableMap androidConfig = null;
143+
144+
if (colorsConfig != null && colorsConfig.hasKey("android")) {
145+
androidConfig = colorsConfig.getMap("android");
146+
}
147+
148+
if (this.isValidColorConfig(androidConfig)) {
149+
ColorScheme colorSchemeWithOverrides = getColors(colorScheme, androidConfig);
150+
if (colorSchemeWithOverrides != null) {
151+
configuration.setColorScheme(colorSchemeWithOverrides);
152+
checkoutConfig = configuration;
153+
return;
154+
}
155+
}
156+
157+
configuration.setColorScheme(colorScheme);
158+
}
159+
160+
checkoutConfig = configuration;
161+
});
162+
}
163+
164+
@ReactMethod
165+
public void initiateGeolocationRequest(Boolean allow) {
166+
checkoutEventProcessor.invokeGeolocationCallback(allow);
167+
}
168+
169+
// Private
170+
120171
private ColorScheme getColorScheme(String colorScheme) {
121172
switch (colorScheme) {
122173
case "web_default":
@@ -233,50 +284,6 @@ private ColorScheme getColors(ColorScheme colorScheme, ReadableMap config) {
233284
return null;
234285
}
235286

236-
@ReactMethod
237-
public void setConfig(ReadableMap config) {
238-
Context context = getReactApplicationContext();
239-
240-
ShopifyCheckoutSheetKit.configure(configuration -> {
241-
if (config.hasKey("preloading")) {
242-
configuration.setPreloading(new Preloading(config.getBoolean("preloading")));
243-
}
244-
245-
if (config.hasKey("colorScheme")) {
246-
ColorScheme colorScheme = getColorScheme(Objects.requireNonNull(config.getString("colorScheme")));
247-
ReadableMap colorsConfig = config.hasKey("colors") ? config.getMap("colors") : null;
248-
ReadableMap androidConfig = null;
249-
250-
if (colorsConfig != null && colorsConfig.hasKey("android")) {
251-
androidConfig = colorsConfig.getMap("android");
252-
}
253-
254-
if (this.isValidColorConfig(androidConfig)) {
255-
ColorScheme colorSchemeWithOverrides = getColors(colorScheme, androidConfig);
256-
if (colorSchemeWithOverrides != null) {
257-
configuration.setColorScheme(colorSchemeWithOverrides);
258-
checkoutConfig = configuration;
259-
return;
260-
}
261-
}
262-
263-
configuration.setColorScheme(colorScheme);
264-
}
265-
266-
checkoutConfig = configuration;
267-
});
268-
}
269-
270-
@ReactMethod
271-
public void getConfig(Promise promise) {
272-
WritableNativeMap resultConfig = new WritableNativeMap();
273-
274-
resultConfig.putBoolean("preloading", checkoutConfig.getPreloading().getEnabled());
275-
resultConfig.putString("colorScheme", colorSchemeToString(checkoutConfig.getColorScheme()));
276-
277-
promise.resolve(resultConfig);
278-
}
279-
280287
private Color parseColor(String colorStr) {
281288
try {
282289
colorStr = colorStr.replace("#", "");

modules/@shopify/checkout-sheet-kit/src/context.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SO
2323

2424
import React, {useCallback, useMemo, useRef} from 'react';
2525
import type {PropsWithChildren} from 'react';
26-
import type {EmitterSubscription} from 'react-native';
26+
import {type EmitterSubscription} from 'react-native';
2727
import {ShopifyCheckoutSheet} from './index';
28+
import type {Features} from './index.d';
2829
import type {
2930
AddEventListener,
3031
RemoveEventListeners,
@@ -61,17 +62,19 @@ const ShopifyCheckoutSheetContext = React.createContext<Context>({
6162
});
6263

6364
interface Props {
65+
features?: Partial<Features>;
6466
configuration?: Configuration;
6567
}
6668

6769
export function ShopifyCheckoutSheetProvider({
70+
features,
6871
configuration,
6972
children,
7073
}: PropsWithChildren<Props>) {
7174
const instance = useRef<ShopifyCheckoutSheet | null>(null);
7275

7376
if (!instance.current) {
74-
instance.current = new ShopifyCheckoutSheet(configuration);
77+
instance.current = new ShopifyCheckoutSheet(configuration, features);
7578
}
7679

7780
const addEventListener: AddEventListener = useCallback(

modules/@shopify/checkout-sheet-kit/src/index.d.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,18 @@ import type {CheckoutException} from './errors';
2828

2929
export type Maybe<T> = T | undefined;
3030

31+
/**
32+
* Configuration options for checkout sheet kit features
33+
*/
34+
export interface Features {
35+
/**
36+
* When enabled, the checkout will handle geolocation permission requests internally.
37+
* If disabled, geolocation requests will emit a 'geolocationRequest' event that
38+
* must be handled by the application.
39+
*/
40+
handleGeolocationRequests: boolean;
41+
}
42+
3143
export enum ColorScheme {
3244
automatic = 'automatic',
3345
light = 'light',
@@ -127,9 +139,15 @@ export type Configuration = CommonConfiguration &
127139
}
128140
);
129141

130-
export type CheckoutEvent = 'close' | 'completed' | 'error' | 'pixel';
142+
export type CheckoutEvent =
143+
| 'close'
144+
| 'completed'
145+
| 'error'
146+
| 'geolocationRequest'
147+
| 'pixel';
131148

132149
export type CloseEventCallback = () => void;
150+
export type GeolocationRequestEventCallback = () => void;
133151
export type PixelEventCallback = (event: PixelEvent) => void;
134152
export type CheckoutExceptionCallback = (error: CheckoutException) => void;
135153
export type CheckoutCompletedEventCallback = (
@@ -162,6 +180,11 @@ function addEventListener(
162180
callback: PixelEventCallback,
163181
): Maybe<EmitterSubscription>;
164182

183+
function addEventListener(
184+
event: 'geolocationRequest',
185+
callback: GeolocationRequestEventCallback,
186+
): Maybe<EmitterSubscription>;
187+
165188
function removeEventListeners(event: CheckoutEvent): void;
166189

167190
export type AddEventListener = typeof addEventListener;
@@ -198,7 +221,11 @@ export interface ShopifyCheckoutSheetKit {
198221
*/
199222
addEventListener: AddEventListener;
200223
/**
201-
* Remove subscriptions to checkout events
224+
* Remove subscriptions to checkout events.
202225
*/
203226
removeEventListeners: RemoveEventListeners;
227+
/**
228+
* Cleans up any event callbacks to prevent memory leaks.
229+
*/
230+
teardown(): void;
204231
}

0 commit comments

Comments
 (0)