Skip to content

Commit

Permalink
feat: add expo config plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
pafry7 committed Jan 26, 2023
1 parent 308cac5 commit 5256a2b
Show file tree
Hide file tree
Showing 14 changed files with 2,957 additions and 70 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,7 @@ lib/
!**/.yarn/versions

# Docs
docs/docs/api/*
docs/docs/api/*

# Keep expo plugin build code
!plugin/build
3 changes: 3 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ docs/
.gitattributes
.yarn/cache
package/
plugin/src
plugin/jest.config.js
plugin/tsconfig.json
1 change: 1 addition & 0 deletions app.plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./plugin/build/withIAP');
40 changes: 38 additions & 2 deletions docs/docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,44 @@

### How do I use `react-native-iap` in Expo?

- You should detach from `expo` and get `expokit` out of it.
- Releated issue in [#174](https://github.com/dooboolab/react-native-iap/issues/174).
> This package cannot be used in the "Expo Go" app because [it requires custom native code](https://docs.expo.io/workflow/customizing/).

After installing this npm package, add the [config plugin](https://docs.expo.io/guides/config-plugins/) to the [`plugins`](https://docs.expo.io/versions/latest/config/app/#plugins) array of your `app.json` or `app.config.js`:

```json
{
"expo": {
"plugins": ["react-native-iap"]
}
}
```

Next, rebuild your app as described in the ["Adding custom native code"](https://docs.expo.io/workflow/customizing/) guide.

## API

The plugin provides props for extra customization. Every time you change the props or plugins, you'll need to rebuild (and `prebuild`) the native app. If no extra properties are added, **Play Store** configuration will be added.

Optional prop:

- `paymentProvider` (_string_): payment provider to configure: `Play Store`, `Amazon AppStore`, `both`

#### Example

```json
{
"expo": {
"plugins": [
[
"react-native-iap",
{
"paymentProvider": "both"
}
]
]
}
}
```

### How do I handle promoted products in iOS?

Expand Down
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module.exports = {
clearMocks: true,
coverageDirectory: 'coverage',
moduleDirectories: ['node_modules', 'src'],
modulePathIgnorePatterns: ['IapExample', 'lib'],
modulePathIgnorePatterns: ['IapExample', 'lib', 'fixtures'],
preset: 'react-native',
setupFiles: ['<rootDir>/test/mocks/react-native-modules.js'],
setupFilesAfterEnv: ['@testing-library/jest-native/extend-expect'],
Expand Down
11 changes: 9 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"!**/__mocks__"
],
"scripts": {
"prepare": "bob build",
"prepare": "bob build && npm run clean:plugin && npm run build:plugin",
"release": "release-it",
"example": "yarn --cwd IapExample",
"test": "jest",
Expand All @@ -67,7 +67,10 @@
"lint:swift": "swiftlint lint --fix --format --path ios/*.swift --config .swiftlint.yml",
"format": "git ls-files -m | xargs yarn prettier --write --ignore-unknown --no-error-on-unmatched-pattern",
"bootstrap": "yarn example && yarn && yarn example pods",
"gen:doc": "typedoc"
"gen:doc": "typedoc",
"build:plugin": "tsc --build plugin",
"clean:plugin": "expo-module clean plugin",
"lint:plugin": "eslint plugin/src/*"
},
"devDependencies": {
"@babel/eslint-parser": "7.18.9",
Expand All @@ -89,6 +92,7 @@
"eslint-plugin-jest": "26.8.2",
"eslint-plugin-prettier": "4.2.1",
"eslint-plugin-simple-import-sort": "7.0.0",
"expo-module-scripts": "^3.0.4",
"jest": "28.1.3",
"monolinter": "1.0.4",
"pod-install": "0.1.38",
Expand All @@ -108,5 +112,8 @@
"peerDependencies": {
"react": ">=16.13.1",
"react-native": ">=0.65.1"
},
"dependencies": {
"@expo/config-plugins": "^5.0.4"
}
}
164 changes: 164 additions & 0 deletions plugin/__tests__/fixtures/buildGradleFiles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
const appBuildGradleWithoutIAP = `
apply plugin: "com.android.application"
import com.android.build.OutputFile
def reactNativeArchitectures() {
def value = project.getProperties().get("reactNativeArchitectures")
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
}
android {
ndkVersion rootProject.ext.ndkVersion
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
applicationId 'com.test.withIAP'
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 34
versionName "1.16.2"
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()`;

const appBuildGradleWithPlayStoreIAP = `
apply plugin: "com.android.application"
import com.android.build.OutputFile
def reactNativeArchitectures() {
def value = project.getProperties().get("reactNativeArchitectures")
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
}
android {
ndkVersion rootProject.ext.ndkVersion
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
missingDimensionStrategy "store", "play"
applicationId 'com.test.withIAP'
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 34
versionName "1.16.2"
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()`;

const appBuildGradleWithAmazonStoreIAP = `
apply plugin: "com.android.application"
import com.android.build.OutputFile
def reactNativeArchitectures() {
def value = project.getProperties().get("reactNativeArchitectures")
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
}
android {
ndkVersion rootProject.ext.ndkVersion
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
missingDimensionStrategy "store", "amazon"
applicationId 'com.test.withIAP'
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 34
versionName "1.16.2"
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()`;

