intervals = new HashMap<>();
28 |
29 | public TimersModule(ReactApplicationContext context) {
30 | super(context);
31 | }
32 |
33 | @NonNull
34 | @Override
35 | public String getName() {
36 | return "TimersModule";
37 | }
38 |
39 | private void sendEvent(String eventName, WritableMap params) {
40 | getReactApplicationContext()
41 | .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
42 | .emit(eventName, params);
43 | }
44 |
45 | @Override
46 | public void initialize() {
47 | super.initialize();
48 |
49 | handler = new Handler();
50 | timer = new Timer();
51 | }
52 |
53 | @ReactMethod
54 | public void setTimeout(int id, int delay) {
55 | Log.i(TAG, "setTimeout: " + delay);
56 |
57 | timeouts.put(id, new Object());
58 | handler.postDelayed(() -> {
59 | if (!timeouts.containsKey(id))
60 | return;
61 |
62 | WritableMap data = Arguments.createMap();
63 | data.putInt("id", id);
64 | sendEvent("timer", data);
65 | }, delay);
66 | }
67 |
68 | @ReactMethod
69 | public void clearTimeout(int id) {
70 | Log.i(TAG, "clearTimeout: " + id);
71 |
72 | Object token = timeouts.remove(id);
73 | if (token != null) {
74 | handler.removeCallbacksAndMessages(token);
75 | }
76 | }
77 |
78 | @ReactMethod
79 | public void setInterval(int id, int period) {
80 | Log.i(TAG, "setInterval: " + period);
81 |
82 | TimerTask timerTask = new TimerTask() {
83 | @Override
84 | public void run() {
85 | if (!intervals.containsKey(id)) {
86 | return;
87 | }
88 |
89 | WritableMap data = Arguments.createMap();
90 | data.putInt("id", id);
91 | sendEvent("interval", data);
92 | }
93 | };
94 | intervals.put(id, timerTask);
95 | timer.scheduleAtFixedRate(timerTask, 0, period);
96 | }
97 |
98 | @ReactMethod
99 | public void clearInterval(int id) {
100 | Log.i(TAG, "clearInterval: " + id);
101 |
102 | TimerTask task = intervals.remove(id);
103 | if (task != null) {
104 | task.cancel();
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/android/app/src/debug/java/com/discordrn/ReactNativeFlipper.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Facebook, Inc. and its affiliates.
3 | *
4 | * This source code is licensed under the MIT license found in the LICENSE file in the root
5 | * directory of this source tree.
6 | */
7 | package com.discordrn;
8 |
9 | import android.content.Context;
10 | import com.facebook.flipper.android.AndroidFlipperClient;
11 | import com.facebook.flipper.android.utils.FlipperUtils;
12 | import com.facebook.flipper.core.FlipperClient;
13 | import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin;
14 | import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin;
15 | import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin;
16 | import com.facebook.flipper.plugins.inspector.DescriptorMapping;
17 | import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
18 | import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;
19 | import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;
20 | import com.facebook.flipper.plugins.react.ReactFlipperPlugin;
21 | import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
22 | import com.facebook.react.ReactInstanceManager;
23 | import com.facebook.react.bridge.ReactContext;
24 | import com.facebook.react.modules.network.NetworkingModule;
25 | import okhttp3.OkHttpClient;
26 |
27 | public class ReactNativeFlipper {
28 | public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
29 | if (FlipperUtils.shouldEnableFlipper(context)) {
30 | final FlipperClient client = AndroidFlipperClient.getInstance(context);
31 |
32 | client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()));
33 | client.addPlugin(new ReactFlipperPlugin());
34 | client.addPlugin(new DatabasesFlipperPlugin(context));
35 | client.addPlugin(new SharedPreferencesFlipperPlugin(context));
36 | client.addPlugin(CrashReporterPlugin.getInstance());
37 |
38 | NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
39 | NetworkingModule.setCustomClientBuilder(
40 | new NetworkingModule.CustomClientBuilder() {
41 | @Override
42 | public void apply(OkHttpClient.Builder builder) {
43 | builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));
44 | }
45 | });
46 | client.addPlugin(networkFlipperPlugin);
47 | client.start();
48 |
49 | // Fresco Plugin needs to ensure that ImagePipelineFactory is initialized
50 | // Hence we run if after all native modules have been initialized
51 | ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
52 | if (reactContext == null) {
53 | reactInstanceManager.addReactInstanceEventListener(
54 | new ReactInstanceManager.ReactInstanceEventListener() {
55 | @Override
56 | public void onReactContextInitialized(ReactContext reactContext) {
57 | reactInstanceManager.removeReactInstanceEventListener(this);
58 | reactContext.runOnNativeModulesQueueThread(
59 | new Runnable() {
60 | @Override
61 | public void run() {
62 | client.addPlugin(new FrescoFlipperPlugin());
63 | }
64 | });
65 | }
66 | });
67 | } else {
68 | client.addPlugin(new FrescoFlipperPlugin());
69 | }
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/android/app/src/main/java/com/discordrn/MainAppPackage.java:
--------------------------------------------------------------------------------
1 | package com.discordrn;
2 |
3 | import androidx.annotation.NonNull;
4 |
5 | import com.discordrn.modules.*;
6 | import com.discordrn.views.*;
7 | import com.facebook.react.ReactPackage;
8 | import com.facebook.react.bridge.NativeModule;
9 | import com.facebook.react.bridge.ReactApplicationContext;
10 | import com.facebook.react.uimanager.ViewManager;
11 |
12 | import java.util.ArrayList;
13 | import java.util.List;
14 |
15 | public class MainAppPackage implements ReactPackage {
16 | @SuppressWarnings("rawtypes")
17 | @NonNull
18 | @Override
19 | public List createViewManagers(@NonNull ReactApplicationContext reactContext) {
20 | List views = new ArrayList<>();
21 |
22 | views.add(new DCDSegmentedControl());
23 | views.add(new ViewStub("DCDSafeArea"));
24 | // views.add(new ViewStub("DCDChatList"));
25 | views.add(new DCDChatList());
26 | views.add(new DCDChat());
27 | views.add(new DCDChatInput());
28 | views.add(new ViewStub("ChannelSpine"));
29 | views.add(new ViewStub("KeyCommandsView")); // https://github.com/envoy/react-native-key-commands, ios only
30 | views.add(new NativeLottieNode());
31 |
32 | return views;
33 | }
34 |
35 | @NonNull
36 | @Override
37 | public List createNativeModules(@NonNull ReactApplicationContext reactContext) {
38 | List modules = new ArrayList<>();
39 |
40 | modules.add(new ActionSheetAndroid(reactContext));
41 | modules.add(new BrowserManager(reactContext));
42 | modules.add(new BundleUpdaterManager(reactContext));
43 | modules.add(new CaptchaManager(reactContext));
44 | modules.add(new DCDChatInputManager(reactContext));
45 | modules.add(new DCDChatManager(reactContext));
46 | modules.add(new DCDColor(reactContext));
47 | modules.add(new DCDCompressionManager(reactContext));
48 | modules.add(new DCDCrashlyticsCrashReports(reactContext));
49 | modules.add(new DCDDeviceManager(reactContext));
50 | modules.add(new DCDFastConnectManager(reactContext));
51 | modules.add(new DCDIconManager(reactContext));
52 | modules.add(new DCDNotificationCategoryUtils(reactContext));
53 | modules.add(new DCDSafeAreaManager(reactContext));
54 | modules.add(new DCDSKAdNetworkManager(reactContext));
55 | modules.add(new DCDTheme(reactContext));
56 | modules.add(new DynamicLinkManager(reactContext));
57 | modules.add(new ExpoRandom(reactContext));
58 | modules.add(new ImageManager(reactContext));
59 | modules.add(new InfoDictionaryManager(reactContext));
60 | modules.add(new IntentsHandler(reactContext));
61 | modules.add(new KeyboardManager(reactContext));
62 | modules.add(new KeyCommandsModule(reactContext));
63 | modules.add(new LocalizationManager(reactContext));
64 | modules.add(new MediaManager(reactContext));
65 | modules.add(new MMKVManager(reactContext, "DCDStrongboxManager"));
66 | modules.add(new MMKVManager(reactContext, "MMKVManager"));
67 | modules.add(new NativePermissionManager(reactContext));
68 | modules.add(new OnePasswordManager(reactContext));
69 | modules.add(new ProximitySensorManager(reactContext));
70 | modules.add(new PushNotificationAndroid(reactContext));
71 | modules.add(new RNCPushNotificationIOS(reactContext));
72 | modules.add(new RNSentryModule(reactContext));
73 | modules.add(new ScreenshotHelper(reactContext));
74 | modules.add(new TimersModule(reactContext));
75 | modules.add(new TTIManager(reactContext));
76 | modules.add(new UserSearchWorkerManager(reactContext));
77 | modules.add(new VoiceEngine(reactContext));
78 |
79 | return modules;
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/android/app/src/main/java/com/discordrn/modules/DCDChatManager.java:
--------------------------------------------------------------------------------
1 | package com.discordrn.modules;
2 |
3 | import android.view.animation.AlphaAnimation;
4 | import android.view.animation.Animation;
5 | import android.widget.LinearLayout;
6 |
7 | import androidx.annotation.NonNull;
8 |
9 | import com.discordrn.models.MessageContent;
10 | import com.discordrn.models.Row;
11 | import com.discordrn.views.DCDChatList;
12 | import com.facebook.react.bridge.ReactApplicationContext;
13 | import com.facebook.react.bridge.ReactContextBaseJavaModule;
14 | import com.facebook.react.bridge.ReactMethod;
15 | import com.facebook.react.uimanager.UIManagerModule;
16 | import com.google.gson.Gson;
17 | import com.google.gson.GsonBuilder;
18 | import com.google.gson.reflect.TypeToken;
19 | import com.google.gson.typeadapters.RuntimeTypeAdapterFactory;
20 |
21 | import java.lang.reflect.Type;
22 | import java.util.List;
23 | import java.util.Objects;
24 |
25 | public class DCDChatManager extends ReactContextBaseJavaModule {
26 | private static final Gson gson;
27 | private static final Type rowsType = TypeToken.getParameterized(List.class, Row.class).getType();
28 |
29 | static {
30 | RuntimeTypeAdapterFactory contentFactory = RuntimeTypeAdapterFactory.of(MessageContent.class, "type");
31 | contentFactory.registerSubtype(MessageContent.Text.class, "text");
32 | contentFactory.registerSubtype(MessageContent.Link.class, "link");
33 | contentFactory.registerSubtype(MessageContent.CodeBlock.class, "codeBlock");
34 | contentFactory.registerSubtype(MessageContent.Mention.class, "mention");
35 | contentFactory.registerSubtype(MessageContent.Channel.class, "channel");
36 | contentFactory.registerSubtype(MessageContent.Emoji.class, "emoji");
37 | contentFactory.registerSubtype(MessageContent.CustomEmoji.class, "customEmoji");
38 | contentFactory.registerSubtype(MessageContent.Strong.class, "strong");
39 | contentFactory.registerSubtype(MessageContent.Emphasis.class, "em");
40 | contentFactory.registerSubtype(MessageContent.Stroke.class, "s");
41 | contentFactory.registerSubtype(MessageContent.Underlined.class, "u");
42 | contentFactory.registerSubtype(MessageContent.InlineCode.class, "inlineCode");
43 | contentFactory.registerSubtype(MessageContent.Spoiler.class, "spoiler");
44 | contentFactory.registerSubtype(MessageContent.Timestamp.class, "timestamp");
45 | contentFactory.registerSubtype(MessageContent.BlockQuote.class, "blockQuote");
46 | contentFactory.registerSubtype(MessageContent.Paragraph.class, "paragraph");
47 |
48 | gson = new GsonBuilder()
49 | .registerTypeAdapterFactory(contentFactory)
50 | .create();
51 | }
52 |
53 | public DCDChatManager(ReactApplicationContext context) {
54 | super(context);
55 | }
56 |
57 | @NonNull
58 | @Override
59 | public String getName() {
60 | return "DCDChatManager";
61 | }
62 |
63 | @ReactMethod
64 | public void updateRows(int id, String json, boolean b) {
65 | Objects.requireNonNull(getReactApplicationContext().getCurrentActivity()).runOnUiThread(() -> {
66 | List rows = gson.fromJson(json, rowsType);
67 | for (Row row : rows) {
68 | if (row.message != null && row.message.content != null)
69 | DCDChatList.addMessage(row.message);
70 | }
71 | });
72 | }
73 |
74 | @ReactMethod
75 | public void clearRows(int id) {
76 | DCDChatList.clearMessages();
77 | }
78 |
79 | @ReactMethod
80 | public void scrollTo(int id, int message, boolean instant) {
81 | }
82 |
83 | @ReactMethod
84 | public void fadeIn(int id) {
85 | Objects.requireNonNull(getReactApplicationContext().getCurrentActivity()).runOnUiThread(() -> {
86 | UIManagerModule managerModule = getReactApplicationContext().getNativeModule(UIManagerModule.class);
87 | assert managerModule != null;
88 | LinearLayout layout = (LinearLayout) managerModule.resolveView(id);
89 | AlphaAnimation anim = new AlphaAnimation(1.0f, 0.0f);
90 | anim.setDuration(300);
91 | anim.setRepeatCount(1);
92 | anim.setRepeatMode(Animation.REVERSE);
93 | layout.startAnimation(anim);
94 | });
95 | }
96 |
97 | @ReactMethod
98 | public void scrollToBottom(int id, boolean b) {
99 | }
100 |
101 | @ReactMethod
102 | public void scrollToTop(int id, boolean b) {
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/android/app/src/main/java/com/discordrn/modules/DCDCompressionManager.java:
--------------------------------------------------------------------------------
1 | package com.discordrn.modules;
2 |
3 | import android.util.Log;
4 |
5 | import androidx.annotation.NonNull;
6 |
7 | import com.facebook.react.bridge.ReactApplicationContext;
8 | import com.facebook.react.bridge.ReactContextBaseJavaModule;
9 | import com.facebook.react.bridge.ReactMethod;
10 | import com.facebook.react.bridge.WritableMap;
11 | import com.facebook.react.modules.websocket.WebSocketModule;
12 |
13 | import org.json.JSONException;
14 | import org.json.JSONObject;
15 |
16 | import java.io.EOFException;
17 | import java.io.IOException;
18 | import java.util.HashSet;
19 | import java.util.Map;
20 | import java.util.Objects;
21 | import java.util.Set;
22 | import java.util.zip.Inflater;
23 |
24 | import okio.Buffer;
25 | import okio.ByteString;
26 | import okio.InflaterSource;
27 |
28 | public class DCDCompressionManager extends ReactContextBaseJavaModule {
29 | private static final String TAG = "CompressionManager";
30 |
31 | public DCDCompressionManager(ReactApplicationContext context) {
32 | super(context);
33 | }
34 |
35 | @NonNull
36 | @Override
37 | public String getName() {
38 | return "DCDCompressionManager";
39 | }
40 |
41 | private WebSocketModule getSocketModule() {
42 | return getReactApplicationContext().getNativeModule(WebSocketModule.class);
43 | }
44 |
45 | private static final ByteString FLUSH = ByteString.of((byte) 0x00, (byte) 0x00, (byte) 0xFF, (byte) 0xFF);
46 |
47 | private Set enabled = new HashSet<>();
48 |
49 | @ReactMethod
50 | public void enableZlibStreamSupport(int socketId) {
51 | Log.i(TAG, "enableZlibStreamSupport: " + socketId);
52 |
53 | if (enabled.contains(socketId))
54 | return;
55 |
56 | enabled.add(socketId);
57 |
58 | Buffer buffer = new Buffer();
59 | Buffer result = new Buffer();
60 | Inflater inflater = new Inflater();
61 |
62 | WebSocketModule webSocketModule = getSocketModule();
63 | webSocketModule.setContentHandler(socketId, new WebSocketModule.ContentHandler() {
64 | @Override
65 | public void onMessage(String text, WritableMap params) {
66 | params.putString("data", text);
67 | }
68 |
69 | @Override
70 | public void onMessage(ByteString byteString, WritableMap params) {
71 | buffer.write(byteString);
72 |
73 | long size = buffer.size();
74 | if (size < 4 || !buffer.rangeEquals(size - 4, FLUSH))
75 | return;
76 |
77 | InflaterSource source = new InflaterSource(buffer, inflater);
78 | result.clear();
79 |
80 | try {
81 | try {
82 | while (source.read(result, Long.MAX_VALUE) != -1) {
83 | }
84 | } catch (EOFException ignored) {
85 | }
86 | } catch (IOException e) {
87 | Log.e(TAG, "inflating failed: " + e);
88 | }
89 |
90 | buffer.clear();
91 |
92 | String json = result.snapshot().utf8();
93 |
94 | Log.d(TAG, "received: " + json);
95 | params.putString("type", "text");
96 | params.putString("data", json);
97 |
98 | try {
99 | int op = new JSONObject(json).getInt("op");
100 | if (op == 10) {
101 | Map identities = Objects.requireNonNull(getReactApplicationContext().getNativeModule(DCDFastConnectManager.class)).getIdentities();
102 |
103 | if (identities.containsKey(socketId)) {
104 | String identity = identities.get(socketId);
105 | Log.d(TAG, "sending: " + identity);
106 | webSocketModule.send(identity, socketId);
107 | }
108 | }
109 | } catch (JSONException e) {
110 | Log.e(TAG, "failed to parse json: " + e);
111 | }
112 | }
113 | });
114 | }
115 |
116 | @ReactMethod
117 | public void disableZlibStreamSupport(int socketId) {
118 | Log.i(TAG, "disableZlibStreamSupport: " + socketId);
119 |
120 | if (!enabled.contains(socketId))
121 | return;
122 |
123 | enabled.remove(socketId);
124 | WebSocketModule webSocketModule = getSocketModule();
125 | webSocketModule.setContentHandler(socketId, null);
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/android/app/src/main/java/com/discordrn/views/DCDSegmentedControl.java:
--------------------------------------------------------------------------------
1 | package com.discordrn.views;
2 |
3 | import android.content.Context;
4 | import android.graphics.Color;
5 | import android.graphics.Paint;
6 | import android.view.Gravity;
7 |
8 | import androidx.annotation.NonNull;
9 | import androidx.annotation.Nullable;
10 |
11 | import com.alanvan.segmented_control.SegmentedControlButton;
12 | import com.alanvan.segmented_control.SegmentedControlGroup;
13 | import com.facebook.react.bridge.*;
14 | import com.facebook.react.common.MapBuilder;
15 | import com.facebook.react.uimanager.SimpleViewManager;
16 | import com.facebook.react.uimanager.ThemedReactContext;
17 | import com.facebook.react.uimanager.annotations.ReactProp;
18 | import com.facebook.react.uimanager.events.RCTEventEmitter;
19 |
20 | import java.lang.reflect.Field;
21 | import java.util.Map;
22 |
23 | import kotlin.Unit;
24 |
25 | public class DCDSegmentedControl extends SimpleViewManager {
26 | private int textColor;
27 | private int selectedTextColor;
28 | private int selected;
29 |
30 | @NonNull
31 | @Override
32 | public String getName() {
33 | return "DCDSegmentedControl";
34 | }
35 |
36 | @NonNull
37 | @Override
38 | protected SegmentedControlGroup createViewInstance(@NonNull ThemedReactContext reactContext) {
39 | SegmentedControlGroup group = new SegmentedControlGroup(reactContext);
40 | group.addView(createButton(reactContext));
41 | group.setOnSelectedOptionChangeCallback((i) -> {
42 | selected = i;
43 | ((SegmentedControlButton) group.getChildAt(i)).setTextColor(selectedTextColor);
44 | for (int index = 0, count = group.getChildCount(); index < count; index++)
45 | if (index != i) ((SegmentedControlButton) group.getChildAt(index)).setTextColor(textColor);
46 |
47 | WritableMap data = Arguments.createMap();
48 | data.putInt("selectedSegmentIndex", i);
49 | ((ReactContext) group.getContext()).getJSModule(RCTEventEmitter.class).receiveEvent(group.getId(), "onValueChange", data);
50 | return Unit.INSTANCE;
51 | });
52 | return group;
53 | }
54 |
55 | @ReactProp(name = "values")
56 | public void setValues(SegmentedControlGroup group, ReadableArray values) {
57 | int size = values.size();
58 | if (size == 0) return;
59 | else ((SegmentedControlButton) group.getChildAt(0)).setText(values.getString(0));
60 | for (int i = 1; i < size; i++) {
61 | SegmentedControlButton btn = createButton(group.getContext());
62 | btn.setText(values.getString(i));
63 | group.addView(btn);
64 | }
65 | }
66 |
67 | @ReactProp(name = "selectedSegmentIndex")
68 | public void setSelectedSegmentIndex(SegmentedControlGroup group, int i) {
69 | selected = i;
70 | group.setSelectedIndex(i, false);
71 | }
72 |
73 | @ReactProp(name = "backgroundColor")
74 | public void setBackgroundColor(SegmentedControlGroup group, String color) {
75 | group.setBackgroundColor(Color.parseColor(color));
76 | }
77 |
78 | @ReactProp(name = "titleAttributes")
79 | public void setTitleAttributes(SegmentedControlGroup group, ReadableMap map) {
80 | textColor = map.getInt("textColor");
81 | for (int i = 0, j = group.getChildCount(); i < j; i++)
82 | ((SegmentedControlButton) group.getChildAt(i)).setTextColor(textColor);
83 | }
84 |
85 | @ReactProp(name = "selectedTitleAttributes")
86 | public void setSelectedTitleAttributes(SegmentedControlGroup group, ReadableMap map) {
87 | selectedTextColor = map.getInt("textColor");
88 | ((SegmentedControlButton) group.getChildAt(selected)).setTextColor(selectedTextColor);
89 | }
90 |
91 | @ReactProp(name = "customSelectedTintColor")
92 | public void setCustomSelectedTintColor(SegmentedControlGroup group, String color) throws Throwable {
93 | Field field = group.getClass().getDeclaredField("sliderPaint");
94 | field.setAccessible(true);
95 | Paint paint = (Paint) field.get(group);
96 | if (paint != null) {
97 | int c = Color.parseColor(color);
98 | paint.setColor(c);
99 | }
100 | }
101 |
102 | @Nullable
103 | @Override
104 | public Map getExportedCustomDirectEventTypeConstants() {
105 | return MapBuilder.of("onValueChange", MapBuilder.of("registrationName", "onValueChange"));
106 | }
107 |
108 | private SegmentedControlButton createButton(Context context) {
109 | SegmentedControlButton btn = new SegmentedControlButton(context);
110 | btn.setGravity(Gravity.CENTER);
111 | if (textColor != 0) btn.setTextColor(textColor);
112 | return btn;
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/android/app/src/main/java/com/discordrn/MainApplication.java:
--------------------------------------------------------------------------------
1 | package com.discordrn;
2 |
3 | import android.app.Application;
4 | import android.content.Context;
5 | import android.content.res.AssetManager;
6 | import android.util.Log;
7 |
8 | import com.facebook.common.logging.FLog;
9 | import com.facebook.debug.debugoverlay.model.DebugOverlayTag;
10 | import com.facebook.debug.holder.Printer;
11 | import com.facebook.debug.holder.PrinterHolder;
12 | import com.facebook.hermes.reactexecutor.HermesExecutorFactory;
13 | import com.facebook.react.CustomReactInstanceManager;
14 | import com.facebook.react.PackageList;
15 | import com.facebook.react.ReactApplication;
16 | import com.facebook.react.ReactInstanceManager;
17 | import com.facebook.react.ReactNativeHost;
18 | import com.facebook.react.ReactPackage;
19 | import com.facebook.react.bridge.JSBundleLoader;
20 | import com.facebook.react.bridge.JSBundleLoaderDelegate;
21 | import com.facebook.react.bridge.JSIModulePackage;
22 | import com.facebook.react.common.LifecycleState;
23 | import com.facebook.soloader.SoLoader;
24 | import com.swmansion.reanimated.ReanimatedJSIModulePackage;
25 |
26 | import java.lang.reflect.InvocationTargetException;
27 | import java.util.List;
28 |
29 | public class MainApplication extends Application implements ReactApplication {
30 | private final ReactNativeHost mReactNativeHost =
31 | new ReactNativeHost(this) {
32 | @Override
33 | public boolean getUseDeveloperSupport() {
34 | return BuildConfig.DEBUG;
35 | }
36 |
37 | @Override
38 | protected List getPackages() {
39 | List packages = new PackageList(this).getPackages();
40 | packages.add(new MainAppPackage());
41 | return packages;
42 | }
43 |
44 | @Override
45 | protected JSIModulePackage getJSIModulePackage() {
46 | return new ReanimatedJSIModulePackage();
47 | }
48 |
49 | @Override
50 | protected String getJSMainModuleName() {
51 | return "index";
52 | }
53 |
54 | @Override
55 | protected String getBundleAssetName() {
56 | return "discord.android.bundle";
57 | }
58 |
59 | @Override
60 | protected ReactInstanceManager createReactInstanceManager() {
61 | return new CustomReactInstanceManager(
62 | getApplication(),
63 | null,
64 | null,
65 | new HermesExecutorFactory(),
66 | new JSBundleLoader() {
67 | @Override
68 | public String loadScript(JSBundleLoaderDelegate delegate) {
69 | AssetManager assetManager = getApplicationContext().getAssets();
70 |
71 | delegate.loadScriptFromAssets(assetManager, "assets://preload.js", true);
72 |
73 | String assetUrl = "assets://" + getBundleAssetName();
74 | delegate.loadScriptFromAssets(assetManager, assetUrl, false);
75 | return assetUrl;
76 | }
77 | },
78 | getJSMainModuleName(),
79 | getPackages(),
80 | getUseDeveloperSupport(),
81 | null,
82 | LifecycleState.BEFORE_CREATE,
83 | getUIImplementationProvider(),
84 | null,
85 | null,
86 | false,
87 | null,
88 | 1,
89 | -1,
90 | getJSIModulePackage(),
91 | null
92 | );
93 | }
94 | };
95 |
96 | @Override
97 | public ReactNativeHost getReactNativeHost() {
98 | return mReactNativeHost;
99 | }
100 |
101 | @Override
102 | public void onCreate() {
103 | super.onCreate();
104 | FLog.setMinimumLoggingLevel(Log.VERBOSE);
105 | SoLoader.init(this, false);
106 | initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
107 | if (BuildConfig.PRINTER_LOGS) PrinterHolder.setPrinter(new Printer() {
108 | private void log(String message) {
109 | if (message.contains("ReanimatedModule") || message.contains("NativeAnimatedModule"))
110 | return;
111 |
112 | Log.v("Printer", message);
113 | }
114 |
115 | @Override
116 | public void logMessage(DebugOverlayTag tag, String message, Object... args) {
117 | log(String.format("[%s] %s", tag.name, String.format(message, args)));
118 | }
119 |
120 | @Override
121 | public void logMessage(DebugOverlayTag tag, String message) {
122 | log(String.format("[%s] %s", tag.name, message));
123 | }
124 |
125 | @Override
126 | public boolean shouldDisplayLogMessage(DebugOverlayTag tag) {
127 | return true;
128 | }
129 | });
130 | }
131 |
132 | private static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
133 | if (BuildConfig.DEBUG) {
134 | try {
135 | Class.forName("com.discordrn.ReactNativeFlipper")
136 | .getMethod("initializeFlipper", Context.class, ReactInstanceManager.class)
137 | .invoke(null, context, reactInstanceManager);
138 | } catch (ClassNotFoundException | InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
139 | e.printStackTrace();
140 | }
141 | }
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/android/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/preload/src/index.js:
--------------------------------------------------------------------------------
1 | import * as driver from "./driver";
2 | import reactDevToolsCode from "./react-devtools-core.txt";
3 |
4 | const loggerId = 13;
5 | const promiseId = 111;
6 | const nativeModulesId = 41;
7 | const localForageId = 892;
8 | const setUpBatchedBridgeId = 152;
9 | const exceptionsManagerId = 99;
10 |
11 | const appStateId = 415;
12 | const viewConfigId = 183;
13 | const flattenStyleId = 164;
14 |
15 | globalThis = new Proxy(
16 | globalThis,
17 | {
18 | set: function (obj, prop, value) {
19 | if (prop === "__d") {
20 | obj[prop] = function (factory, moduleId, dependencyMap) {
21 | if (moduleId === loggerId || moduleId == promiseId || moduleId == localForageId || moduleId == setUpBatchedBridgeId || moduleId == exceptionsManagerId) {
22 | const _factory = factory;
23 | factory = function (global, _$$_REQUIRE, _$$_IMPORT_DEFAULT, _$$_IMPORT_ALL, module, exports, _dependencyMap) {
24 | const ret = _factory(global, _$$_REQUIRE, _$$_IMPORT_DEFAULT, _$$_IMPORT_ALL, module, exports, _dependencyMap);
25 |
26 | if (moduleId === loggerId) {
27 | const _logger = module.exports.default;
28 | module.exports.default = function (name) {
29 | const logger = new _logger(name);
30 | const prefix = `[${name}] `;
31 | logger.log = (s) => console.log(prefix + s);
32 | logger.warn = (s) => console.warn(prefix + s);
33 | logger.error = (s) => console.error(prefix + (s && s.stack ? s.stack : s));
34 | logger.trace = (s) => console.log(prefix + s);
35 | logger.verbose = (s) => console.log(prefix + s);
36 | return logger;
37 | }
38 | } else if (moduleId == promiseId) {
39 | module.exports = new Proxy(module.exports, {
40 | construct: function (target, argumentsList, newTarget) {
41 | const promise = new target(...argumentsList);
42 |
43 | if (argumentsList.length > 0 && argumentsList[0] !== Promise._0) {
44 | const stack = new Error("Promise not resolved after 10 seconds").stack;
45 | setTimeout(() => {
46 | if (promise._V === 0) {
47 | console.warn(stack);
48 | }
49 | }, 10000);
50 | }
51 |
52 | return promise;
53 | }
54 | });
55 |
56 | module.exports._Z = (promise, error) => {
57 | console[error instanceof Error ? "error" : "warn"](`Promise rejection (_deferredState: ${promise._U})\n` + (error instanceof Error ? error.stack : new Error(typeof error === "object" ? JSON.stringify(error) : error).stack));
58 | };
59 | } else if (moduleId == localForageId) {
60 | driver.AsyncStorage = _$$_REQUIRE(nativeModulesId).RNC_AsyncSQLiteDBStorage;
61 | const _driver = driver.driverWithoutSerialization();
62 | module.exports.defineDriver(_driver).then(() => module.exports.setDriver(_driver._driver));
63 |
64 | module.exports.__proto__.getDriver = function getDriver(t, n, o) {
65 | const d = Promise.resolve(_driver);
66 | typeof n == "function" && d.then(n);
67 | typeof o == "function" && d.catch(o);
68 | return d;
69 | };
70 | } else if (moduleId == setUpBatchedBridgeId) {
71 | (0, eval)(reactDevToolsCode);
72 | const reactDevTools = require_react_devtools_core();
73 |
74 | function setUpReactDevTools() {
75 | const AppState = _$$_REQUIRE(appStateId);
76 | const isAppActive = () => AppState.currentState !== "background";
77 |
78 | const ws = new WebSocket("ws://localhost:8097");
79 | ws.addEventListener("close", function (event) {
80 | console.log("Devtools connection closed: " + event.message);
81 | });
82 |
83 | const viewConfig = _$$_REQUIRE(viewConfigId);
84 | const { flattenStyle } = _$$_REQUIRE(flattenStyleId);
85 | console.log("Connecting to devtools");
86 | reactDevTools.connectToDevTools({
87 | isAppActive,
88 | resolveRNStyle: flattenStyle,
89 | nativeStyleEditorValidAttributes: viewConfig.validAttributes.style ? Object.keys(
90 | viewConfig.validAttributes.style,
91 | ) : undefined,
92 | websocket: ws
93 | });
94 | }
95 |
96 | setUpReactDevTools();
97 |
98 | const { NativeAppEventEmitter } = _$$_REQUIRE(17);
99 | NativeAppEventEmitter.addListener("RCTDevMenuShown", setUpReactDevTools);
100 | } else if (moduleId == exceptionsManagerId) {
101 | module.exports.handleException = (e, isFatal) => {
102 | if (e instanceof Error) {
103 | console.error(e.stack);
104 | } else {
105 | console.error(e);
106 | }
107 | };
108 | }
109 |
110 | return ret;
111 | };
112 | }
113 |
114 | return value(factory, moduleId, dependencyMap);
115 | }
116 | } else {
117 | obj[prop] = value;
118 | }
119 |
120 | return true;
121 | },
122 | },
123 | );
124 |
--------------------------------------------------------------------------------
/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | import groovy.json.JsonSlurper
2 | import java.security.*
3 |
4 | apply plugin: "com.android.application"
5 | apply plugin: "kotlin-android"
6 |
7 | project.ext.react = [
8 | enableHermes : true,
9 | bundleInRelease: false,
10 | ]
11 |
12 | apply from: "../../node_modules/react-native/react.gradle"
13 |
14 | /**
15 | * Set this to true to create two separate APKs instead of one:
16 | * - An APK that only works on ARM devices
17 | * - An APK that only works on x86 devices
18 | * The advantage is the size of the APK is reduced by about 4MB.
19 | * Upload all the APKs to the Play Store and people will download
20 | * the correct one based on the CPU architecture of their device.
21 | */
22 | def enableSeparateBuildPerCPUArchitecture = false
23 |
24 | android {
25 | ndkVersion rootProject.ext.ndkVersion
26 |
27 | compileSdkVersion rootProject.ext.compileSdkVersion
28 |
29 | compileOptions {
30 | sourceCompatibility JavaVersion.VERSION_11
31 | targetCompatibility JavaVersion.VERSION_11
32 | }
33 |
34 | defaultConfig {
35 | applicationId "com.discordrn"
36 | minSdkVersion rootProject.ext.minSdkVersion
37 | targetSdkVersion rootProject.ext.targetSdkVersion
38 | versionCode 1
39 | versionName "1.0"
40 |
41 | buildConfigField "boolean", "PRINTER_LOGS", "false"
42 | }
43 |
44 | buildFeatures {
45 | compose true
46 | }
47 |
48 | composeOptions {
49 | kotlinCompilerExtensionVersion rootProject.ext.compose_version
50 | }
51 |
52 | kotlinOptions {
53 | jvmTarget = "11"
54 | }
55 |
56 | splits {
57 | abi {
58 | reset()
59 | enable enableSeparateBuildPerCPUArchitecture
60 | universalApk false // If true, also generate a universal APK
61 | include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
62 | }
63 | }
64 | signingConfigs {
65 | debug {
66 | storeFile file('debug.keystore')
67 | storePassword 'android'
68 | keyAlias 'androiddebugkey'
69 | keyPassword 'android'
70 | }
71 | }
72 | buildTypes {
73 | debug {
74 | signingConfig signingConfigs.debug
75 | }
76 | release {
77 | // Caution! In production, you need to generate your own keystore file.
78 | // see https://reactnative.dev/docs/signed-apk-android.
79 | signingConfig signingConfigs.debug
80 | minifyEnabled false
81 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
82 | }
83 | }
84 |
85 | // applicationVariants are e.g. debug, release
86 | applicationVariants.all { variant ->
87 | variant.outputs.each { output ->
88 | // For each separate APK per architecture, set a unique version code as described here:
89 | // https://developer.android.com/studio/build/configure-apk-splits.html
90 | // Example: versionCode 1 will generate 1001 for armeabi-v7a, 1002 for x86, etc.
91 | def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
92 | def abi = output.getFilter(com.android.build.OutputFile.ABI)
93 | if (abi != null) { // null for the universal-debug, universal-release variants
94 | output.versionCodeOverride =
95 | defaultConfig.versionCode * 1000 + versionCodes.get(abi)
96 | }
97 |
98 | }
99 | }
100 | }
101 |
102 | dependencies {
103 | implementation fileTree(dir: "libs", include: ["*.jar"])
104 | //noinspection GradleDynamicVersion
105 | implementation "com.facebook.react:react-native:+" // From node_modules
106 |
107 | implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
108 |
109 | debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
110 | exclude group: 'com.facebook.fbjni'
111 | }
112 |
113 | debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
114 | exclude group: 'com.facebook.flipper'
115 | exclude group: 'com.squareup.okhttp3', module: 'okhttp'
116 | }
117 |
118 | debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") {
119 | exclude group: 'com.facebook.flipper'
120 | }
121 |
122 | def hermesPath = "../../node_modules/hermes-engine/android/";
123 | debugImplementation files(hermesPath + "hermes-debug.aar")
124 | releaseImplementation files(hermesPath + "hermes-release.aar")
125 |
126 | implementation 'com.github.alanvan0502:segmented-control-group:v1.0'
127 | implementation 'androidx.palette:palette-ktx:1.0.0'
128 | implementation 'com.github.khoyron:Actionsheet-android:4'
129 | implementation 'androidx.recyclerview:recyclerview:1.2.1'
130 | implementation 'com.google.code.gson:gson:2.8.9'
131 | implementation 'org.danilopianini:gson-extras:0.4.0'
132 |
133 | implementation "androidx.appcompat:appcompat:1.3.1"
134 | implementation "androidx.core:core-ktx:$androidXCore"
135 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
136 | implementation "androidx.compose.compiler:compiler:$compose_version"
137 | implementation "androidx.compose.runtime:runtime:1.1.0-rc01"
138 | implementation "androidx.compose.ui:ui:1.0.5"
139 | implementation 'androidx.compose.material:material:1.0.5'
140 |
141 | implementation('com.google.accompanist:accompanist-appcompat-theme:0.22.0-rc') {
142 | exclude group: 'androidx.appcompat', module: 'appcompat'
143 | }
144 |
145 | implementation "com.github.skydoves:landscapist-fresco:1.4.5"
146 | }
147 |
148 | apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
149 |
150 | static def generateMD5(File file) {
151 | file.withInputStream {
152 | new DigestInputStream(it, MessageDigest.getInstance('MD5')).withStream {
153 | it.eachByte {}
154 | it.messageDigest.digest().encodeHex() as String
155 | }
156 | }
157 | }
158 |
159 | task downloadAssets {
160 | doLast {
161 | buildDir.mkdirs()
162 | File manifestFile = new File(buildDir, "manifest.json")
163 | if (!manifestFile.exists()) {
164 | new URL("https://discord.com/android/90.0/manifest.json").withInputStream { i -> manifestFile.withOutputStream { it << i } }
165 | }
166 | assert generateMD5(manifestFile) == "7aa00a9db0b647f6076e464401c05977"
167 |
168 | def json = new JsonSlurper().parse(manifestFile)
169 | def commit = json.metadata.commit
170 |
171 | json.hashes.entrySet().parallelStream().forEach { entry ->
172 | def path = entry.key
173 | def hash = entry.value
174 | def file = new File(projectDir, path.substring(4).replace("index.android.bundle", "discord.android.bundle"))
175 |
176 | if (!file.exists()) {
177 | file.parentFile.mkdirs()
178 | new URL("https://discord.com/assets/android/$commit/$path").withInputStream { i -> file.withOutputStream { it << i } }
179 | }
180 |
181 | assert generateMD5(file) == hash
182 | }
183 | }
184 | }
185 | tasks.named("preBuild") { dependsOn("downloadAssets") }
186 |
--------------------------------------------------------------------------------
/android/app/src/main/java/com/discordrn/views/DCDChatList.kt:
--------------------------------------------------------------------------------
1 | package com.discordrn.views
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.*
5 | import androidx.compose.foundation.lazy.LazyColumn
6 | import androidx.compose.foundation.lazy.items
7 | import androidx.compose.foundation.shape.CircleShape
8 | import androidx.compose.material.Colors
9 | import androidx.compose.material.MaterialTheme
10 | import androidx.compose.material.Text
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.runtime.mutableStateListOf
13 | import androidx.compose.ui.Alignment
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.draw.clip
16 | import androidx.compose.ui.graphics.Color
17 | import androidx.compose.ui.platform.ComposeView
18 | import androidx.compose.ui.platform.LocalContext
19 | import androidx.compose.ui.text.*
20 | import androidx.compose.ui.text.font.FontStyle
21 | import androidx.compose.ui.text.font.FontWeight
22 | import androidx.compose.ui.text.style.TextDecoration
23 | import androidx.compose.ui.unit.dp
24 | import androidx.compose.ui.unit.sp
25 | import com.discordrn.BuildConfig
26 | import com.discordrn.R
27 | import com.discordrn.models.Message
28 | import com.discordrn.models.MessageContent
29 | import com.facebook.react.uimanager.SimpleViewManager
30 | import com.facebook.react.uimanager.ThemedReactContext
31 | import com.google.accompanist.appcompattheme.AppCompatTheme
32 | import com.skydoves.landscapist.fresco.FrescoImage
33 |
34 | class DCDChatList : SimpleViewManager() {
35 |
36 | companion object {
37 | private val messages = mutableStateListOf()
38 |
39 | @JvmStatic
40 | fun addMessage(message: Message) {
41 | messages.add(message)
42 | }
43 |
44 | @JvmStatic
45 | fun clearMessages() {
46 | messages.clear()
47 | }
48 |
49 | fun getDefaultAvatarUri(name: String) = "res:/" + when (name) {
50 | "images_ios_avatars_default_avatar_1" -> R.drawable.images_ios_avatars_default_avatar_1
51 | "images_ios_avatars_default_avatar_2" -> R.drawable.images_ios_avatars_default_avatar_2
52 | "images_ios_avatars_default_avatar_3" -> R.drawable.images_ios_avatars_default_avatar_3
53 | "images_ios_avatars_default_avatar_4" -> R.drawable.images_ios_avatars_default_avatar_4
54 | else -> R.drawable.images_ios_avatars_default_avatar_0
55 | }
56 | }
57 |
58 | override fun getName() = "DCDChatList"
59 |
60 | override fun createViewInstance(reactContext: ThemedReactContext) =
61 | ComposeView(reactContext).apply {
62 | setContent {
63 | AppCompatTheme(context = reactContext) {
64 | LazyColumn(
65 | modifier = Modifier
66 | .fillMaxWidth()
67 | .wrapContentHeight(),
68 | reverseLayout = true
69 | ) {
70 | items(messages) { message ->
71 | WidgetChatMessage(
72 | modifier = Modifier
73 | .fillMaxWidth()
74 | .padding(6.dp),
75 | message = message,
76 | )
77 | }
78 | }
79 | }
80 | }
81 | }
82 |
83 | @Composable
84 | private fun WidgetChatMessage(
85 | message: Message,
86 | modifier: Modifier = Modifier
87 | ) {
88 | val messageContentString = parseMessageContentToSpan(message.content)
89 |
90 | Row(
91 | modifier = modifier,
92 | horizontalArrangement = Arrangement.spacedBy(8.dp),
93 | verticalAlignment = Alignment.Top
94 | ) {
95 | if (message.avatarURL != null) {
96 | FrescoImage(
97 | imageUrl = message.avatarURL.run {
98 | if (startsWith("images_")) getDefaultAvatarUri(this) else this },
99 | modifier = Modifier
100 | .size(40.dp)
101 | .clip(CircleShape),
102 | )
103 | Column(
104 | modifier = Modifier
105 | .weight(1f)
106 | .heightIn(min = 40.dp),
107 | verticalArrangement = Arrangement.SpaceEvenly,
108 | ) {
109 | Row {
110 | Text(
111 | text = message.username,
112 | fontSize = 14.sp,
113 | fontWeight = FontWeight.SemiBold,
114 | color = Color(message.colorString)
115 | )
116 | Text(
117 | text = message.timestamp,
118 | fontSize = 12.sp,
119 | color = Color((message.timestampColor as Double).toInt()),
120 | modifier = Modifier.padding(start = 4.dp)
121 | )
122 | }
123 | Text(
124 | text = messageContentString,
125 | style = MaterialTheme.typography.body2,
126 | color = Color.White
127 | )
128 | }
129 | } else {
130 | Text(
131 | text = messageContentString,
132 | style = MaterialTheme.typography.body2,
133 | color = Color.White
134 | )
135 | }
136 | }
137 | }
138 |
139 | @Composable
140 | private fun parseMessageContentToSpan(
141 | contentList: List
142 | ): AnnotatedString = buildAnnotatedString {
143 | for (content in contentList) {
144 | when (content) {
145 | is MessageContent.Text -> append(content.content)
146 | is MessageContent.Link -> {
147 | withStyle(SpanStyle(color = MaterialTheme.colors.blurple)) {
148 | append(content.target)
149 | }
150 | }
151 | is MessageContent.CodeBlock -> {
152 | withStyle(
153 | SpanStyle(
154 | color = Color.LightGray,
155 | background = Color.DarkGray
156 | )
157 | ) {
158 | append(content.content)
159 | }
160 | }
161 | is MessageContent.Mention -> {
162 | withStyle(
163 | SpanStyle(
164 | color = MaterialTheme.colors.blurple,
165 | background = MaterialTheme.colors.blurpleBackground
166 | )
167 | ) {
168 | append('@')
169 | append(content.userId)
170 | }
171 | }
172 | is MessageContent.Emoji -> append(content.content)
173 | is MessageContent.CustomEmoji -> {
174 | append(':')
175 | append(content.alt)
176 | append(':')
177 | }
178 | is MessageContent.Channel -> {
179 | withStyle(
180 | SpanStyle(
181 | color = MaterialTheme.colors.blurple,
182 | background = MaterialTheme.colors.blurpleBackground
183 | )
184 | ) {
185 | append('#')
186 | append(content.channelId)
187 | }
188 | }
189 | is MessageContent.Strong -> {
190 | withStyle(SpanStyle(fontWeight = FontWeight.SemiBold)) {
191 | append(parseMessageContentToSpan(content.content))
192 | }
193 | }
194 | is MessageContent.Emphasis -> {
195 | withStyle(SpanStyle(fontStyle = FontStyle.Italic)) {
196 | append(parseMessageContentToSpan(content.content))
197 | }
198 | }
199 | is MessageContent.Stroke -> {
200 | withStyle(SpanStyle(textDecoration = TextDecoration.LineThrough)) {
201 | append(parseMessageContentToSpan(content.content))
202 | }
203 | }
204 | is MessageContent.Underlined -> {
205 | withStyle(SpanStyle(textDecoration = TextDecoration.Underline)) {
206 | append(parseMessageContentToSpan(content.content))
207 | }
208 | }
209 | is MessageContent.InlineCode -> {
210 | withStyle(
211 | SpanStyle(
212 | color = Color.LightGray,
213 | background = Color.DarkGray
214 | )
215 | ) {
216 | append(content.content)
217 | }
218 | }
219 | else -> {
220 | //TODO
221 | }
222 | }
223 | }
224 | }
225 |
226 | private val Colors.blurple
227 | get() =
228 | Color(88, 101, 242)
229 |
230 | private val Colors.blurpleBackground
231 | get() =
232 | blurple.copy(alpha = 0.3f)
233 |
234 | }
235 |
--------------------------------------------------------------------------------
/android/app/src/main/java/com/facebook/react/CustomReactInstanceManager.java:
--------------------------------------------------------------------------------
1 | package com.facebook.react;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.util.Log;
6 | import android.view.View;
7 |
8 | import androidx.annotation.Nullable;
9 |
10 | import com.facebook.react.bridge.JSBundleLoader;
11 | import com.facebook.react.bridge.JSIModulePackage;
12 | import com.facebook.react.bridge.JavaScriptExecutorFactory;
13 | import com.facebook.react.bridge.NativeModuleCallExceptionHandler;
14 | import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener;
15 | import com.facebook.react.bridge.ReactContext;
16 | import com.facebook.react.bridge.ReadableArray;
17 | import com.facebook.react.common.LifecycleState;
18 | import com.facebook.react.devsupport.RedBoxHandler;
19 | import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener;
20 | import com.facebook.react.devsupport.interfaces.DevOptionHandler;
21 | import com.facebook.react.devsupport.interfaces.DevSplitBundleCallback;
22 | import com.facebook.react.devsupport.interfaces.DevSupportManager;
23 | import com.facebook.react.devsupport.interfaces.ErrorCustomizer;
24 | import com.facebook.react.devsupport.interfaces.PackagerStatusCallback;
25 | import com.facebook.react.devsupport.interfaces.StackFrame;
26 | import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
27 | import com.facebook.react.modules.debug.interfaces.DeveloperSettings;
28 | import com.facebook.react.packagerconnection.RequestHandler;
29 | import com.facebook.react.uimanager.UIImplementationProvider;
30 |
31 | import java.io.File;
32 | import java.lang.reflect.Field;
33 | import java.lang.reflect.InvocationTargetException;
34 | import java.lang.reflect.Method;
35 | import java.util.List;
36 | import java.util.Map;
37 |
38 | public class CustomReactInstanceManager extends ReactInstanceManager {
39 | public CustomReactInstanceManager(Context applicationContext, @Nullable Activity currentActivity, @Nullable DefaultHardwareBackBtnHandler defaultHardwareBackBtnHandler, JavaScriptExecutorFactory javaScriptExecutorFactory, @Nullable JSBundleLoader bundleLoader, @Nullable String jsMainModulePath, List packages, boolean useDeveloperSupport, @Nullable NotThreadSafeBridgeIdleDebugListener bridgeIdleDebugListener, LifecycleState initialLifecycleState, @Nullable UIImplementationProvider mUIImplementationProvider, NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler, @Nullable RedBoxHandler redBoxHandler, boolean lazyViewManagersEnabled, @Nullable DevBundleDownloadListener devBundleDownloadListener, int minNumShakes, int minTimeLeftInFrameForNonBatchedOperationMs, @Nullable JSIModulePackage jsiModulePackage, @Nullable Map customPackagerCommandHandlers) {
40 | super(applicationContext, currentActivity, defaultHardwareBackBtnHandler, javaScriptExecutorFactory, bundleLoader, jsMainModulePath, packages, useDeveloperSupport, bridgeIdleDebugListener, initialLifecycleState, mUIImplementationProvider, nativeModuleCallExceptionHandler, redBoxHandler, lazyViewManagersEnabled, devBundleDownloadListener, minNumShakes, minTimeLeftInFrameForNonBatchedOperationMs, jsiModulePackage, customPackagerCommandHandlers);
41 |
42 | try {
43 | Field field = ReactInstanceManager.class.getDeclaredField("mDevSupportManager");
44 | field.setAccessible(true);
45 | field.set(this, new DevSupportManagerWrapper(getDevSupportManager()));
46 | } catch (NoSuchFieldException | IllegalAccessException e) {
47 | throw new RuntimeException(e);
48 | }
49 | }
50 |
51 | @Override
52 | public void createReactContextInBackground() {
53 | if (!hasStartedCreatingInitialContext()) {
54 | try {
55 | Field field = ReactInstanceManager.class.getDeclaredField("mHasStartedCreatingInitialContext");
56 | field.setAccessible(true);
57 | field.set(this, true);
58 | } catch (NoSuchFieldException | IllegalAccessException e) {
59 | throw new RuntimeException(e);
60 | }
61 |
62 | this.recreateReactContextInBackground();
63 | }
64 | }
65 |
66 | @Override
67 | public void recreateReactContextInBackground() {
68 | try {
69 | Method method = ReactInstanceManager.class.getDeclaredMethod("recreateReactContextInBackgroundFromBundleLoader");
70 | method.setAccessible(true);
71 | method.invoke(this);
72 | } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
73 | throw new RuntimeException(e);
74 | }
75 | }
76 |
77 | public static class DevSupportManagerWrapper implements DevSupportManager {
78 | private static final String TAG = "DevSupportManager";
79 |
80 | private final DevSupportManager wrapped;
81 |
82 | public DevSupportManagerWrapper(DevSupportManager wrapped) {
83 | this.wrapped = wrapped;
84 | }
85 |
86 | @Override
87 | public void showNewJavaError(String message, Throwable e) {
88 | wrapped.showNewJavaError(message, e);
89 | }
90 |
91 | @Override
92 | public void addCustomDevOption(String optionName, DevOptionHandler optionHandler) {
93 | wrapped.addCustomDevOption(optionName, optionHandler);
94 | }
95 |
96 | @Nullable
97 | @Override
98 | public View createRootView(String appKey) {
99 | return wrapped.createRootView(appKey);
100 | }
101 |
102 | @Override
103 | public void destroyRootView(View rootView) {
104 | wrapped.destroyRootView(rootView);
105 | }
106 |
107 | @Override
108 | public void showNewJSError(String message, ReadableArray details, int errorCookie) {
109 | wrapped.showNewJSError(message, details, errorCookie);
110 | }
111 |
112 | @Override
113 | public void updateJSError(String message, ReadableArray details, int errorCookie) {
114 | wrapped.updateJSError(message, details, errorCookie);
115 | }
116 |
117 | @Override
118 | public void hideRedboxDialog() {
119 | wrapped.hideRedboxDialog();
120 | }
121 |
122 | @Override
123 | public void showDevOptionsDialog() {
124 | wrapped.showDevOptionsDialog();
125 | }
126 |
127 | @Override
128 | public void setDevSupportEnabled(boolean isDevSupportEnabled) {
129 | wrapped.setDevSupportEnabled(isDevSupportEnabled);
130 | }
131 |
132 | @Override
133 | public void startInspector() {
134 | wrapped.startInspector();
135 | }
136 |
137 | @Override
138 | public void stopInspector() {
139 | wrapped.stopInspector();
140 | }
141 |
142 | @Override
143 | public boolean getDevSupportEnabled() {
144 | return wrapped.getDevSupportEnabled();
145 | }
146 |
147 | @Override
148 | public DeveloperSettings getDevSettings() {
149 | return wrapped.getDevSettings();
150 | }
151 |
152 | @Override
153 | public void onNewReactContextCreated(ReactContext reactContext) {
154 | wrapped.onNewReactContextCreated(reactContext);
155 | }
156 |
157 | @Override
158 | public void onReactInstanceDestroyed(ReactContext reactContext) {
159 | wrapped.onReactInstanceDestroyed(reactContext);
160 | }
161 |
162 | @Override
163 | public String getSourceMapUrl() {
164 | return wrapped.getSourceMapUrl();
165 | }
166 |
167 | @Override
168 | public String getSourceUrl() {
169 | return wrapped.getSourceUrl();
170 | }
171 |
172 | @Override
173 | public String getJSBundleURLForRemoteDebugging() {
174 | return wrapped.getJSBundleURLForRemoteDebugging();
175 | }
176 |
177 | @Override
178 | public String getDownloadedJSBundleFile() {
179 | return wrapped.getDownloadedJSBundleFile();
180 | }
181 |
182 | @Override
183 | public boolean hasUpToDateJSBundleInCache() {
184 | return wrapped.hasUpToDateJSBundleInCache();
185 | }
186 |
187 | @Override
188 | public void reloadSettings() {
189 | wrapped.reloadSettings();
190 | }
191 |
192 | @Override
193 | public void handleReloadJS() {
194 | Log.i(TAG, "ignored handleReloadJS");
195 | // wrapped.handleReloadJS();
196 | }
197 |
198 | @Override
199 | public void reloadJSFromServer(String bundleURL) {
200 | Log.i(TAG, "ignored reloadJSFromServer");
201 | // wrapped.reloadJSFromServer(bundleURL);
202 | }
203 |
204 | @Override
205 | public void loadSplitBundleFromServer(String bundlePath, DevSplitBundleCallback callback) {
206 | wrapped.loadSplitBundleFromServer(bundlePath, callback);
207 | }
208 |
209 | @Override
210 | public void isPackagerRunning(PackagerStatusCallback callback) {
211 | wrapped.isPackagerRunning(callback);
212 | }
213 |
214 | @Override
215 | public void setHotModuleReplacementEnabled(boolean isHotModuleReplacementEnabled) {
216 | wrapped.setHotModuleReplacementEnabled(isHotModuleReplacementEnabled);
217 | }
218 |
219 | @Override
220 | public void setRemoteJSDebugEnabled(boolean isRemoteJSDebugEnabled) {
221 | wrapped.setRemoteJSDebugEnabled(isRemoteJSDebugEnabled);
222 | }
223 |
224 | @Override
225 | public void setFpsDebugEnabled(boolean isFpsDebugEnabled) {
226 | wrapped.setRemoteJSDebugEnabled(isFpsDebugEnabled);
227 | }
228 |
229 | @Override
230 | public void toggleElementInspector() {
231 | wrapped.toggleElementInspector();
232 | }
233 |
234 | @Nullable
235 | @Override
236 | public File downloadBundleResourceFromUrlSync(String resourceURL, File outputFile) {
237 | return wrapped.downloadBundleResourceFromUrlSync(resourceURL, outputFile);
238 | }
239 |
240 | @Nullable
241 | @Override
242 | public String getLastErrorTitle() {
243 | return wrapped.getLastErrorTitle();
244 | }
245 |
246 | @Nullable
247 | @Override
248 | public StackFrame[] getLastErrorStack() {
249 | return wrapped.getLastErrorStack();
250 | }
251 |
252 | @Override
253 | public void registerErrorCustomizer(ErrorCustomizer errorCustomizer) {
254 | wrapped.registerErrorCustomizer(errorCustomizer);
255 | }
256 |
257 | @Override
258 | public void setPackagerLocationCustomizer(PackagerLocationCustomizer packagerLocationCustomizer) {
259 | wrapped.setPackagerLocationCustomizer(packagerLocationCustomizer);
260 | }
261 |
262 | @Override
263 | public void handleException(Exception e) {
264 | wrapped.handleException(e);
265 | }
266 | }
267 | }
268 |
--------------------------------------------------------------------------------
/preload/pnpm-lock.yaml:
--------------------------------------------------------------------------------
1 | lockfileVersion: 5.3
2 |
3 | specifiers:
4 | esbuild: ^0.14.10
5 | react-devtools-core: ^4.22.1
6 | rollup: ^2.63.0
7 | rollup-plugin-esbuild: ^4.8.2
8 | rollup-plugin-string: ^3.0.0
9 |
10 | dependencies:
11 | react-devtools-core: 4.22.1
12 |
13 | devDependencies:
14 | esbuild: 0.14.11
15 | rollup: 2.64.0
16 | rollup-plugin-esbuild: 4.8.2_esbuild@0.14.11+rollup@2.64.0
17 | rollup-plugin-string: 3.0.0
18 |
19 | packages:
20 |
21 | /@rollup/pluginutils/4.1.2:
22 | resolution: {integrity: sha512-ROn4qvkxP9SyPeHaf7uQC/GPFY6L/OWy9+bd9AwcjOAWQwxRscoEyAUD8qCY5o5iL4jqQwoLk2kaTKJPb/HwzQ==}
23 | engines: {node: '>= 8.0.0'}
24 | dependencies:
25 | estree-walker: 2.0.2
26 | picomatch: 2.3.1
27 | dev: true
28 |
29 | /debug/4.3.3:
30 | resolution: {integrity: sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==}
31 | engines: {node: '>=6.0'}
32 | peerDependencies:
33 | supports-color: '*'
34 | peerDependenciesMeta:
35 | supports-color:
36 | optional: true
37 | dependencies:
38 | ms: 2.1.2
39 | dev: true
40 |
41 | /es-module-lexer/0.9.3:
42 | resolution: {integrity: sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==}
43 | dev: true
44 |
45 | /esbuild-android-arm64/0.14.11:
46 | resolution: {integrity: sha512-6iHjgvMnC/SzDH8TefL+/3lgCjYWwAd1LixYfmz/TBPbDQlxcuSkX0yiQgcJB9k+ibZ54yjVXziIwGdlc+6WNw==}
47 | cpu: [arm64]
48 | os: [android]
49 | requiresBuild: true
50 | dev: true
51 | optional: true
52 |
53 | /esbuild-darwin-64/0.14.11:
54 | resolution: {integrity: sha512-olq84ikh6TiBcrs3FnM4eR5VPPlcJcdW8BnUz/lNoEWYifYQ+Po5DuYV1oz1CTFMw4k6bQIZl8T3yxL+ZT2uvQ==}
55 | cpu: [x64]
56 | os: [darwin]
57 | requiresBuild: true
58 | dev: true
59 | optional: true
60 |
61 | /esbuild-darwin-arm64/0.14.11:
62 | resolution: {integrity: sha512-Jj0ieWLREPBYr/TZJrb2GFH8PVzDqiQWavo1pOFFShrcmHWDBDrlDxPzEZ67NF/Un3t6sNNmeI1TUS/fe1xARg==}
63 | cpu: [arm64]
64 | os: [darwin]
65 | requiresBuild: true
66 | dev: true
67 | optional: true
68 |
69 | /esbuild-freebsd-64/0.14.11:
70 | resolution: {integrity: sha512-C5sT3/XIztxxz/zwDjPRHyzj/NJFOnakAanXuyfLDwhwupKPd76/PPHHyJx6Po6NI6PomgVp/zi6GRB8PfrOTA==}
71 | cpu: [x64]
72 | os: [freebsd]
73 | requiresBuild: true
74 | dev: true
75 | optional: true
76 |
77 | /esbuild-freebsd-arm64/0.14.11:
78 | resolution: {integrity: sha512-y3Llu4wbs0bk4cwjsdAtVOesXb6JkdfZDLKMt+v1U3tOEPBdSu6w8796VTksJgPfqvpX22JmPLClls0h5p+L9w==}
79 | cpu: [arm64]
80 | os: [freebsd]
81 | requiresBuild: true
82 | dev: true
83 | optional: true
84 |
85 | /esbuild-linux-32/0.14.11:
86 | resolution: {integrity: sha512-Cg3nVsxArjyLke9EuwictFF3Sva+UlDTwHIuIyx8qpxRYAOUTmxr2LzYrhHyTcGOleLGXUXYsnUVwKqnKAgkcg==}
87 | cpu: [ia32]
88 | os: [linux]
89 | requiresBuild: true
90 | dev: true
91 | optional: true
92 |
93 | /esbuild-linux-64/0.14.11:
94 | resolution: {integrity: sha512-oeR6dIrrojr8DKVrxtH3xl4eencmjsgI6kPkDCRIIFwv4p+K7ySviM85K66BN01oLjzthpUMvBVfWSJkBLeRbg==}
95 | cpu: [x64]
96 | os: [linux]
97 | requiresBuild: true
98 | dev: true
99 | optional: true
100 |
101 | /esbuild-linux-arm/0.14.11:
102 | resolution: {integrity: sha512-vcwskfD9g0tojux/ZaTJptJQU3a7YgTYsptK1y6LQ/rJmw7U5QJvboNawqM98Ca3ToYEucfCRGbl66OTNtp6KQ==}
103 | cpu: [arm]
104 | os: [linux]
105 | requiresBuild: true
106 | dev: true
107 | optional: true
108 |
109 | /esbuild-linux-arm64/0.14.11:
110 | resolution: {integrity: sha512-+e6ZCgTFQYZlmg2OqLkg1jHLYtkNDksxWDBWNtI4XG4WxuOCUErLqfEt9qWjvzK3XBcCzHImrajkUjO+rRkbMg==}
111 | cpu: [arm64]
112 | os: [linux]
113 | requiresBuild: true
114 | dev: true
115 | optional: true
116 |
117 | /esbuild-linux-mips64le/0.14.11:
118 | resolution: {integrity: sha512-Rrs99L+p54vepmXIb87xTG6ukrQv+CzrM8eoeR+r/OFL2Rg8RlyEtCeshXJ2+Q66MXZOgPJaokXJZb9snq28bw==}
119 | cpu: [mips64el]
120 | os: [linux]
121 | requiresBuild: true
122 | dev: true
123 | optional: true
124 |
125 | /esbuild-linux-ppc64le/0.14.11:
126 | resolution: {integrity: sha512-JyzziGAI0D30Vyzt0HDihp4s1IUtJ3ssV2zx9O/c+U/dhUHVP2TmlYjzCfCr2Q6mwXTeloDcLS4qkyvJtYptdQ==}
127 | cpu: [ppc64]
128 | os: [linux]
129 | requiresBuild: true
130 | dev: true
131 | optional: true
132 |
133 | /esbuild-linux-s390x/0.14.11:
134 | resolution: {integrity: sha512-DoThrkzunZ1nfRGoDN6REwmo8ZZWHd2ztniPVIR5RMw/Il9wiWEYBahb8jnMzQaSOxBsGp0PbyJeVLTUatnlcw==}
135 | cpu: [s390x]
136 | os: [linux]
137 | requiresBuild: true
138 | dev: true
139 | optional: true
140 |
141 | /esbuild-netbsd-64/0.14.11:
142 | resolution: {integrity: sha512-12luoRQz+6eihKYh1zjrw0CBa2aw3twIiHV/FAfjh2NEBDgJQOY4WCEUEN+Rgon7xmLh4XUxCQjnwrvf8zhACw==}
143 | cpu: [x64]
144 | os: [netbsd]
145 | requiresBuild: true
146 | dev: true
147 | optional: true
148 |
149 | /esbuild-openbsd-64/0.14.11:
150 | resolution: {integrity: sha512-l18TZDjmvwW6cDeR4fmizNoxndyDHamGOOAenwI4SOJbzlJmwfr0jUgjbaXCUuYVOA964siw+Ix+A+bhALWg8Q==}
151 | cpu: [x64]
152 | os: [openbsd]
153 | requiresBuild: true
154 | dev: true
155 | optional: true
156 |
157 | /esbuild-sunos-64/0.14.11:
158 | resolution: {integrity: sha512-bmYzDtwASBB8c+0/HVOAiE9diR7+8zLm/i3kEojUH2z0aIs6x/S4KiTuT5/0VKJ4zk69kXel1cNWlHBMkmavQg==}
159 | cpu: [x64]
160 | os: [sunos]
161 | requiresBuild: true
162 | dev: true
163 | optional: true
164 |
165 | /esbuild-windows-32/0.14.11:
166 | resolution: {integrity: sha512-J1Ys5hMid8QgdY00OBvIolXgCQn1ARhYtxPnG6ESWNTty3ashtc4+As5nTrsErnv8ZGUcWZe4WzTP/DmEVX1UQ==}
167 | cpu: [ia32]
168 | os: [win32]
169 | requiresBuild: true
170 | dev: true
171 | optional: true
172 |
173 | /esbuild-windows-64/0.14.11:
174 | resolution: {integrity: sha512-h9FmMskMuGeN/9G9+LlHPAoiQk9jlKDUn9yA0MpiGzwLa82E7r1b1u+h2a+InprbSnSLxDq/7p5YGtYVO85Mlg==}
175 | cpu: [x64]
176 | os: [win32]
177 | requiresBuild: true
178 | dev: true
179 | optional: true
180 |
181 | /esbuild-windows-arm64/0.14.11:
182 | resolution: {integrity: sha512-dZp7Krv13KpwKklt9/1vBFBMqxEQIO6ri7Azf8C+ob4zOegpJmha2XY9VVWP/OyQ0OWk6cEeIzMJwInRZrzBUQ==}
183 | cpu: [arm64]
184 | os: [win32]
185 | requiresBuild: true
186 | dev: true
187 | optional: true
188 |
189 | /esbuild/0.14.11:
190 | resolution: {integrity: sha512-xZvPtVj6yecnDeFb3KjjCM6i7B5TCAQZT77kkW/CpXTMnd6VLnRPKrUB1XHI1pSq6a4Zcy3BGueQ8VljqjDGCg==}
191 | hasBin: true
192 | requiresBuild: true
193 | optionalDependencies:
194 | esbuild-android-arm64: 0.14.11
195 | esbuild-darwin-64: 0.14.11
196 | esbuild-darwin-arm64: 0.14.11
197 | esbuild-freebsd-64: 0.14.11
198 | esbuild-freebsd-arm64: 0.14.11
199 | esbuild-linux-32: 0.14.11
200 | esbuild-linux-64: 0.14.11
201 | esbuild-linux-arm: 0.14.11
202 | esbuild-linux-arm64: 0.14.11
203 | esbuild-linux-mips64le: 0.14.11
204 | esbuild-linux-ppc64le: 0.14.11
205 | esbuild-linux-s390x: 0.14.11
206 | esbuild-netbsd-64: 0.14.11
207 | esbuild-openbsd-64: 0.14.11
208 | esbuild-sunos-64: 0.14.11
209 | esbuild-windows-32: 0.14.11
210 | esbuild-windows-64: 0.14.11
211 | esbuild-windows-arm64: 0.14.11
212 | dev: true
213 |
214 | /estree-walker/0.6.1:
215 | resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==}
216 | dev: true
217 |
218 | /estree-walker/2.0.2:
219 | resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
220 | dev: true
221 |
222 | /fsevents/2.3.2:
223 | resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
224 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
225 | os: [darwin]
226 | requiresBuild: true
227 | dev: true
228 | optional: true
229 |
230 | /joycon/3.1.1:
231 | resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
232 | engines: {node: '>=10'}
233 | dev: true
234 |
235 | /jsonc-parser/3.0.0:
236 | resolution: {integrity: sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==}
237 | dev: true
238 |
239 | /ms/2.1.2:
240 | resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
241 | dev: true
242 |
243 | /picomatch/2.3.1:
244 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
245 | engines: {node: '>=8.6'}
246 | dev: true
247 |
248 | /react-devtools-core/4.22.1:
249 | resolution: {integrity: sha512-pvpNDHE7p0FtcCmIWGazoY8LLVfBI9sw0Kf10kdHhPI9Tzt3OG/qEt16GrAbE0keuna5WzX3r1qPKVjqOqsuUg==}
250 | dependencies:
251 | shell-quote: 1.7.3
252 | ws: 7.5.6
253 | transitivePeerDependencies:
254 | - bufferutil
255 | - utf-8-validate
256 | dev: false
257 |
258 | /rollup-plugin-esbuild/4.8.2_esbuild@0.14.11+rollup@2.64.0:
259 | resolution: {integrity: sha512-wsaYNOjzTb6dN1qCIZsMZ7Q0LWiPJklYs2TDI8vJA2LUbvtPUY+17TC8C0vSat3jPMInfR9XWKdA7ttuwkjsGQ==}
260 | engines: {node: '>=12'}
261 | peerDependencies:
262 | esbuild: '>=0.10.1'
263 | rollup: ^1.20.0 || ^2.0.0
264 | dependencies:
265 | '@rollup/pluginutils': 4.1.2
266 | debug: 4.3.3
267 | es-module-lexer: 0.9.3
268 | esbuild: 0.14.11
269 | joycon: 3.1.1
270 | jsonc-parser: 3.0.0
271 | rollup: 2.64.0
272 | transitivePeerDependencies:
273 | - supports-color
274 | dev: true
275 |
276 | /rollup-plugin-string/3.0.0:
277 | resolution: {integrity: sha512-vqyzgn9QefAgeKi+Y4A7jETeIAU1zQmS6VotH6bzm/zmUQEnYkpIGRaOBPY41oiWYV4JyBoGAaBjYMYuv+6wVw==}
278 | dependencies:
279 | rollup-pluginutils: 2.8.2
280 | dev: true
281 |
282 | /rollup-pluginutils/2.8.2:
283 | resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==}
284 | dependencies:
285 | estree-walker: 0.6.1
286 | dev: true
287 |
288 | /rollup/2.64.0:
289 | resolution: {integrity: sha512-+c+lbw1lexBKSMb1yxGDVfJ+vchJH3qLbmavR+awDinTDA2C5Ug9u7lkOzj62SCu0PKUExsW36tpgW7Fmpn3yQ==}
290 | engines: {node: '>=10.0.0'}
291 | hasBin: true
292 | optionalDependencies:
293 | fsevents: 2.3.2
294 | dev: true
295 |
296 | /shell-quote/1.7.3:
297 | resolution: {integrity: sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==}
298 | dev: false
299 |
300 | /ws/7.5.6:
301 | resolution: {integrity: sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==}
302 | engines: {node: '>=8.3.0'}
303 | peerDependencies:
304 | bufferutil: ^4.0.1
305 | utf-8-validate: ^5.0.2
306 | peerDependenciesMeta:
307 | bufferutil:
308 | optional: true
309 | utf-8-validate:
310 | optional: true
311 | dev: false
312 |
--------------------------------------------------------------------------------
/preload/src/driver.js:
--------------------------------------------------------------------------------
1 | // https://github.com/aveq-research/localforage-asyncstorage-driver/blob/master/src/main.js
2 |
3 | /**
4 | * This implementation adds localforage support for react-native async storage,
5 | * as documented in: https://facebook.github.io/react-native/docs/asyncstorage
6 | *
7 | * The implementation itself is based on the already existing localstorage
8 | * support implementation. A future work could try to unite similar code
9 | * structures of both implementations.
10 | */
11 |
12 | export let AsyncStorage;
13 |
14 | async function _withCallback(callback, promise) {
15 | try {
16 | const ret = await promise();
17 |
18 | if (callback) {
19 | callback(null, ret);
20 | }
21 |
22 | return ret;
23 | }
24 | catch (e) {
25 | if (callback) {
26 | callback(e);
27 | }
28 |
29 | throw e;
30 | }
31 | }
32 |
33 | function _getKeyPrefix(options, defaultConfig) {
34 | let keyPrefix = `${options.name}/`;
35 | if (options.storeName && options.storeName !== defaultConfig.storeName) {
36 | keyPrefix += `${options.storeName}/`;
37 | }
38 |
39 | return keyPrefix;
40 | }
41 |
42 | async function _iterate(dbInfo, keys, iterator, iterationNumber) {
43 | const key = keys.shift();
44 | if (key === undefined) {
45 | return;
46 | }
47 |
48 | const keyPrefix = dbInfo.keyPrefix;
49 | if (key.indexOf(keyPrefix) === 0) {
50 | const serializedValue = await AsyncStorage.getItem(key);
51 |
52 | // If a result was found, parse it from the serialized
53 | // string into a JS object. If result isn't truthy, the
54 | // key is likely undefined and we'll pass it straight
55 | // to the iterator.
56 | const value = serializedValue && dbInfo.serializer
57 | ? dbInfo.serializer.deserialize(serializedValue)
58 | : serializedValue;
59 |
60 | const itVal = iterator(
61 | value,
62 | key.substring(keyPrefix.length),
63 | iterationNumber++
64 | );
65 |
66 | if (itVal !== undefined) {
67 | return itVal;
68 | }
69 | }
70 |
71 | return _iterate(dbInfo, keys, iterator, iterationNumber);
72 | }
73 |
74 | const defaultDriver = {
75 |
76 | /**
77 | * The name of this driver!
78 | */
79 | _driver: 'rnAsyncStorageWrapper',
80 |
81 | /**
82 | * Returns true if the driver can support the React Native Async Storage on the executed platform, false otherwise.
83 | * @returns {boolean}
84 | * @private
85 | */
86 | // eslint-disable-next-line require-await
87 | _support: async function () {
88 | try {
89 | return (
90 | typeof AsyncStorage !== 'undefined' &&
91 | 'setItem' in AsyncStorage &&
92 | !!AsyncStorage.setItem
93 | );
94 | }
95 | catch (e) {
96 | return false;
97 | }
98 | },
99 |
100 | /**
101 | * Config the localStorage backend, using options set in the config.
102 | * @param serializer
103 | * @param options
104 | * @returns {Promise}
105 | * @private
106 | */
107 | _initStorage: async function (serializer, options = {}) {
108 | const dbInfo = Object.assign({}, options);
109 | dbInfo.keyPrefix = _getKeyPrefix(options, this._defaultConfig);
110 |
111 | const localStorageTestKey = '_localforage_support_test';
112 | await AsyncStorage.setItem(localStorageTestKey, 'it-works');
113 | await AsyncStorage.removeItem(localStorageTestKey);
114 | // if these operations throw, the storage initialization fails, because something is wrong with the storage!
115 |
116 | if (serializer === undefined) {
117 | serializer = await this.getSerializer();
118 | }
119 |
120 | if (serializer) {
121 | dbInfo.serializer = serializer;
122 | }
123 |
124 | this._dbInfo = dbInfo;
125 | return Promise.resolve();
126 | },
127 |
128 | /**
129 | * Iterate over all items in the store.
130 | *
131 | * @param iterator the method to execute on each item individually!
132 | * @param callback
133 | * @returns {Promise}
134 | */
135 | iterate: function (iterator, callback) {
136 | return _withCallback(callback, async () => {
137 | await this.ready();
138 |
139 | const allKeys = await AsyncStorage.getAllKeys();
140 | return _iterate(this._dbInfo, allKeys, iterator, 0);
141 | });
142 | },
143 |
144 | /**
145 | * Retrieve an item from the store. Unlike the original async_storage
146 | * library in Gaia, we don't modify return values at all. If a key's value
147 | * is `undefined`, we pass that value to the callback function.
148 | *
149 | * @param key
150 | * @param callback
151 | * @returns {Promise}
152 | */
153 | getItem: function (key, callback) {
154 | return _withCallback(callback, async () => {
155 | key = String(key);
156 | await this.ready();
157 | const dbInfo = this._dbInfo;
158 | const item = await AsyncStorage.getItem(`${dbInfo.keyPrefix}${key}`);
159 |
160 | return (dbInfo.serializer && item) ? dbInfo.serializer.deserialize(item) : item;
161 | });
162 | },
163 |
164 | /**
165 | * Set a key's value and run an optional callback once the value is set.
166 | * Unlike Gaia's implementation, the callback function is passed the value,
167 | * in case you want to operate on that value only after you're sure it
168 | * saved, or something like that.
169 | *
170 | * @param key
171 | * @param value
172 | * @param callback
173 | * @returns {Promise}
174 | */
175 | setItem: function (key, value, callback) {
176 | return _withCallback(callback, async () => {
177 | key = String(key);
178 | await this.ready();
179 | // Convert undefined values to null.
180 | // https://github.com/mozilla/localForage/pull/42
181 | if (value === undefined) {
182 | value = null;
183 | }
184 |
185 | // Save the original value to pass to the callback.
186 | const originalValue = value; //_copyValue(value);
187 | const dbInfo = this._dbInfo;
188 |
189 | return new Promise((resolve, reject) => {
190 | async function writeToStorage(valueToWrite) {
191 | await AsyncStorage.setItem(`${dbInfo.keyPrefix}${key}`, valueToWrite);
192 | return originalValue;
193 | }
194 |
195 | if (dbInfo.serializer) {
196 | dbInfo.serializer.serialize(value, (serializedValue, error) => {
197 | if (error) {
198 | reject(error);
199 | return;
200 | }
201 |
202 | writeToStorage(serializedValue)
203 | .then(resolve)
204 | .catch(reject);
205 | });
206 | }
207 | else {
208 | writeToStorage(value)
209 | .then(resolve)
210 | .catch(reject);
211 | }
212 | });
213 | });
214 | },
215 |
216 | /**
217 | * Remove an item from the store, nice and simple.
218 | * @param key
219 | * @param callback
220 | * @returns {Promise}
221 | */
222 | removeItem: function (key, callback) {
223 | return _withCallback(callback, async () => {
224 | key = String(key);
225 | await this.ready();
226 | await AsyncStorage.removeItem(`${this._dbInfo.keyPrefix}${key}`);
227 | });
228 | },
229 |
230 | /**
231 | * Remove all keys from the datastore, effectively destroying all data in
232 | * the app's key/value store!
233 | *
234 | * @param callback
235 | * @returns {Promise}
236 | */
237 | clear: function (callback) {
238 | return _withCallback(callback, async () => {
239 | await this.ready();
240 | const keyPrefix = this._dbInfo.keyPrefix;
241 | const keysToDelete = [];
242 | const allKeys = await AsyncStorage.getAllKeys();
243 |
244 | for (const key of allKeys) {
245 | if (key.indexOf(keyPrefix) === 0) {
246 | keysToDelete.push(key);
247 | }
248 | }
249 |
250 | await AsyncStorage.multiRemove(keysToDelete);
251 | });
252 | },
253 |
254 | /**
255 | * Supply the number of keys in the datastore to the callback function.
256 | *
257 | * @param callback
258 | * @returns {PromiseLike | Promise}
259 | */
260 | length: function (callback) {
261 | return _withCallback(callback, async () => {
262 | const keys = await this.keys();
263 | return keys.length;
264 | });
265 | },
266 |
267 | /**
268 | * Same as localStorage's key() method, except takes a callback.
269 | * @returns {Promise<*>}
270 | */
271 | key: function (n, callback) {
272 | return _withCallback(callback, async () => {
273 | await this.ready();
274 | const dbInfo = this._dbInfo;
275 | const allKeys = await AsyncStorage.getAllKeys();
276 | const key = allKeys[n];
277 |
278 | return key ? key.substring(dbInfo.keyPrefix.length) : null;
279 | });
280 | },
281 |
282 | keys: function (callback) {
283 | return _withCallback(callback, async () => {
284 | await this.ready();
285 |
286 | const dbInfo = this._dbInfo;
287 | const allKeys = await AsyncStorage.getAllKeys();
288 | const driverKeys = [];
289 |
290 | for (const key of allKeys) {
291 | if (key.indexOf(dbInfo.keyPrefix) === 0) {
292 | driverKeys.push(key.substring(dbInfo.keyPrefix.length));
293 | }
294 | }
295 |
296 | return driverKeys;
297 | });
298 | },
299 |
300 | dropInstance: function (options = {}, callback) {
301 | return _withCallback(callback, async () => {
302 | const currentConfig = this.config();
303 | options.name = options.name || currentConfig.name;
304 | options.storeName = options.storeName || currentConfig.storeName;
305 |
306 | if (options.name === undefined) {
307 | throw Error('Invalid arguments');
308 | }
309 |
310 | const keyPrefix = _getKeyPrefix(options, this._defaultConfig);
311 | const keys = await this.keys();
312 | const keysToDelete = keys.map(k => `${keyPrefix}${k}`);
313 |
314 | return AsyncStorage.multiRemove(keysToDelete);
315 | });
316 | }
317 |
318 | };
319 |
320 | export function driverWithSerialization(serializer) {
321 | const driver = Object.assign({}, defaultDriver, {
322 | _driver: `${defaultDriver._driver}-with${serializer === undefined ? 'DefaultSerializer' : (serializer === null ? 'outSerializer' : 'Serializer')}`,
323 | });
324 |
325 | const orgInitFunctionBody = driver._initStorage;
326 | driver._initStorage = function (options) {
327 | return orgInitFunctionBody.apply(this, [serializer, options]);
328 | };
329 |
330 | return driver;
331 | }
332 |
333 | export function driverWithoutSerialization() {
334 | return driverWithSerialization(null);
335 | }
336 |
--------------------------------------------------------------------------------
/android/app/src/main/java/com/discordrn/modules/RNSentryModule.java:
--------------------------------------------------------------------------------
1 | package com.discordrn.modules;
2 | import android.content.Context;
3 | import android.content.pm.PackageInfo;
4 | import com.facebook.react.bridge.Arguments;
5 | import com.facebook.react.bridge.ReactApplicationContext;
6 | import com.facebook.react.bridge.ReactContextBaseJavaModule;
7 | import com.facebook.react.bridge.ReactMethod;
8 | import com.facebook.react.bridge.WritableMap;
9 | import com.facebook.react.bridge.ReadableMap;
10 | import com.facebook.react.bridge.Promise;
11 | import com.facebook.react.module.annotations.ReactModule;
12 | import java.util.Map;
13 | import java.util.HashMap;
14 | import java.util.logging.Logger;
15 | import java.io.UnsupportedEncodingException;
16 |
17 | @ReactModule(name = RNSentryModule.NAME)
18 | public class RNSentryModule extends ReactContextBaseJavaModule {
19 |
20 | public static final String NAME = "RNSentry";
21 |
22 | private static final Logger logger = Logger.getLogger("react-native-sentry");
23 |
24 | private PackageInfo packageInfo = null;
25 | private boolean didFetchAppStart;
26 | private Object frameMetricsAggregator = null;
27 | private boolean androidXAvailable = true;
28 |
29 | // 700ms to constitute frozen frames.
30 | private static final int FROZEN_FRAME_THRESHOLD = 700;
31 | // 16ms (slower than 60fps) to constitute slow frames.
32 | private static final int SLOW_FRAME_THRESHOLD = 16;
33 |
34 | public RNSentryModule(ReactApplicationContext reactContext) {
35 | super(reactContext);
36 | packageInfo = getPackageInfo(reactContext);
37 | }
38 |
39 | @Override
40 | public String getName() {
41 | return NAME;
42 | }
43 |
44 | @Override
45 | public Map getConstants() {
46 | final Map constants = new HashMap<>();
47 | constants.put("nativeClientAvailable", true);
48 | constants.put("nativeTransport", true);
49 | return constants;
50 | }
51 |
52 | @ReactMethod
53 | public void initNativeSdk(final ReadableMap rnOptions, Promise promise) {
54 | // SentryAndroid.init(this.getReactApplicationContext(), options -> {
55 | // if (rnOptions.hasKey("debug") && rnOptions.getBoolean("debug")) {
56 | // options.setDebug(true);
57 | // logger.setLevel(Level.INFO);
58 | // }
59 | // if (rnOptions.hasKey("dsn") && rnOptions.getString("dsn") != null) {
60 | // String dsn = rnOptions.getString("dsn");
61 | // logger.info(String.format("Starting with DSN: '%s'", dsn));
62 | // options.setDsn(dsn);
63 | // } else {
64 | // // SentryAndroid needs an empty string fallback for the dsn.
65 | // options.setDsn("");
66 | // }
67 | // if (rnOptions.hasKey("maxBreadcrumbs")) {
68 | // options.setMaxBreadcrumbs(rnOptions.getInt("maxBreadcrumbs"));
69 | // }
70 | // if (rnOptions.hasKey("environment") && rnOptions.getString("environment") != null) {
71 | // options.setEnvironment(rnOptions.getString("environment"));
72 | // }
73 | // if (rnOptions.hasKey("release") && rnOptions.getString("release") != null) {
74 | // options.setRelease(rnOptions.getString("release"));
75 | // }
76 | // if (rnOptions.hasKey("dist") && rnOptions.getString("dist") != null) {
77 | // options.setDist(rnOptions.getString("dist"));
78 | // }
79 | // if (rnOptions.hasKey("enableAutoSessionTracking")) {
80 | // options.setEnableAutoSessionTracking(rnOptions.getBoolean("enableAutoSessionTracking"));
81 | // }
82 | // if (rnOptions.hasKey("sessionTrackingIntervalMillis")) {
83 | // options.setSessionTrackingIntervalMillis(rnOptions.getInt("sessionTrackingIntervalMillis"));
84 | // }
85 | // if (rnOptions.hasKey("enableNdkScopeSync")) {
86 | // options.setEnableScopeSync(rnOptions.getBoolean("enableNdkScopeSync"));
87 | // }
88 | // if (rnOptions.hasKey("attachStacktrace")) {
89 | // options.setAttachStacktrace(rnOptions.getBoolean("attachStacktrace"));
90 | // }
91 | // if (rnOptions.hasKey("attachThreads")) {
92 | // // JS use top level stacktraces and android attaches Threads which hides them so
93 | // // by default we hide.
94 | // options.setAttachThreads(rnOptions.getBoolean("attachThreads"));
95 | // }
96 | // if (rnOptions.hasKey("sendDefaultPii")) {
97 | // options.setSendDefaultPii(rnOptions.getBoolean("sendDefaultPii"));
98 | // }
99 | // if (rnOptions.hasKey("enableAutoPerformanceTracking")
100 | // && rnOptions.getBoolean("enableAutoPerformanceTracking")) {
101 | // androidXAvailable = checkAndroidXAvailability();
102 |
103 | // if (androidXAvailable) {
104 | // frameMetricsAggregator = new FrameMetricsAggregator();
105 | // final Activity currentActivity = getCurrentActivity();
106 |
107 | // if (frameMetricsAggregator != null && currentActivity != null) {
108 | // try {
109 | // frameMetricsAggregator.add(currentActivity);
110 | // } catch (Throwable ignored) {
111 | // // throws ConcurrentModification when calling addOnFrameMetricsAvailableListener
112 | // // this is a best effort since we can't reproduce it
113 | // logger.warning("Error adding Activity to frameMetricsAggregator.");
114 | // }
115 | // }
116 | // } else {
117 | // logger.warning("androidx.core' isn't available as a dependency.");
118 | // }
119 | // } else {
120 | // this.disableNativeFramesTracking();
121 | // }
122 |
123 | // options.setBeforeSend((event, hint) -> {
124 | // // React native internally throws a JavascriptException
125 | // // Since we catch it before that, we don't want to send this one
126 | // // because we would send it twice
127 | // try {
128 | // SentryException ex = event.getExceptions().get(0);
129 | // if (null != ex && ex.getType().contains("JavascriptException")) {
130 | // return null;
131 | // }
132 | // } catch (Throwable ignored) {
133 | // // We do nothing
134 | // }
135 |
136 | // setEventOriginTag(event);
137 | // addPackages(event, options.getSdkVersion());
138 |
139 | // return event;
140 | // });
141 |
142 | // if (rnOptions.hasKey("enableNativeCrashHandling") && !rnOptions.getBoolean("enableNativeCrashHandling")) {
143 | // final List integrations = options.getIntegrations();
144 | // for (final Integration integration : integrations) {
145 | // if (integration instanceof UncaughtExceptionHandlerIntegration
146 | // || integration instanceof AnrIntegration || integration instanceof NdkIntegration) {
147 | // integrations.remove(integration);
148 | // }
149 | // }
150 | // }
151 |
152 | // logger.info(String.format("Native Integrations '%s'", options.getIntegrations().toString()));
153 | // });
154 |
155 | promise.resolve(true);
156 | }
157 |
158 | @ReactMethod
159 | public void crash() {
160 | throw new RuntimeException("TEST - Sentry Client Crash (only works in release mode)");
161 | }
162 |
163 | @ReactMethod
164 | public void fetchNativeRelease(Promise promise) {
165 | WritableMap release = Arguments.createMap();
166 | // release.putString("id", packageInfo.packageName);
167 | // release.putString("version", packageInfo.versionName);
168 | // release.putString("build", String.valueOf(packageInfo.versionCode));
169 | release.putString("id", "com.hammerandchisel.discord");
170 | release.putString("version", "");
171 | release.putString("build", "");
172 | promise.resolve(release);
173 | }
174 |
175 | @ReactMethod
176 | public void fetchNativeAppStart(Promise promise) {
177 | // final AppStartState appStartInstance = AppStartState.getInstance();
178 | // final Date appStartTime = appStartInstance.getAppStartTime();
179 | // final Boolean isColdStart = appStartInstance.isColdStart();
180 |
181 | // if (appStartTime == null) {
182 | // logger.warning("App start won't be sent due to missing appStartTime.");
183 | // promise.resolve(null);
184 | // } else if (isColdStart == null) {
185 | // logger.warning("App start won't be sent due to missing isColdStart.");
186 | // promise.resolve(null);
187 | // } else {
188 | // final double appStartTimestamp = (double) appStartTime.getTime();
189 |
190 | // WritableMap appStart = Arguments.createMap();
191 |
192 | // appStart.putDouble("appStartTime", appStartTimestamp);
193 | // appStart.putBoolean("isColdStart", isColdStart);
194 | // appStart.putBoolean("didFetchAppStart", didFetchAppStart);
195 |
196 | // promise.resolve(appStart);
197 | // }
198 | // // This is always set to true, as we would only allow an app start fetch to only
199 | // // happen once in the case of a JS bundle reload, we do not want it to be
200 | // // instrumented again.
201 | didFetchAppStart = true;
202 | WritableMap appStart = Arguments.createMap();
203 |
204 | appStart.putDouble("appStartTime", 0.0);
205 | appStart.putBoolean("isColdStart", true);
206 | appStart.putBoolean("didFetchAppStart", true);
207 |
208 | promise.resolve(appStart);
209 | }
210 |
211 | /**
212 | * Returns frames metrics at the current point in time.
213 | */
214 | @ReactMethod
215 | public void fetchNativeFrames(Promise promise) {
216 | promise.resolve(null);
217 | // if (!isFrameMetricsAggregatorAvailable()) {
218 | // promise.resolve(null);
219 | // } else {
220 | // try {
221 | // int totalFrames = 0;
222 | // int slowFrames = 0;
223 | // int frozenFrames = 0;
224 |
225 | // final SparseIntArray[] framesRates = frameMetricsAggregator.getMetrics();
226 |
227 | // if (framesRates != null) {
228 | // final SparseIntArray totalIndexArray = framesRates[FrameMetricsAggregator.TOTAL_INDEX];
229 | // if (totalIndexArray != null) {
230 | // for (int i = 0; i < totalIndexArray.size(); i++) {
231 | // int frameTime = totalIndexArray.keyAt(i);
232 | // int numFrames = totalIndexArray.valueAt(i);
233 | // totalFrames += numFrames;
234 | // // hard coded values, its also in the official android docs and frame metrics
235 | // // API
236 | // if (frameTime > FROZEN_FRAME_THRESHOLD) {
237 | // // frozen frames, threshold is 700ms
238 | // frozenFrames += numFrames;
239 | // } else if (frameTime > SLOW_FRAME_THRESHOLD) {
240 | // // slow frames, above 16ms, 60 frames/second
241 | // slowFrames += numFrames;
242 | // }
243 | // }
244 | // }
245 | // }
246 |
247 | // WritableMap map = Arguments.createMap();
248 | // map.putInt("totalFrames", totalFrames);
249 | // map.putInt("slowFrames", slowFrames);
250 | // map.putInt("frozenFrames", frozenFrames);
251 |
252 | // promise.resolve(map);
253 | // } catch (Throwable ignored) {
254 | // logger.warning("Error fetching native frames.");
255 | // promise.resolve(null);
256 | // }
257 | // }
258 | }
259 |
260 | @ReactMethod
261 | public void captureEnvelope(String envelope, Promise promise) {
262 | // try {
263 | // final String outboxPath = HubAdapter.getInstance().getOptions().getOutboxPath();
264 |
265 | // if (outboxPath == null) {
266 | // logger.severe(
267 | // "Error retrieving outboxPath. Envelope will not be sent. Is the Android SDK initialized?");
268 | // } else {
269 | // File installation = new File(outboxPath, UUID.randomUUID().toString());
270 | // try (FileOutputStream out = new FileOutputStream(installation)) {
271 | // out.write(envelope.getBytes(Charset.forName("UTF-8")));
272 | // }
273 | // }
274 | // } catch (Throwable ignored) {
275 | // logger.severe("Error reading envelope");
276 | // }
277 | promise.resolve(true);
278 | }
279 |
280 | @ReactMethod
281 | public void getStringBytesLength(String payload, Promise promise) {
282 | try {
283 | promise.resolve(payload.getBytes("UTF-8").length);
284 | } catch (UnsupportedEncodingException e) {
285 | promise.reject(e);
286 | }
287 | }
288 |
289 | private static PackageInfo getPackageInfo(Context ctx) {
290 | // try {
291 | // return ctx.getPackageManager().getPackageInfo(ctx.getPackageName(), 0);
292 | // } catch (PackageManager.NameNotFoundException e) {
293 | // logger.warning("Error getting package info.");
294 | // return null;
295 | // }
296 | return null;
297 | }
298 |
299 | @ReactMethod
300 | public void setUser(final ReadableMap user, final ReadableMap otherUserKeys) {
301 | // Sentry.configureScope(scope -> {
302 | // if (user == null && otherUserKeys == null) {
303 | // scope.setUser(null);
304 | // } else {
305 | // User userInstance = new User();
306 |
307 | // if (user != null) {
308 | // if (user.hasKey("email")) {
309 | // userInstance.setEmail(user.getString("email"));
310 | // }
311 |
312 | // if (user.hasKey("id")) {
313 | // userInstance.setId(user.getString("id"));
314 | // }
315 |
316 | // if (user.hasKey("username")) {
317 | // userInstance.setUsername(user.getString("username"));
318 | // }
319 |
320 | // if (user.hasKey("ip_address")) {
321 | // userInstance.setIpAddress(user.getString("ip_address"));
322 | // }
323 | // }
324 |
325 | // if (otherUserKeys != null) {
326 | // HashMap otherUserKeysMap = new HashMap();
327 | // ReadableMapKeySetIterator it = otherUserKeys.keySetIterator();
328 | // while (it.hasNextKey()) {
329 | // String key = it.nextKey();
330 | // String value = otherUserKeys.getString(key);
331 |
332 | // otherUserKeysMap.put(key, value);
333 | // }
334 |
335 | // userInstance.setOthers(otherUserKeysMap);
336 | // }
337 |
338 | // scope.setUser(userInstance);
339 | // }
340 | // });
341 | }
342 |
343 | @ReactMethod
344 | public void addBreadcrumb(final ReadableMap breadcrumb) {
345 | // Sentry.configureScope(scope -> {
346 | // Breadcrumb breadcrumbInstance = new Breadcrumb();
347 |
348 | // if (breadcrumb.hasKey("message")) {
349 | // breadcrumbInstance.setMessage(breadcrumb.getString("message"));
350 | // }
351 |
352 | // if (breadcrumb.hasKey("type")) {
353 | // breadcrumbInstance.setType(breadcrumb.getString("type"));
354 | // }
355 |
356 | // if (breadcrumb.hasKey("category")) {
357 | // breadcrumbInstance.setCategory(breadcrumb.getString("category"));
358 | // }
359 |
360 | // if (breadcrumb.hasKey("level")) {
361 | // switch (breadcrumb.getString("level")) {
362 | // case "fatal":
363 | // breadcrumbInstance.setLevel(SentryLevel.FATAL);
364 | // break;
365 | // case "warning":
366 | // breadcrumbInstance.setLevel(SentryLevel.WARNING);
367 | // break;
368 | // case "info":
369 | // breadcrumbInstance.setLevel(SentryLevel.INFO);
370 | // break;
371 | // case "debug":
372 | // breadcrumbInstance.setLevel(SentryLevel.DEBUG);
373 | // break;
374 | // case "error":
375 | // breadcrumbInstance.setLevel(SentryLevel.ERROR);
376 | // break;
377 | // default:
378 | // breadcrumbInstance.setLevel(SentryLevel.ERROR);
379 | // break;
380 | // }
381 | // }
382 |
383 | // if (breadcrumb.hasKey("data")) {
384 | // ReadableMap data = breadcrumb.getMap("data");
385 | // ReadableMapKeySetIterator it = data.keySetIterator();
386 | // while (it.hasNextKey()) {
387 | // String key = it.nextKey();
388 | // String value = data.getString(key);
389 |
390 | // breadcrumbInstance.setData(key, value);
391 | // }
392 | // }
393 |
394 | // scope.addBreadcrumb(breadcrumbInstance);
395 | // });
396 | }
397 |
398 | @ReactMethod
399 | public void clearBreadcrumbs() {
400 | // Sentry.configureScope(scope -> {
401 | // scope.clearBreadcrumbs();
402 | // });
403 | }
404 |
405 | @ReactMethod
406 | public void setExtra(String key, String extra) {
407 | // Sentry.configureScope(scope -> {
408 | // scope.setExtra(key, extra);
409 | // });
410 | }
411 |
412 | @ReactMethod
413 | public void setTag(String key, String value) {
414 | // Sentry.configureScope(scope -> {
415 | // scope.setTag(key, value);
416 | // });
417 | }
418 |
419 | @ReactMethod
420 | public void closeNativeSdk(Promise promise) {
421 | // Sentry.close();
422 |
423 | // disableNativeFramesTracking();
424 |
425 | promise.resolve(true);
426 | }
427 |
428 | @ReactMethod
429 | public void disableNativeFramesTracking() {
430 | // if (isFrameMetricsAggregatorAvailable()) {
431 | // frameMetricsAggregator.stop();
432 | // frameMetricsAggregator = null;
433 | // }
434 | }
435 |
436 | // private void setEventOriginTag(SentryEvent event) {
437 | // SdkVersion sdk = event.getSdk();
438 | // if (sdk != null) {
439 | // switch (sdk.getName()) {
440 | // // If the event is from capacitor js, it gets set there and we do not handle it
441 | // // here.
442 | // case "sentry.native":
443 | // setEventEnvironmentTag(event, "android", "native");
444 | // break;
445 | // case "sentry.java.android":
446 | // setEventEnvironmentTag(event, "android", "java");
447 | // break;
448 | // default:
449 | // break;
450 | // }
451 | // }
452 | // }
453 |
454 | // private void setEventEnvironmentTag(SentryEvent event, String origin, String environment) {
455 | // event.setTag("event.origin", origin);
456 | // event.setTag("event.environment", environment);
457 | // }
458 |
459 | // private void addPackages(SentryEvent event, SdkVersion sdk) {
460 | // SdkVersion eventSdk = event.getSdk();
461 | // if (eventSdk != null && eventSdk.getName().equals("sentry.javascript.react-native") && sdk != null) {
462 | // List sentryPackages = sdk.getPackages();
463 | // if (sentryPackages != null) {
464 | // for (SentryPackage sentryPackage : sentryPackages) {
465 | // eventSdk.addPackage(sentryPackage.getName(), sentryPackage.getVersion());
466 | // }
467 | // }
468 |
469 | // List integrations = sdk.getIntegrations();
470 | // if (integrations != null) {
471 | // for (String integration : integrations) {
472 | // eventSdk.addIntegration(integration);
473 | // }
474 | // }
475 |
476 | // event.setSdk(eventSdk);
477 | // }
478 | // }
479 |
480 | private boolean checkAndroidXAvailability() {
481 | try {
482 | Class.forName("androidx.core.app.FrameMetricsAggregator");
483 | return true;
484 | } catch (ClassNotFoundException ignored) {
485 | // androidx.core isn't available.
486 | return false;
487 | }
488 | }
489 |
490 | private boolean isFrameMetricsAggregatorAvailable() {
491 | return androidXAvailable && frameMetricsAggregator != null;
492 | }
493 | }
--------------------------------------------------------------------------------