Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions platforms/react-native/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ instance of the `ShopifyCheckout` class.
| `preloading` | | `true` | Enable/disable [preloading](#preloading). |
| `colors` | | `{}` | An object with `ios` and `android` properties to override the colors for iOS and Android platforms individually. See [`colors`](#colors) for more information. |
| `logLevel` | | `error` | Sets the log level for the native SDK. Use `LogLevel.debug` for verbose logging during development, or `LogLevel.error` for production. |
| `allowedMessageOrigins` | | `[]` | Extra origins trusted to send incoming checkout messages. See [Incoming message origin validation](#incoming-message-origin-validation). |

Here's an example of how a fully customized configuration object might look:

Expand Down Expand Up @@ -468,6 +469,29 @@ function AppWithContext() {
}
```

### Incoming message origin validation

Native checkout accepts messages from every origin by default. To restrict
messages, configure one or more exact origins or wildcard subdomains. The
checkout URL's origin and `shop.app` remain trusted automatically.

```tsx
const config: Configuration = {
allowedMessageOrigins: [
'https://checkout.example.com',
'https://*.example.org',
],
};
```

Entries may be exact origins (`https://example.com`), wildcard subdomains
(`https://*.example.com`, matching subdomains but not the apex), or `'*'` to
explicitly disable origin validation.

Messages dropped by origin validation are never silently discarded: the native
SDK logs each rejection as a warning with the message origin and the reason it
was dropped. The message body is untrusted and is not logged.

### Localization

#### Checkout Sheet title
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

public class ShopifyCheckoutKitModule extends NativeShopifyCheckoutKitSpec {

Expand Down Expand Up @@ -62,7 +64,7 @@
public void present(String checkoutURL, ReadableArray subscribedMethods) {
releaseCheckoutListener();

Activity currentActivity = getCurrentActivity();

Check warning on line 67 in platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/main/java/com/shopify/reactnative/checkoutkit/ShopifyCheckoutKitModule.java

View workflow job for this annotation

GitHub Actions / React Native / Build Android Sample

[removal] getCurrentActivity() in ReactContextBaseJavaModule has been deprecated and marked for removal

Check warning on line 67 in platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/main/java/com/shopify/reactnative/checkoutkit/ShopifyCheckoutKitModule.java

View workflow job for this annotation

GitHub Actions / React Native / Run Android Tests

[removal] getCurrentActivity() in ReactContextBaseJavaModule has been deprecated and marked for removal

Check warning on line 67 in platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/main/java/com/shopify/reactnative/checkoutkit/ShopifyCheckoutKitModule.java

View workflow job for this annotation

GitHub Actions / React Native / Run Android Tests

[removal] getCurrentActivity() in ReactContextBaseJavaModule has been deprecated and marked for removal
if (currentActivity instanceof ComponentActivity) {
DispatchHandle dispatch = new DispatchHandle(json -> emitOnDispatch(json));
CustomCheckoutListener listener = new CustomCheckoutListener(dispatch);
Expand Down Expand Up @@ -99,7 +101,7 @@

@ReactMethod
public void preload(String checkoutURL) {
Activity currentActivity = getCurrentActivity();

Check warning on line 104 in platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/main/java/com/shopify/reactnative/checkoutkit/ShopifyCheckoutKitModule.java

View workflow job for this annotation

GitHub Actions / React Native / Build Android Sample

[removal] getCurrentActivity() in ReactContextBaseJavaModule has been deprecated and marked for removal

Check warning on line 104 in platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/main/java/com/shopify/reactnative/checkoutkit/ShopifyCheckoutKitModule.java

View workflow job for this annotation

GitHub Actions / React Native / Run Android Tests

[removal] getCurrentActivity() in ReactContextBaseJavaModule has been deprecated and marked for removal

Check warning on line 104 in platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/main/java/com/shopify/reactnative/checkoutkit/ShopifyCheckoutKitModule.java

View workflow job for this annotation

GitHub Actions / React Native / Run Android Tests

[removal] getCurrentActivity() in ReactContextBaseJavaModule has been deprecated and marked for removal
if (currentActivity instanceof ComponentActivity) {
ShopifyCheckoutKit.preload(checkoutURL, (ComponentActivity) currentActivity);
}
Expand All @@ -125,6 +127,8 @@
resultConfig.putString("colorScheme", colorSchemeStringFor(checkoutConfig.getAppearance()));
resultConfig.putString("logLevel", logLevelStringFor(checkoutConfig.getLogLevel()));
resultConfig.putBoolean("preloading", checkoutConfig.getPreloading().getEnabled());
resultConfig.putArray("allowedMessageOrigins",
Arguments.fromList(new ArrayList<>(checkoutConfig.getAllowedMessageOrigins())));

return resultConfig;
}
Expand All @@ -140,6 +144,10 @@
configuration.setPreloading(new Preloading(config.getBoolean("preloading")));
}

if (config.hasKey("allowedMessageOrigins")) {
configuration.setAllowedMessageOrigins(toStringSet(config.getArray("allowedMessageOrigins")));
}

if (config.hasKey("logLevel")) {
LogLevel logLevel = logLevelFor(config.getString("logLevel"));

Expand Down Expand Up @@ -168,6 +176,20 @@
});
}

private static Set<String> toStringSet(ReadableArray array) {
Set<String> values = new HashSet<>();
if (array == null) {
return values;
}
for (int i = 0; i < array.size(); i++) {
String value = array.getString(i);
if (value != null) {
values.add(value);
}
}
return values;
}

@ReactMethod(isBlockingSynchronousMethod = true)
public boolean configureAcceleratedCheckouts(
String storefrontDomain,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,10 @@ class RCTShopifyCheckoutKit: NSObject {
ShopifyCheckoutKit.configuration.preloading.enabled = preloading
}

if let allowedMessageOrigins = configuration["allowedMessageOrigins"] as? [String] {
ShopifyCheckoutKit.configuration.allowedMessageOrigins = allowedMessageOrigins
}

if let colorScheme = configuration["colorScheme"] as? String,
let appearance = appearanceFor(colorScheme)
{
Expand Down Expand Up @@ -199,6 +203,7 @@ class RCTShopifyCheckoutKit: NSObject {
"tintColor": ShopifyCheckoutKit.configuration.tintColor,
"backgroundColor": ShopifyCheckoutKit.configuration.backgroundColor,
"closeButtonColor": ShopifyCheckoutKit.configuration.closeButtonTintColor,
"allowedMessageOrigins": ShopifyCheckoutKit.configuration.allowedMessageOrigins,
"logLevel": logLevelToString(ShopifyCheckoutKit.configuration.logLevel)
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
},
"checkoutKit": {
"nativeSdkVersions": {
"ios": "4.0.0-alpha.4",
"android": "4.0.0-alpha.4"
"ios": "4.0.0-alpha.5",
"android": "4.0.0-alpha.5"
}
},
"scripts": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,22 @@ interface CommonConfiguration {
* @default true
*/
preloading?: boolean;
/**
* Origins trusted to send incoming checkout messages, in addition to the
* loaded checkout origin and `shop.app` (including its subdomains).
*
* The native surface is open by default: when this is empty (the default),
* messages from any origin are accepted. Provide one or more origins to
* restrict which origins are trusted. Entries may be exact origins
* (`https://example.com`), wildcard subdomains (`https://*.example.com`), or
* `'*'` to explicitly disable origin validation.
*
* Rejected messages are never silently dropped: the native SDK logs each
* rejection as a warning with the message origin and reason.
*
* @default [] (all origins trusted)
*/
allowedMessageOrigins?: string[];
}

export type Configuration = CommonConfiguration & {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type ConfigurationSpec = {
colorScheme?: string;
logLevel?: string;
preloading?: boolean;
allowedMessageOrigins?: string[];
colors?: ColorsSpec;
};

Expand All @@ -46,6 +47,7 @@ type ConfigurationResultSpec = {
tintColor?: string;
backgroundColor?: string;
closeButtonColor?: string;
allowedMessageOrigins: string[];
};

export interface Spec extends TurboModule {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,18 @@ describe('ShopifyCheckoutKit', () => {
instance.setConfig(configWithTitle);
expect(NativeModule.setConfig).toHaveBeenCalledWith(configWithTitle);
});

it('calls `setConfig` with allowedMessageOrigins configuration', () => {
const instance = new ShopifyCheckout();
const configWithAllowedOrigins: Configuration = {
colorScheme: ColorScheme.automatic,
allowedMessageOrigins: ['https://example.com', 'https://*.example.com'],
};
instance.setConfig(configWithAllowedOrigins);
expect(NativeModule.setConfig).toHaveBeenCalledWith(
configWithAllowedOrigins,
);
});
});

describe('preload', () => {
Expand Down Expand Up @@ -668,6 +680,24 @@ describe('ShopifyCheckoutKit', () => {
expect(result.logLevel).toBe('trace');
expect(result.colorScheme).toBe('sepia');
});

it('returns configured allowed message origins', () => {
NativeModule.getConfig.mockReturnValueOnce({
colorScheme: 'automatic',
logLevel: 'error',
preloading: true,
allowedMessageOrigins: ['https://example.com'],
});

const instance = new ShopifyCheckout();

expect(instance.getConfig()).toStrictEqual({
colorScheme: ColorScheme.automatic,
logLevel: LogLevel.error,
preloading: true,
allowedMessageOrigins: ['https://example.com'],
});
});
});

describe('Geolocation', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ public void setup() {
mocks = MockitoAnnotations.openMocks(this);
mockedArguments = Mockito.mockStatic(Arguments.class);
mockedArguments.when(Arguments::createMap).thenAnswer(invocation -> new JavaOnlyMap());
mockedArguments.when(() -> Arguments.fromList(anyList()))
.thenAnswer(invocation -> JavaOnlyArray.from(invocation.getArgument(0)));

when(mockReactContext.getCurrentActivity()).thenReturn(mockComponentActivity);
shopifyCheckoutKitModule = new ShopifyCheckoutKitModule(mockReactContext);
Expand Down Expand Up @@ -317,6 +319,22 @@ public void testUnknownColorSchemeKeepsTheNativeDefaultAppearance() {
.isEqualTo("storefront");
}

@Test
public void testAllowedMessageOriginsRoundTrip() {
JavaOnlyMap config = new JavaOnlyMap();
JavaOnlyArray allowedMessageOrigins = new JavaOnlyArray();
allowedMessageOrigins.pushString("https://example.com");
allowedMessageOrigins.pushString("https://*.example.com");
config.putArray("allowedMessageOrigins", allowedMessageOrigins);

shopifyCheckoutKitModule.setConfig(config);

assertThat(ShopifyCheckoutKitModule.checkoutConfig.getAllowedMessageOrigins())
.containsExactlyInAnyOrder("https://example.com", "https://*.example.com");
assertThat(shopifyCheckoutKitModule.getConfig().getArray("allowedMessageOrigins").toArrayList())
.containsExactlyInAnyOrder("https://example.com", "https://*.example.com");
}

@Test
public void testCanConfigureLightColorSchemeWithValidColors() {
JavaOnlyMap androidColors = createValidLightColors();
Expand Down
16 changes: 8 additions & 8 deletions platforms/react-native/sample/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2605,8 +2605,8 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ShopifyCheckoutKit (~> 4.0.0-alpha.4)
- ShopifyCheckoutKit/AcceleratedCheckouts (~> 4.0.0-alpha.4)
- ShopifyCheckoutKit (~> 4.0.0-alpha.5)
- ShopifyCheckoutKit/AcceleratedCheckouts (~> 4.0.0-alpha.5)
- SocketRocket
- Yoga
- RNVectorIcons (10.3.0):
Expand Down Expand Up @@ -2638,11 +2638,11 @@ PODS:
- ReactCommon/turbomodule/core
- SocketRocket
- Yoga
- ShopifyCheckoutKit (4.0.0-alpha.4):
- ShopifyCheckoutKit/Core (= 4.0.0-alpha.4)
- ShopifyCheckoutKit/AcceleratedCheckouts (4.0.0-alpha.4):
- ShopifyCheckoutKit (4.0.0-alpha.5):
- ShopifyCheckoutKit/Core (= 4.0.0-alpha.5)
- ShopifyCheckoutKit/AcceleratedCheckouts (4.0.0-alpha.5):
- ShopifyCheckoutKit/Core
- ShopifyCheckoutKit/Core (4.0.0-alpha.4)
- ShopifyCheckoutKit/Core (4.0.0-alpha.5)
- SocketRocket (0.7.1)
- Yoga (0.0.0)

Expand Down Expand Up @@ -2996,9 +2996,9 @@ SPEC CHECKSUMS:
RNGestureHandler: eeb622199ef1fb3a076243131095df1c797072f0
RNReanimated: 237d420b7bb4378ef1dacc7d7a5c674fddb4b5d2
RNScreens: 3fc29af06302e1f1c18a7829fe57cbc2c0259912
RNShopifyCheckoutKit: 2f123d00c3b48120f8e02a3c1c650db61caec677
RNShopifyCheckoutKit: 25f8126109e2af64a90429370ef0436a2871fbdc
RNVectorIcons: be4d047a76ad307ffe54732208fb0498fcb8477f
ShopifyCheckoutKit: 95b6402d901c45c2b2b3fc1182a6fe735f1b7c54
ShopifyCheckoutKit: 7874c8866e6c889d86194398c97e00388111d972
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
Yoga: a742cc68e8366fcfc681808162492bc0aa7a9498

Expand Down
16 changes: 8 additions & 8 deletions platforms/react-native/test/rct-integration-app/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2149,15 +2149,15 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ShopifyCheckoutKit (~> 4.0.0-alpha.4)
- ShopifyCheckoutKit/AcceleratedCheckouts (~> 4.0.0-alpha.4)
- ShopifyCheckoutKit (~> 4.0.0-alpha.5)
- ShopifyCheckoutKit/AcceleratedCheckouts (~> 4.0.0-alpha.5)
- SocketRocket
- Yoga
- ShopifyCheckoutKit (4.0.0-alpha.4):
- ShopifyCheckoutKit/Core (= 4.0.0-alpha.4)
- ShopifyCheckoutKit/AcceleratedCheckouts (4.0.0-alpha.4):
- ShopifyCheckoutKit (4.0.0-alpha.5):
- ShopifyCheckoutKit/Core (= 4.0.0-alpha.5)
- ShopifyCheckoutKit/AcceleratedCheckouts (4.0.0-alpha.5):
- ShopifyCheckoutKit/Core
- ShopifyCheckoutKit/Core (4.0.0-alpha.4)
- ShopifyCheckoutKit/Core (4.0.0-alpha.5)
- SocketRocket (0.7.1)
- Yoga (0.0.0)

Expand Down Expand Up @@ -2464,8 +2464,8 @@ SPEC CHECKSUMS:
ReactAppDependencyProvider: 8df342c127fd0c1e30e8b9f71ff814c22414a7c0
ReactCodegen: 3ba2a79bc32ff858814c17ade10931b33b09dcf4
ReactCommon: 592ef441605638b95e533653259254b4bd35ff4f
RNShopifyCheckoutKit: 2f123d00c3b48120f8e02a3c1c650db61caec677
ShopifyCheckoutKit: 95b6402d901c45c2b2b3fc1182a6fe735f1b7c54
RNShopifyCheckoutKit: 25f8126109e2af64a90429370ef0436a2871fbdc
ShopifyCheckoutKit: 7874c8866e6c889d86194398c97e00388111d972
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
Yoga: a742cc68e8366fcfc681808162492bc0aa7a9498

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class ShopifyCheckoutKitTests: XCTestCase {
ShopifyCheckoutKit.configuration.closeButtonTintColor = nil
ShopifyCheckoutKit.configuration.logLevel = LogLevel.warn
ShopifyCheckoutKit.configuration.preloading.enabled = true
ShopifyCheckoutKit.configuration.allowedMessageOrigins = []
}

private func getShopifyCheckoutKit() -> RCTShopifyCheckoutKit {
Expand Down Expand Up @@ -99,6 +100,21 @@ class ShopifyCheckoutKitTests: XCTestCase {
XCTAssertEqual(result?["title"] as? String, "Custom Checkout")
}

func testAllowedMessageOriginsRoundTrip() {
shopifyCheckoutKit.setConfig(["allowedMessageOrigins": ["https://example.com", "https://*.example.com"]])

XCTAssertEqual(
ShopifyCheckoutKit.configuration.allowedMessageOrigins,
["https://example.com", "https://*.example.com"]
)

let result = shopifyCheckoutKit.getConfig() as? [String: Any]
XCTAssertEqual(
result?["allowedMessageOrigins"] as? [String],
["https://example.com", "https://*.example.com"]
)
}

func testConfigureWithInvalidColors() {
let configuration: [AnyHashable: Any] = [
"colors": [
Expand Down
Loading