const appBuildGradleWithBothIAP = `
apply plugin: "com.android.application"
import com.android.build.OutputFile
def reactNativeArchitectures() {
def value = project.getProperties().get("reactNativeArchitectures")
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
}
android {
ndkVersion rootProject.ext.ndkVersion
compileSdkVersion rootProject.ext.compileSdkVersion
flavorDimensions "appstore"
productFlavors {
googlePlay {
dimension "appstore"
missingDimensionStrategy "store", "play"
}
amazon {
dimension "appstore"
missingDimensionStrategy "store", "amazon"
}
}
defaultConfig {
applicationId 'com.test.withIAP'
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 34
versionName "1.16.2"
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()`;

const projectBuildGradleWithoutIAP = `
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext {
buildToolsVersion = findProperty('android.buildToolsVersion') ?: '31.0.0'
minSdkVersion = Integer.parseInt(findProperty('android.minSdkVersion') ?: '21')
compileSdkVersion = Integer.parseInt(findProperty('android.compileSdkVersion') ?: '31')
targetSdkVersion = Integer.parseInt(findProperty('android.targetSdkVersion') ?: '31')
if (findProperty('android.kotlinVersion')) {
kotlinVersion = findProperty('android.kotlinVersion')
}
frescoVersion = findProperty('expo.frescoVersion') ?: '2.5.0'
if (System.properties['os.arch'] == 'aarch64') {
// For M1 Users we need to use the NDK 24 which added support for aarch64
ndkVersion = '24.0.8215888'
} else {
// Otherwise we default to the side-by-side NDK version from AGP.
ndkVersion = '21.4.7075529'
}
}
}`;

const projectBuildGradleWithIAP = `
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext {
supportLibVersion = "28.0.0"
buildToolsVersion = findProperty('android.buildToolsVersion') ?: '31.0.0'
minSdkVersion = Integer.parseInt(findProperty('android.minSdkVersion') ?: '21')
compileSdkVersion = Integer.parseInt(findProperty('android.compileSdkVersion') ?: '31')
targetSdkVersion = Integer.parseInt(findProperty('android.targetSdkVersion') ?: '31')
if (findProperty('android.kotlinVersion')) {
kotlinVersion = findProperty('android.kotlinVersion')
}
frescoVersion = findProperty('expo.frescoVersion') ?: '2.5.0'
if (System.properties['os.arch'] == 'aarch64') {
// For M1 Users we need to use the NDK 24 which added support for aarch64
ndkVersion = '24.0.8215888'
} else {
// Otherwise we default to the side-by-side NDK version from AGP.
ndkVersion = '21.4.7075529'
}
}
}`;

export {
appBuildGradleWithAmazonStoreIAP,
appBuildGradleWithBothIAP,
appBuildGradleWithoutIAP,
appBuildGradleWithPlayStoreIAP,
projectBuildGradleWithIAP,
projectBuildGradleWithoutIAP,
};
56 changes: 56 additions & 0 deletions plugin/__tests__/withIAP-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {modifyAppBuildGradle, modifyProjectBuildGradle} from '../src/withIAP';

import {
appBuildGradleWithAmazonStoreIAP,
appBuildGradleWithBothIAP,
appBuildGradleWithoutIAP,
appBuildGradleWithPlayStoreIAP,
projectBuildGradleWithIAP,
projectBuildGradleWithoutIAP,
} from './fixtures/buildGradleFiles';

jest.mock('@expo/config-plugins', () => {
const plugins = jest.requireActual('@expo/config-plugins');

return {
...plugins,
WarningAggregator: {addWarningAndroid: jest.fn()},
};
});

describe('Configures Android native project correctly', () => {
it(`Add supportLibVersion to android/build.gradle if it is not present`, () => {
expect(modifyProjectBuildGradle(projectBuildGradleWithoutIAP)).toMatch(
projectBuildGradleWithIAP,
);
});

it(`Add play store missingDimenstionStrategy to android/app/build.gradle if is not present`, () => {
expect(
modifyAppBuildGradle(appBuildGradleWithoutIAP, 'Play Store'),
).toMatch(appBuildGradleWithPlayStoreIAP);
});

it(`Add amazon store missingDimenstionStrategy to android/app/build.gradle if is not present`, () => {
expect(
modifyAppBuildGradle(appBuildGradleWithoutIAP, 'Amazon AppStore'),
).toMatch(appBuildGradleWithAmazonStoreIAP);
});

it(`Add play store and amazon payment providers to android/app/build.gradle if is not present`, () => {
expect(modifyAppBuildGradle(appBuildGradleWithoutIAP, 'both')).toMatch(
appBuildGradleWithBothIAP,
);
});
it(`Doesn't modify android/build.gradle if supportLibVersion already configured`, () => {
expect(modifyProjectBuildGradle(projectBuildGradleWithIAP)).toMatch(
projectBuildGradleWithIAP,
);
});

it(`Doesn't modify android/app/build.gradle if missingDimensionStrategy already configured`, () => {
expect(
modifyAppBuildGradle(appBuildGradleWithPlayStoreIAP, 'Play Store'),
).toMatch(appBuildGradleWithPlayStoreIAP);
});
});
9 changes: 9 additions & 0 deletions plugin/build/withIAP.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { ConfigPlugin } from '@expo/config-plugins';
declare type PaymentProvider = 'Amazon AppStore' | 'both' | 'Play Store';
export declare const modifyAppBuildGradle: (buildGradle: string, paymentProvider: PaymentProvider) => string;
export declare const modifyProjectBuildGradle: (buildGradle: string) => string;
interface Props {
paymentProvider?: PaymentProvider;
}
declare const withIAP: ConfigPlugin<Props | undefined>;
export default withIAP;
Loading

0 comments on commit 5256a2b

Please sign in to comment.