expo / config-plugins

Out-of-tree Expo config plugins for packages that haven't adopted the config plugin system yet.
427 stars 91 forks source link

Snapchat SnapKit config plugin #153

Open ansh opened 1 year ago

ansh commented 1 year ago

Library

https://www.npmjs.com/package/@snapchat/snap-kit-react-native

Summary

A Snapchat SnapKit config plugin would be great! Would allow us to use Snapchat's React Native package and it's forgo its complex setup process. (https://docs.snap.com/snap-kit/creative-kit/Tutorials/react-native#step-2-update-your-androidmanifestxml-file)

I already managed to get a config plugin sort of working. Can't figure out how to do step 3 (Step 3: Define Paths in Your res/xml/filre_paths.xml File) within the config plugin

Any existing examples?

import {
  AndroidConfig,
  createRunOncePlugin,
  IOSConfig,
  withXcodeProject,
  withAppBuildGradle,
  ConfigPlugin,
  withStringsXml,
  withProjectBuildGradle,
  withAndroidManifest,
} from "@expo/config-plugins";
import {
  createGeneratedHeaderComment,
  MergeResults,
  mergeContents,
  removeGeneratedContents,
} from "@expo/config-plugins/build/utils/generateCode";
import { ExpoConfig } from "expo/config";
const { addMetaDataItemToMainApplication, getMainApplicationOrThrow } = AndroidConfig.Manifest;

// Fork of config-plugins mergeContents, but appends the contents to the end of the file. Taken from https://github.com/expo/expo/blob/master/packages/expo-camera/plugin/src/withCamera.ts
function appendContents({
  src,
  newSrc,
  tag,
  comment,
}: {
  src: string;
  newSrc: string;
  tag: string;
  comment: string;
}): MergeResults {
  const header = createGeneratedHeaderComment(newSrc, tag, comment);
  if (!src.includes(header)) {
    // Ensure the old generated contents are removed.
    const sanitizedTarget = removeGeneratedContents(src, tag);
    const contentsToAdd = [
      // @something
      header,
      // contents
      newSrc,
      // @end
      `${comment} @generated end ${tag}`,
    ].join("\n");

    return {
      contents: sanitizedTarget ?? src + contentsToAdd,
      didMerge: true,
      didClear: !!sanitizedTarget,
    };
  }
  return { contents: src, didClear: false, didMerge: false };
}

const pkg = require("@snapchat/snap-kit-react-native/package.json");

const addSnapkitImport = (src: string): MergeResults => {
  return appendContents({
    tag: "expo-snapkit-import",
    src,
    newSrc: `allprojects { repositories { maven { url "https://storage.googleapis.com/snap-kit-build/maven" } } }`,
    comment: "//",
  });
};
const withSnapkitGradle: ConfigPlugin = (config) => {
  return withProjectBuildGradle(config, (config) => {
    if (config.modResults.language === "groovy") {
      config.modResults.contents = addSnapkitImport(config.modResults.contents).contents;
    } else {
      throw new Error(
        "Cannot add Snapkit maven gradle because the project build.gradle is not groovy"
      );
    }
    return config;
  });
};

async function addMetaDataToAndroidManifest(
  config: Pick<ExpoConfig, "android">,
  androidManifest: AndroidConfig.Manifest.AndroidManifest
): Promise<AndroidConfig.Manifest.AndroidManifest> {
  const name = "com.snapchat.kit.sdk.clientId";
  const value = "{{YOUR API KEY HERE}}";
  const mainApplication = getMainApplicationOrThrow(androidManifest); // Get the <application /> tag and assert if it doesn't exist.
  addMetaDataItemToMainApplication(
    mainApplication,
    name, // value for `android:name`
    value // value for `android:value`
  );
  return androidManifest;
}
const withCustomMetaData: ConfigPlugin = (config) => {
  return withAndroidManifest(config, async (config) => {
    // Modifiers can be async, but try to keep them fast.
    config.modResults = await addMetaDataToAndroidManifest(config, config.modResults);
    return config;
  });
};

function addProviderToAndroidManifest(androidManifest: AndroidConfig.Manifest.AndroidManifest) {
  const app = AndroidConfig.Manifest.getMainApplicationOrThrow(
    androidManifest
  ) as AndroidConfig.Manifest.ManifestApplication & { provider?: any[] };
  // Add the provider if it doesn't exist.
  if (!app.provider) {
    app.provider = [];
  }
  // if the provider doesn't have the FileProvider, add it.
  if (
    !app.provider.some((p) => p.$["android:name"] === "android.support.v4.content.FileProvider")
  ) {
    // <provider android:authorities="${applicationId}.fileprovider" android:name="android.support.v4.content.FileProvider" android:exported="false" android:grantUriPermissions="true"><meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths"/></provider>
    app.provider.push({
      $: {
        "android:name": "android.support.v4.content.FileProvider",
        "android:authorities": "${applicationId}.fileprovider",
        "android:exported": "false",
        "android:grantUriPermissions": "true",
      },
      "meta-data": {
        $: {
          "android:name": "android.support.FILE_PROVIDER_PATHS",
          "android:resource": "@xml/file_paths",
        },
      },
    });
  }
  return androidManifest;
}
function withProvider(config: ExpoConfig) {
  return withAndroidManifest(config, async (config) => {
    config.modResults = addProviderToAndroidManifest(config.modResults);
    return config;
  });
}

function addPackageToQuery(androidManifest: AndroidConfig.Manifest.AndroidManifest) {
  // <package android:name="com.snapchat.android" />
  const packageToAdd = {
    package: {
      $: {
        "android:name": "com.snapchat.android",
      },
    },
  };
  // @ts-ignore since queries does exist but Expo's types don't have it.
  const queries = androidManifest.manifest.queries;
  if (!queries) {
    // @ts-ignore since queries does exist but Expo's types don't have it.
    androidManifest.manifest.queries = [...packageToAdd];
  } else {
    // @ts-ignore since queries does exist but Expo's types don't have it.
    // TODO: this will break if there are other <package> tags in the queries.
    androidManifest.manifest.queries[0].package = {
      $: { ...packageToAdd.package.$ },
    };
  }

  return androidManifest;
}
function withPackage(config: ExpoConfig) {
  return withAndroidManifest(config, async (config) => {
    config.modResults = addPackageToQuery(config.modResults);
    return config;
  });
}

const withSnapchatSdk: ConfigPlugin = (config) => {
  return withPackage((withCustomMetaData(withSnapkitGradle(config))));
  // skipping withProvider as filePath was giving me issues.
  // return withPackage(withProvider(withCustomMetaData(withSnapkitGradle(config))));
};

export default createRunOncePlugin(withSnapchatSdk, pkg.name, pkg.version);
yingyingbangbagng commented 1 year ago

I faced some problem while request access to the Snapchat camera kit and I didn't receive any response from Snapchat company, so what can I do to get the licenses to access the Snapchat camera kit, how can I solve this problem?

ansh commented 1 year ago

That is unrelated please mention that in Snapchat’s repo @yingyingbangbagng

singhayush1403 commented 1 year ago

@ansh were you able to add it using the plugins? Stuck on the same step

ansh commented 1 year ago

Yes I was.

On Fri, Mar 24, 2023 at 7:31 AM, Ayush Singh < @.*** > wrote:

@ ansh ( https://github.com/ansh ) were you able to add it using the plugins? Stuck on the same step

— Reply to this email directly, view it on GitHub ( https://github.com/expo/config-plugins/issues/153#issuecomment-1482899870 ) , or unsubscribe ( https://github.com/notifications/unsubscribe-auth/AB6T25L7ZU6IYYI6I5X3JYLW5WV3RANCNFSM6AAAAAAS2HFI6Q ). You are receiving this because you were mentioned. Message ID: <expo/config-plugins/issues/153/1482899870 @ github. com>

singhayush1403 commented 1 year ago

@ansh Could you guide me? Stuck on the part where I have to modify file_paths.xml using the config functions provided by expo