├── .gitignore
├── CHANGELOG.md
├── FcmSharp
├── Examples
│ ├── Android
│ │ └── messaging-app
│ │ │ ├── .gitignore
│ │ │ ├── README.md
│ │ │ ├── app
│ │ │ ├── build.gradle
│ │ │ ├── proguard-rules.pro
│ │ │ └── src
│ │ │ │ ├── androidTest
│ │ │ │ └── java
│ │ │ │ │ └── com
│ │ │ │ │ └── google
│ │ │ │ │ └── firebase
│ │ │ │ │ └── quickstart
│ │ │ │ │ └── fcm
│ │ │ │ │ └── MainActivityEspressoTest.java
│ │ │ │ ├── main
│ │ │ │ ├── AndroidManifest.xml
│ │ │ │ ├── java
│ │ │ │ │ └── com
│ │ │ │ │ │ └── google
│ │ │ │ │ │ └── firebase
│ │ │ │ │ │ └── quickstart
│ │ │ │ │ │ └── fcm
│ │ │ │ │ │ ├── MainActivity.java
│ │ │ │ │ │ ├── MyFirebaseMessagingService.java
│ │ │ │ │ │ └── MyJobService.java
│ │ │ │ └── res
│ │ │ │ │ ├── drawable-hdpi-v11
│ │ │ │ │ └── ic_stat_ic_notification.png
│ │ │ │ │ ├── drawable-hdpi
│ │ │ │ │ ├── firebase_lockup_400.png
│ │ │ │ │ └── ic_stat_ic_notification.png
│ │ │ │ │ ├── drawable-mdpi-v11
│ │ │ │ │ └── ic_stat_ic_notification.png
│ │ │ │ │ ├── drawable-mdpi
│ │ │ │ │ ├── firebase_lockup_400.png
│ │ │ │ │ └── ic_stat_ic_notification.png
│ │ │ │ │ ├── drawable-xhdpi-v11
│ │ │ │ │ └── ic_stat_ic_notification.png
│ │ │ │ │ ├── drawable-xhdpi
│ │ │ │ │ ├── firebase_lockup_400.png
│ │ │ │ │ └── ic_stat_ic_notification.png
│ │ │ │ │ ├── drawable-xxhdpi-v11
│ │ │ │ │ └── ic_stat_ic_notification.png
│ │ │ │ │ ├── drawable-xxhdpi
│ │ │ │ │ ├── firebase_lockup_400.png
│ │ │ │ │ └── ic_stat_ic_notification.png
│ │ │ │ │ ├── drawable-xxxhdpi-v11
│ │ │ │ │ └── ic_stat_ic_notification.png
│ │ │ │ │ ├── drawable-xxxhdpi
│ │ │ │ │ └── ic_stat_ic_notification.png
│ │ │ │ │ ├── layout
│ │ │ │ │ └── activity_main.xml
│ │ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── values-w820dp
│ │ │ │ │ └── dimens.xml
│ │ │ │ │ └── values
│ │ │ │ │ ├── colors.xml
│ │ │ │ │ ├── dimens.xml
│ │ │ │ │ ├── strings.xml
│ │ │ │ │ └── styles.xml
│ │ │ │ └── screen.png
│ │ │ ├── build.gradle
│ │ │ ├── gradle.properties
│ │ │ ├── gradle
│ │ │ └── wrapper
│ │ │ │ ├── gradle-wrapper.jar
│ │ │ │ └── gradle-wrapper.properties
│ │ │ ├── gradlew
│ │ │ ├── gradlew.bat
│ │ │ └── settings.gradle
│ ├── CSharp
│ │ ├── FcmSharp.Example
│ │ │ ├── FcmSharp.Example.csproj
│ │ │ └── Program.cs
│ │ ├── FcmSharp.Examples.sln
│ │ ├── FcmSharp.Scheduler.Quartz
│ │ │ ├── Database
│ │ │ │ ├── ApplicationDbContext.cs
│ │ │ │ ├── Configuration
│ │ │ │ │ ├── Mapping.cs
│ │ │ │ │ └── Seeding.cs
│ │ │ │ └── Model
│ │ │ │ │ ├── Message.cs
│ │ │ │ │ └── StatusEnum.cs
│ │ │ ├── Extensions
│ │ │ │ └── LoggerExtensions.cs
│ │ │ ├── FcmSharp.Scheduler.Quartz.csproj
│ │ │ ├── Program.cs
│ │ │ ├── Properties
│ │ │ │ └── launchSettings.json
│ │ │ ├── Quartz
│ │ │ │ ├── JobFactory
│ │ │ │ │ └── JobFactory.cs
│ │ │ │ └── Jobs
│ │ │ │ │ └── ProcessMessageJob.cs
│ │ │ ├── Services
│ │ │ │ ├── Converters
│ │ │ │ │ └── MessageConverter.cs
│ │ │ │ ├── MessagingService.cs
│ │ │ │ └── SchedulerService.cs
│ │ │ ├── Startup.cs
│ │ │ ├── Testing
│ │ │ │ └── MockFcmClient.cs
│ │ │ ├── Web
│ │ │ │ ├── Contracts
│ │ │ │ │ ├── Message.cs
│ │ │ │ │ └── StatusEnum.cs
│ │ │ │ ├── Controllers
│ │ │ │ │ └── SchedulerController.cs
│ │ │ │ ├── Converters
│ │ │ │ │ └── MessageConverter.cs
│ │ │ │ └── Extensions
│ │ │ │ │ ├── DatabaseExtensions.cs
│ │ │ │ │ └── QuartzExtensions.cs
│ │ │ └── appsettings.json
│ │ └── FcmSharp.Scheduler
│ │ │ ├── Converters
│ │ │ └── MessageConverter.cs
│ │ │ ├── Database
│ │ │ ├── ApplicationDbContext.cs
│ │ │ ├── Configuration
│ │ │ │ ├── Mapping.cs
│ │ │ │ └── Seeding.cs
│ │ │ └── Model
│ │ │ │ ├── Message.cs
│ │ │ │ └── StatusEnum.cs
│ │ │ ├── FcmSharp.Scheduler.csproj
│ │ │ ├── Program.cs
│ │ │ ├── Services
│ │ │ └── SchedulerService.cs
│ │ │ └── Testing
│ │ │ └── MockFcmClient.cs
│ └── Images
│ │ ├── Screenshot_20181118-100555.png
│ │ ├── Screenshot_20181118-100611.png
│ │ ├── Screenshot_20181118-101026.png
│ │ └── Screenshot_20181118-103856.png
├── FcmSharp.Console
│ ├── FcmSharp.ConsoleApp.csproj
│ └── Program.cs
├── FcmSharp.Test
│ ├── FcmSharp.Test.csproj
│ ├── Integration
│ │ ├── BatchMessageIntegrationTest.cs
│ │ ├── IntegrationTest.cs
│ │ └── ProxyIntegrationTest.cs
│ └── Requests
│ │ ├── AndroidConfigTest.cs
│ │ └── ApnsConfigTest.cs
├── FcmSharp.sln
├── FcmSharp
│ ├── BackOff
│ │ └── ExponentialBackOffSettings.cs
│ ├── Batch
│ │ └── BatchMessageBuilder.cs
│ ├── Exceptions
│ │ ├── FcmHttpException.cs
│ │ ├── FcmMessageException.cs
│ │ └── FcmTopicManagementException.cs
│ ├── Extensions
│ │ └── FcmClientExtensions.cs
│ ├── FcmClient.cs
│ ├── FcmSharp.csproj
│ ├── FcmSharp.snk
│ ├── Http
│ │ ├── Builder
│ │ │ ├── HttpRequestMessageBuilder.cs
│ │ │ ├── HttpRequestUtils.cs
│ │ │ └── UrlSegment.cs
│ │ ├── Client
│ │ │ ├── FcmHttpClient.cs
│ │ │ └── IFcmHttpClient.cs
│ │ ├── Constants
│ │ │ ├── HttpHeaderNames.cs
│ │ │ └── MediaTypeNames.cs
│ │ └── Proxy
│ │ │ ├── ProxyHttpClientFactory.cs
│ │ │ └── WebProxy.cs
│ ├── IFcmClient.cs
│ ├── Requests
│ │ ├── AndroidConfig.cs
│ │ ├── AndroidMessagePriorityEnum.cs
│ │ ├── AndroidNotification.cs
│ │ ├── ApnsConfig.cs
│ │ ├── ApnsConfigPayload.cs
│ │ ├── Aps.cs
│ │ ├── ApsAlert.cs
│ │ ├── Converters
│ │ │ ├── AndroidMessagePriorityEnumConverter.cs
│ │ │ ├── BoolToIntConverter.cs
│ │ │ └── DurationFormatConverter.cs
│ │ ├── FcmMessage.cs
│ │ ├── Message.cs
│ │ ├── Notification.cs
│ │ ├── TopicManagementRequest.cs
│ │ ├── WebpushConfig.cs
│ │ ├── WebpushFcmOptions.cs
│ │ └── WebpushNotification.cs
│ ├── Responses
│ │ ├── Converters
│ │ │ └── TopicErrorEnumConverter.cs
│ │ ├── FcmBatchResponse.cs
│ │ ├── FcmMessageErrorResponse.cs
│ │ ├── FcmMessageResponse.cs
│ │ ├── InstanceIdServiceErrorResponse.cs
│ │ ├── InstanceIdServiceResponse.cs
│ │ ├── TopicMessageResponse.cs
│ │ └── TopicMessageResponseError.cs
│ ├── Serializer
│ │ ├── IJsonSerializer.cs
│ │ └── JsonSerializer.cs
│ └── Settings
│ │ ├── FcmClientSettings.cs
│ │ ├── FileBasedFcmClientSettings.cs
│ │ ├── IFcmClientSettings.cs
│ │ └── StreamBasedFcmClientSettings.cs
├── Images
│ ├── OV_Logo.png
│ └── jetbrains.png
└── NuGet
│ ├── FcmSharp.1.0.0.nupkg
│ ├── FcmSharp.1.1.0.nupkg
│ ├── FcmSharp.2.0.0.nupkg
│ ├── FcmSharp.2.1.0.nupkg
│ ├── FcmSharp.2.1.1.nupkg
│ ├── FcmSharp.2.2.0.nupkg
│ ├── FcmSharp.2.3.0.nupkg
│ ├── FcmSharp.2.3.1.nupkg
│ ├── FcmSharp.2.3.2.nupkg
│ ├── FcmSharp.2.4.0.nupkg
│ ├── FcmSharp.2.5.0.nupkg
│ ├── FcmSharp.2.6.0-alpha.nupkg
│ ├── FcmSharp.2.6.0.nupkg
│ ├── FcmSharp.2.7.0.nupkg
│ ├── FcmSharp.2.7.1.nupkg
│ ├── FcmSharp.2.8.0.nupkg
│ ├── FcmSharp.2.8.1.nupkg
│ ├── FcmSharp.2.8.2.nupkg
│ ├── FcmSharp.2.8.3.nupkg
│ ├── FcmSharp.2.8.4.nupkg
│ ├── FcmSharp.3.0.0.nupkg
│ └── FcmSharp.3.0.1.nupkg
├── LICENSE
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .vs/
2 | .idea/
3 | *.xproj.user
4 | *.csproj.user
5 | project.lock.json
6 | bin/
7 | obj/
8 | packages/
9 | *.DotSettings.user
10 | *.db
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | .idea/
3 | *.iml
4 | build/
5 | /local.properties
6 | .DS_Store
7 | /captures
8 | google-services.json
9 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/README.md:
--------------------------------------------------------------------------------
1 | Firebase Cloud Messaging Quickstart
2 | ==============================
3 |
4 | The Firebase Cloud Messaging Android Quickstart app demonstrates registering
5 | an Android app for notifications and handling the receipt of a message.
6 | **InstanceID** allows easy registration while **FirebaseMessagingService** and **FirebaseInstanceIDService**
7 | enable token refreshes and message handling on the client.
8 |
9 | Introduction
10 | ------------
11 |
12 | - [Read more about Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging)
13 |
14 | Getting Started
15 | ---------------
16 |
17 | - [Add Firebase to your Android Project](https://firebase.google.com/docs/android/setup).
18 | - Run the sample on Android device or emulator.
19 |
20 | Sending Notifications
21 | ---------------------
22 |
23 | Use Firebase console to send FCM messages to device or emulator.
24 |
25 | ## Send to a single device
26 |
27 | - From Firebase console Notification section, click **New Message**.
28 | - Enter the text of your message in the Message Text field.
29 | - Set the target to **Single Device**.
30 | - Check the logs for the **InstanceID** token, copy and paste it into the Firebase console Token field.
31 | - If you cannot find the token in your logs, click on the **LOG TOKEN** button in the application and the token will
32 | be logged in **logcat**.
33 | - Click on the **Send Message** button.
34 | - If your application is in the foreground you should see the incoming
35 | message printed in the logs. If it is in the background, a system notification should be
36 | displayed. When the notification is tapped, the application should return to the quickstart application.
37 |
38 | ## Send to a topic
39 |
40 | - From Firebase console Notification section, click **New Message**.
41 | - Enter the text of your message in the Message Text field.
42 | - Click on the **SUBSCRIBE TO NEWS** button to subscribe to the news topic.
43 | - Set the target to **Topic**.
44 | - Select the news topic from the list of topics ("news" in this sample).
45 | You must subscribe from the device or emulator before the topic will will be visible in the console.
46 | - Click on the **Send Message** button.
47 | - If your application is in the foreground you should see the incoming
48 | message printed in the logs. If it is in the background, a system notification should be
49 | displayed. When the notification is tapped, the application should return to the quickstart application.
50 |
51 | Best Practices
52 | --------------
53 |
54 | ## Android notification channels
55 |
56 | ### Set default channel
57 |
58 | If incoming FCM messages do not specify an Android notification channel, you can indicate
59 | to FCM what channel should be used as the default by adding a metadata element to your
60 | application manifest. In the metadata element specify the ID of the channel that should
61 | be used by default by FCM.
62 |
63 |
66 |
67 | Note: You are still required to create a notification channel in code with an ID that
68 | matches the one defined in the manifest. See the Android [docs](https://goo.gl/x9fh5X) for more.
69 |
70 | ## Customize default notification
71 |
72 | ### Custom default icon
73 |
74 | Setting a custom default icon allows you to specify what icon is used for notification
75 | messages if no icon is set in the notification payload. Also use the custom default
76 | icon to set the icon used by notification messages sent from the Firebase console.
77 | If no custom default icon is set and no icon is set in the notification payload,
78 | the application icon (rendered in white) is used.
79 |
80 | ### Custom default Color
81 |
82 | You can also define what color is used with your notification. Different android
83 | versions use this settings in different ways: Android < N use this as background color
84 | for the icon. Android >= N use this to color the icon and the app name.
85 |
86 | See the [docs](https://goo.gl/sPggnS) for more.
87 |
88 | Result
89 | -----------
90 |
91 |
92 | Support
93 | -------
94 |
95 | - [Stack Overflow](https://stackoverflow.com/questions/tagged/firebase-cloud-messaging)
96 | - [Firebase Support](https://firebase.google.com/support/)
97 |
98 | License
99 | -------
100 |
101 | Copyright 2016 Google, Inc.
102 |
103 | Licensed to the Apache Software Foundation (ASF) under one or more contributor
104 | license agreements. See the NOTICE file distributed with this work for
105 | additional information regarding copyright ownership. The ASF licenses this
106 | file to you under the Apache License, Version 2.0 (the "License"); you may not
107 | use this file except in compliance with the License. You may obtain a copy of
108 | the License at
109 |
110 | http://www.apache.org/licenses/LICENSE-2.0
111 |
112 | Unless required by applicable law or agreed to in writing, software
113 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
114 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
115 | License for the specific language governing permissions and limitations under
116 | the License.
117 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | check.dependsOn 'assembleDebugAndroidTest'
3 |
4 | android {
5 | compileSdkVersion 27
6 |
7 | defaultConfig {
8 | applicationId "com.google.firebase.quickstart.fcm"
9 | minSdkVersion 16
10 | targetSdkVersion 27
11 | versionCode 1
12 | versionName "1.0"
13 |
14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
15 | }
16 |
17 | buildTypes {
18 | release {
19 | minifyEnabled true
20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 |
24 | packagingOptions {
25 | exclude 'LICENSE.txt'
26 | }
27 |
28 | lintOptions {
29 | abortOnError false
30 | }
31 | }
32 |
33 | configurations.all {
34 | resolutionStrategy.force 'com.android.support:support-annotations:27.1.1'
35 | }
36 |
37 | repositories {
38 | google()
39 | }
40 |
41 | dependencies {
42 | implementation 'com.android.support:appcompat-v7:27.1.1'
43 | implementation 'com.android.support:animated-vector-drawable:27.1.1'
44 | implementation 'com.android.support:support-v4:27.1.1'
45 | implementation 'com.android.support:support-compat:27.1.1'
46 |
47 | implementation 'com.google.firebase:firebase-core:16.0.3'
48 | implementation 'com.google.firebase:firebase-iid:17.0.1'
49 | implementation 'com.google.firebase:firebase-messaging:17.3.1'
50 | implementation 'com.firebase:firebase-jobdispatcher:0.8.5'
51 |
52 | // Testing dependencies
53 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
54 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
55 | androidTestImplementation 'com.android.support.test:rules:1.0.2'
56 | androidTestImplementation 'com.android.support:support-annotations:27.1.1'
57 | }
58 |
59 | apply plugin: 'com.google.gms.google-services'
60 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | -keepattributes EnclosingMethod
20 | -keepattributes InnerClasses
21 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/app/src/androidTest/java/com/google/firebase/quickstart/fcm/MainActivityEspressoTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright Google Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.firebase.quickstart.fcm;
18 |
19 | import android.support.test.rule.ActivityTestRule;
20 | import android.support.test.runner.AndroidJUnit4;
21 | import android.test.suitebuilder.annotation.LargeTest;
22 | import android.view.View;
23 |
24 | import org.junit.Rule;
25 | import org.junit.Test;
26 | import org.junit.runner.RunWith;
27 |
28 | import static android.support.test.espresso.Espresso.onView;
29 | import static android.support.test.espresso.action.ViewActions.click;
30 | import static android.support.test.espresso.assertion.ViewAssertions.matches;
31 | import static android.support.test.espresso.matcher.RootMatchers.withDecorView;
32 | import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
33 | import static android.support.test.espresso.matcher.ViewMatchers.withId;
34 | import static android.support.test.espresso.matcher.ViewMatchers.withText;
35 | import static org.hamcrest.Matchers.allOf;
36 | import static org.hamcrest.Matchers.is;
37 | import static org.hamcrest.Matchers.not;
38 | import static org.hamcrest.Matchers.startsWith;
39 |
40 | @RunWith(AndroidJUnit4.class)
41 | @LargeTest
42 | public class MainActivityEspressoTest {
43 |
44 | @Rule
45 | public ActivityTestRule mActivityRule =
46 | new ActivityTestRule<>(MainActivity.class);
47 |
48 | @Test
49 | public void testSubscribeAndLog() throws InterruptedException {
50 | onView(withId(R.id.informationTextView)).check(matches(isDisplayed()));
51 |
52 | // Click subscribe button and check toast
53 | onView(allOf(withId(R.id.subscribeButton), withText(R.string.subscribe_to_news)))
54 | .check(matches(isDisplayed()))
55 | .perform(click());
56 | confirmToastStartsWith(mActivityRule.getActivity().getString(R.string.msg_subscribed));
57 |
58 | // Sleep so the Toast goes away, this is lazy but it works (Toast.LENGTH_SHORT = 2000)
59 | Thread.sleep(2000);
60 |
61 | // Click log token and check toast
62 | onView(allOf(withId(R.id.logTokenButton), withText(R.string.log_token)))
63 | .check(matches(isDisplayed()))
64 | .perform(click());
65 | confirmToastStartsWith(mActivityRule.getActivity().getString(R.string.msg_token_fmt, ""));
66 | }
67 |
68 | private void confirmToastStartsWith(String string) {
69 | View activityWindowDecorView = mActivityRule.getActivity().getWindow().getDecorView();
70 | onView(withText(startsWith(string)))
71 | .inRoot(withDecorView(not(is(activityWindowDecorView))))
72 | .check(matches(isDisplayed()));
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
11 |
12 |
13 |
15 |
18 |
20 |
23 |
24 |
25 |
28 |
29 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
41 |
42 |
43 |
44 |
45 |
46 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/app/src/main/java/com/google/firebase/quickstart/fcm/MainActivity.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.firebase.quickstart.fcm;
18 |
19 | import android.app.NotificationChannel;
20 | import android.app.NotificationManager;
21 | import android.os.Build;
22 | import android.os.Bundle;
23 | import android.support.annotation.NonNull;
24 | import android.support.v7.app.AppCompatActivity;
25 | import android.util.Log;
26 | import android.view.View;
27 | import android.widget.Button;
28 | import android.widget.Toast;
29 |
30 | import com.google.android.gms.tasks.OnCompleteListener;
31 | import com.google.android.gms.tasks.Task;
32 | import com.google.firebase.iid.FirebaseInstanceId;
33 | import com.google.firebase.iid.InstanceIdResult;
34 | import com.google.firebase.messaging.FirebaseMessaging;
35 |
36 | public class MainActivity extends AppCompatActivity {
37 |
38 | private static final String TAG = "MainActivity";
39 |
40 | @Override
41 | protected void onCreate(Bundle savedInstanceState) {
42 | super.onCreate(savedInstanceState);
43 | setContentView(R.layout.activity_main);
44 |
45 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
46 | // Create channel to show notifications.
47 | String channelId = getString(R.string.default_notification_channel_id);
48 | String channelName = getString(R.string.default_notification_channel_name);
49 | NotificationManager notificationManager =
50 | getSystemService(NotificationManager.class);
51 | notificationManager.createNotificationChannel(new NotificationChannel(channelId,
52 | channelName, NotificationManager.IMPORTANCE_LOW));
53 | }
54 |
55 | // If a notification message is tapped, any data accompanying the notification
56 | // message is available in the intent extras. In this sample the launcher
57 | // intent is fired when the notification is tapped, so any accompanying data would
58 | // be handled here. If you want a different intent fired, set the click_action
59 | // field of the notification message to the desired intent. The launcher intent
60 | // is used when no click_action is specified.
61 | //
62 | // Handle possible data accompanying notification message.
63 | // [START handle_data_extras]
64 | if (getIntent().getExtras() != null) {
65 | for (String key : getIntent().getExtras().keySet()) {
66 | Object value = getIntent().getExtras().get(key);
67 | Log.d(TAG, "Key: " + key + " Value: " + value);
68 | }
69 | }
70 | // [END handle_data_extras]
71 |
72 | Button subscribeButton = findViewById(R.id.subscribeButton);
73 | subscribeButton.setOnClickListener(new View.OnClickListener() {
74 | @Override
75 | public void onClick(View v) {
76 | Log.d(TAG, "Subscribing to news topic");
77 | // [START subscribe_topics]
78 | FirebaseMessaging.getInstance().subscribeToTopic("news")
79 | .addOnCompleteListener(new OnCompleteListener() {
80 | @Override
81 | public void onComplete(@NonNull Task task) {
82 | String msg = getString(R.string.msg_subscribed);
83 | if (!task.isSuccessful()) {
84 | msg = getString(R.string.msg_subscribe_failed);
85 | }
86 | Log.d(TAG, msg);
87 | Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
88 | }
89 | });
90 | // [END subscribe_topics]
91 | }
92 | });
93 |
94 | Button logTokenButton = findViewById(R.id.logTokenButton);
95 | logTokenButton.setOnClickListener(new View.OnClickListener() {
96 | @Override
97 | public void onClick(View v) {
98 | // Get token
99 | FirebaseInstanceId.getInstance().getInstanceId()
100 | .addOnCompleteListener(new OnCompleteListener() {
101 | @Override
102 | public void onComplete(@NonNull Task task) {
103 | if (!task.isSuccessful()) {
104 | Log.w(TAG, "getInstanceId failed", task.getException());
105 | return;
106 | }
107 |
108 | // Get new Instance ID token
109 | String token = task.getResult().getToken();
110 |
111 | // Log and toast
112 | String msg = getString(R.string.msg_token_fmt, token);
113 | Log.d(TAG, msg);
114 | Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
115 | }
116 | });
117 |
118 |
119 | }
120 | });
121 | }
122 |
123 | }
124 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/app/src/main/java/com/google/firebase/quickstart/fcm/MyFirebaseMessagingService.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2016 Google Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.firebase.quickstart.fcm;
18 |
19 | import android.app.NotificationChannel;
20 | import android.app.NotificationManager;
21 | import android.app.PendingIntent;
22 | import android.content.Context;
23 | import android.content.Intent;
24 | import android.media.RingtoneManager;
25 | import android.net.Uri;
26 | import android.os.Build;
27 | import android.support.v4.app.NotificationCompat;
28 | import android.util.Log;
29 |
30 | import com.firebase.jobdispatcher.FirebaseJobDispatcher;
31 | import com.firebase.jobdispatcher.GooglePlayDriver;
32 | import com.firebase.jobdispatcher.Job;
33 | import com.google.firebase.messaging.FirebaseMessagingService;
34 | import com.google.firebase.messaging.RemoteMessage;
35 |
36 | public class MyFirebaseMessagingService extends FirebaseMessagingService {
37 |
38 | private static final String TAG = "MyFirebaseMsgService";
39 |
40 | /**
41 | * Called when message is received.
42 | *
43 | * @param remoteMessage Object representing the message received from Firebase Cloud Messaging.
44 | */
45 | // [START receive_message]
46 | @Override
47 | public void onMessageReceived(RemoteMessage remoteMessage) {
48 | // [START_EXCLUDE]
49 | // There are two types of messages data messages and notification messages. Data messages are handled
50 | // here in onMessageReceived whether the app is in the foreground or background. Data messages are the type
51 | // traditionally used with GCM. Notification messages are only received here in onMessageReceived when the app
52 | // is in the foreground. When the app is in the background an automatically generated notification is displayed.
53 | // When the user taps on the notification they are returned to the app. Messages containing both notification
54 | // and data payloads are treated as notification messages. The Firebase console always sends notification
55 | // messages. For more see: https://firebase.google.com/docs/cloud-messaging/concept-options
56 | // [END_EXCLUDE]
57 |
58 | // TODO(developer): Handle FCM messages here.
59 | // Not getting messages here? See why this may be: https://goo.gl/39bRNJ
60 | Log.d(TAG, "From: " + remoteMessage.getFrom());
61 |
62 | // Check if message contains a data payload.
63 | if (remoteMessage.getData().size() > 0) {
64 | Log.d(TAG, "Message data payload: " + remoteMessage.getData());
65 |
66 | if (/* Check if data needs to be processed by long running job */ true) {
67 | // For long-running tasks (10 seconds or more) use Firebase Job Dispatcher.
68 | scheduleJob();
69 | } else {
70 | // Handle message within 10 seconds
71 | handleNow();
72 | }
73 |
74 | }
75 |
76 | // Check if message contains a notification payload.
77 | if (remoteMessage.getNotification() != null) {
78 | Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody());
79 | }
80 |
81 | sendNotification(remoteMessage.getNotification().getTitle(), remoteMessage.getNotification().getBody());
82 | }
83 | // [END receive_message]
84 |
85 |
86 | // [START on_new_token]
87 | /**
88 | * Called if InstanceID token is updated. This may occur if the security of
89 | * the previous token had been compromised. Note that this is called when the InstanceID token
90 | * is initially generated so this is where you would retrieve the token.
91 | */
92 | @Override
93 | public void onNewToken(String token) {
94 | Log.d(TAG, "Refreshed token: " + token);
95 |
96 | // If you want to send messages to this application instance or
97 | // manage this apps subscriptions on the server side, send the
98 | // Instance ID token to your app server.
99 | sendRegistrationToServer(token);
100 | }
101 | // [END on_new_token]
102 |
103 | /**
104 | * Schedule a job using FirebaseJobDispatcher.
105 | */
106 | private void scheduleJob() {
107 | // [START dispatch_job]
108 | FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(this));
109 | Job myJob = dispatcher.newJobBuilder()
110 | .setService(MyJobService.class)
111 | .setTag("my-job-tag")
112 | .build();
113 | dispatcher.schedule(myJob);
114 | // [END dispatch_job]
115 | }
116 |
117 | /**
118 | * Handle time allotted to BroadcastReceivers.
119 | */
120 | private void handleNow() {
121 | Log.d(TAG, "Short lived task is done.");
122 | }
123 |
124 | /**
125 | * Persist token to third-party servers.
126 | *
127 | * Modify this method to associate the user's FCM InstanceID token with any server-side account
128 | * maintained by your application.
129 | *
130 | * @param token The new token.
131 | */
132 | private void sendRegistrationToServer(String token) {
133 | // TODO: Implement this method to send token to your app server.
134 | }
135 |
136 | private void sendNotification(String title, String body) {
137 | Intent intent = new Intent(this, MainActivity.class);
138 | intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
139 | PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
140 | PendingIntent.FLAG_ONE_SHOT);
141 |
142 | String channelId = getString(R.string.default_notification_channel_id);
143 | Uri defaultSoundUri= RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
144 | NotificationCompat.Builder notificationBuilder =
145 | new NotificationCompat.Builder(this, channelId)
146 | .setSmallIcon(R.drawable.ic_stat_ic_notification)
147 | .setContentTitle(title)
148 | .setContentText(body)
149 | .setAutoCancel(true)
150 | .setSound(defaultSoundUri)
151 | .setContentIntent(pendingIntent);
152 |
153 | NotificationManager notificationManager =
154 | (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
155 |
156 | // Since android Oreo notification channel is needed.
157 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
158 | NotificationChannel channel = new NotificationChannel(channelId,
159 | "Channel human readable title",
160 | NotificationManager.IMPORTANCE_DEFAULT);
161 | notificationManager.createNotificationChannel(channel);
162 | }
163 |
164 | notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/app/src/main/java/com/google/firebase/quickstart/fcm/MyJobService.java:
--------------------------------------------------------------------------------
1 | package com.google.firebase.quickstart.fcm;
2 |
3 | import android.util.Log;
4 |
5 | import com.firebase.jobdispatcher.JobParameters;
6 | import com.firebase.jobdispatcher.JobService;
7 |
8 | public class MyJobService extends JobService {
9 |
10 | private static final String TAG = "MyJobService";
11 |
12 | @Override
13 | public boolean onStartJob(JobParameters jobParameters) {
14 | Log.d(TAG, "Performing long running task in scheduled job");
15 | // TODO(developer): add long running task here.
16 | return false;
17 | }
18 |
19 | @Override
20 | public boolean onStopJob(JobParameters jobParameters) {
21 | return false;
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/app/src/main/res/drawable-hdpi-v11/ic_stat_ic_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/Examples/Android/messaging-app/app/src/main/res/drawable-hdpi-v11/ic_stat_ic_notification.png
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/app/src/main/res/drawable-hdpi/firebase_lockup_400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/Examples/Android/messaging-app/app/src/main/res/drawable-hdpi/firebase_lockup_400.png
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/app/src/main/res/drawable-hdpi/ic_stat_ic_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/Examples/Android/messaging-app/app/src/main/res/drawable-hdpi/ic_stat_ic_notification.png
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/app/src/main/res/drawable-mdpi-v11/ic_stat_ic_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/Examples/Android/messaging-app/app/src/main/res/drawable-mdpi-v11/ic_stat_ic_notification.png
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/app/src/main/res/drawable-mdpi/firebase_lockup_400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/Examples/Android/messaging-app/app/src/main/res/drawable-mdpi/firebase_lockup_400.png
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/app/src/main/res/drawable-mdpi/ic_stat_ic_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/Examples/Android/messaging-app/app/src/main/res/drawable-mdpi/ic_stat_ic_notification.png
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/app/src/main/res/drawable-xhdpi-v11/ic_stat_ic_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/Examples/Android/messaging-app/app/src/main/res/drawable-xhdpi-v11/ic_stat_ic_notification.png
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/app/src/main/res/drawable-xhdpi/firebase_lockup_400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/Examples/Android/messaging-app/app/src/main/res/drawable-xhdpi/firebase_lockup_400.png
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/app/src/main/res/drawable-xhdpi/ic_stat_ic_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/Examples/Android/messaging-app/app/src/main/res/drawable-xhdpi/ic_stat_ic_notification.png
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/app/src/main/res/drawable-xxhdpi-v11/ic_stat_ic_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/Examples/Android/messaging-app/app/src/main/res/drawable-xxhdpi-v11/ic_stat_ic_notification.png
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/app/src/main/res/drawable-xxhdpi/firebase_lockup_400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/Examples/Android/messaging-app/app/src/main/res/drawable-xxhdpi/firebase_lockup_400.png
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/app/src/main/res/drawable-xxhdpi/ic_stat_ic_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/Examples/Android/messaging-app/app/src/main/res/drawable-xxhdpi/ic_stat_ic_notification.png
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/app/src/main/res/drawable-xxxhdpi-v11/ic_stat_ic_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/Examples/Android/messaging-app/app/src/main/res/drawable-xxxhdpi-v11/ic_stat_ic_notification.png
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/app/src/main/res/drawable-xxxhdpi/ic_stat_ic_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/Examples/Android/messaging-app/app/src/main/res/drawable-xxxhdpi/ic_stat_ic_notification.png
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
18 |
19 |
26 |
27 |
34 |
35 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/Examples/Android/messaging-app/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/Examples/Android/messaging-app/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/Examples/Android/messaging-app/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/Examples/Android/messaging-app/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/Examples/Android/messaging-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #039BE5
4 | #0288D1
5 | #FFA000
6 |
7 | #607D8B
8 | #546E7A
9 | #455A64
10 | #37474F
11 | #263238
12 |
13 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 | 200dp
7 |
8 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Firebase Cloud Messaging
3 | Click the SUBSCRIBE TO NEWS button below to subscribe to the
4 | news topic. Messages sent to the news topic will be received. The LOG TOKEN button logs the
5 | InstanceID token to logcat.
6 | Subscribe To News
7 | Log Token
8 |
9 | Subscribed to news topic
10 | InstanceID Token: %s
11 |
12 | fcm_default_channel
13 |
17 | News
18 | Failed to subscribe to news topic
19 |
20 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/app/src/screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/Examples/Android/messaging-app/app/src/screen.png
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | mavenLocal()
6 | jcenter()
7 | google()
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.1.4'
11 | classpath 'com.google.gms:google-services:4.1.0'
12 |
13 | // NOTE: Do not place your application dependencies here; they belong
14 | // in the individual module build.gradle files
15 | }
16 | }
17 |
18 | allprojects {
19 | repositories {
20 | //mavenLocal() must be listed at the top to facilitate testing
21 | mavenLocal()
22 | google()
23 | maven {
24 | url 'https://maven.google.com/'
25 | name 'Google'
26 | }
27 | jcenter()
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/Examples/Android/messaging-app/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-bin.zip
6 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/Android/messaging-app/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Example/FcmSharp.Example.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp2.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Example/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading;
4 | using FcmSharp.Requests;
5 | using FcmSharp.Settings;
6 |
7 | namespace FcmSharp.Example
8 | {
9 | public class Program
10 | {
11 | public static void Main(string[] args)
12 | {
13 | // Read the Credentials from a File, which is not under Version Control:
14 | var settings = FileBasedFcmClientSettings.CreateFromFile(@"your_project_id", @"D:\serviceAccountKey.json");
15 |
16 | // Construct the Client:
17 | using (var client = new FcmClient(settings))
18 | {
19 | // Construct the Data Payload to send:
20 | var data = new Dictionary()
21 | {
22 | {"A", "B"},
23 | {"C", "D"}
24 | };
25 |
26 | // Get the Registration from Console:
27 | Console.Write("Device Token: ");
28 |
29 | string registrationId = Console.ReadLine();
30 |
31 | // The Message should be sent to the given token:
32 | var message = new FcmMessage()
33 | {
34 | ValidateOnly = false,
35 | Message = new Message
36 | {
37 | Token = registrationId,
38 | Data = data
39 | }
40 | };
41 |
42 | // Finally send the Message and wait for the Result:
43 | CancellationTokenSource cts = new CancellationTokenSource();
44 |
45 | // Send the Message and wait synchronously:
46 | var result = client.SendAsync(message, cts.Token).GetAwaiter().GetResult();
47 |
48 | // Print the Result to the Console:
49 | Console.WriteLine("Message ID = {0}", result.Name);
50 |
51 | Console.ReadLine();
52 | }
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Examples.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.27130.2010
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FcmSharp.Example", "FcmSharp.Example\FcmSharp.Example.csproj", "{E5238F02-96AC-4079-9C16-72D169E1C11A}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FcmSharp.Scheduler", "FcmSharp.Scheduler\FcmSharp.Scheduler.csproj", "{C06CCA6F-A633-475F-B29E-AA7D8C9A06DC}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FcmSharp.Scheduler.Quartz", "FcmSharp.Scheduler.Quartz\FcmSharp.Scheduler.Quartz.csproj", "{C5215216-5CDC-4DE0-A2E1-4ECFDD860995}"
11 | EndProject
12 | Global
13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
14 | Debug|Any CPU = Debug|Any CPU
15 | Release|Any CPU = Release|Any CPU
16 | EndGlobalSection
17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
18 | {E5238F02-96AC-4079-9C16-72D169E1C11A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19 | {E5238F02-96AC-4079-9C16-72D169E1C11A}.Debug|Any CPU.Build.0 = Debug|Any CPU
20 | {E5238F02-96AC-4079-9C16-72D169E1C11A}.Release|Any CPU.ActiveCfg = Release|Any CPU
21 | {E5238F02-96AC-4079-9C16-72D169E1C11A}.Release|Any CPU.Build.0 = Release|Any CPU
22 | {C06CCA6F-A633-475F-B29E-AA7D8C9A06DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {C06CCA6F-A633-475F-B29E-AA7D8C9A06DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {C06CCA6F-A633-475F-B29E-AA7D8C9A06DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {C06CCA6F-A633-475F-B29E-AA7D8C9A06DC}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {C5215216-5CDC-4DE0-A2E1-4ECFDD860995}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {C5215216-5CDC-4DE0-A2E1-4ECFDD860995}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {C5215216-5CDC-4DE0-A2E1-4ECFDD860995}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {C5215216-5CDC-4DE0-A2E1-4ECFDD860995}.Release|Any CPU.Build.0 = Release|Any CPU
30 | EndGlobalSection
31 | GlobalSection(SolutionProperties) = preSolution
32 | HideSolutionNode = FALSE
33 | EndGlobalSection
34 | GlobalSection(ExtensibilityGlobals) = postSolution
35 | SolutionGuid = {900A25E6-54F4-4714-A7A5-E257B71A032A}
36 | EndGlobalSection
37 | EndGlobal
38 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Scheduler.Quartz/Database/ApplicationDbContext.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using FcmSharp.Scheduler.Quartz.Database.Configuration;
5 | using FcmSharp.Scheduler.Quartz.Database.Model;
6 | using Microsoft.EntityFrameworkCore;
7 |
8 | namespace FcmSharp.Scheduler.Quartz.Database
9 | {
10 |
11 | public class ApplicationDbContext : DbContext
12 | {
13 | public DbSet Messages { get; set; }
14 |
15 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
16 | {
17 | optionsBuilder.UseSqlite(@"Data Source=Messaging.db");
18 | }
19 |
20 | protected override void OnModelCreating(ModelBuilder modelBuilder)
21 | {
22 | modelBuilder.ApplyConfiguration(new MessageTypeConfiguration());
23 |
24 | Seeding.SeedData(modelBuilder);
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Scheduler.Quartz/Database/Configuration/Mapping.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using FcmSharp.Scheduler.Quartz.Database.Model;
5 | using Microsoft.EntityFrameworkCore;
6 | using Microsoft.EntityFrameworkCore.Metadata.Builders;
7 |
8 | namespace FcmSharp.Scheduler.Quartz.Database.Configuration
9 | {
10 | public class MessageTypeConfiguration : IEntityTypeConfiguration
11 | {
12 | public void Configure(EntityTypeBuilder builder)
13 | {
14 | builder
15 | .ToTable("message")
16 | .HasKey(x => x.Id);
17 |
18 | builder
19 | .Property(x => x.Id)
20 | .HasColumnName("message_id")
21 | .ValueGeneratedOnAdd();
22 |
23 | builder
24 | .Property(x => x.Topic)
25 | .HasColumnName("topic")
26 | .IsRequired();
27 |
28 | builder
29 | .Property(x => x.Title)
30 | .HasColumnName("title")
31 | .IsRequired();
32 |
33 | builder
34 | .Property(x => x.Body)
35 | .HasColumnName("body")
36 | .IsRequired();
37 |
38 | builder
39 | .Property(x => x.ScheduledTime)
40 | .HasColumnName("scheduled_time")
41 | .IsRequired();
42 |
43 | builder
44 | .Property(x => x.Status)
45 | .HasConversion()
46 | .HasColumnName("status_id");
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Scheduler.Quartz/Database/Configuration/Seeding.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using Microsoft.EntityFrameworkCore;
5 |
6 | namespace FcmSharp.Scheduler.Quartz.Database.Configuration
7 | {
8 | public static class Seeding
9 | {
10 | public static void SeedData(ModelBuilder modelBuilder)
11 | {
12 | // Seed initial data here...
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Scheduler.Quartz/Database/Model/Message.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System;
5 |
6 | namespace FcmSharp.Scheduler.Quartz.Database.Model
7 | {
8 | public class Message
9 | {
10 | public int Id { get; set; }
11 |
12 | public string Topic { get; set; }
13 |
14 | public string Title { get; set; }
15 |
16 | public string Body { get; set; }
17 |
18 | public StatusEnum Status { get; set; }
19 |
20 | public DateTime ScheduledTime { get; set; }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Scheduler.Quartz/Database/Model/StatusEnum.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | namespace FcmSharp.Scheduler.Quartz.Database.Model
5 | {
6 | public enum StatusEnum
7 | {
8 | Scheduled = 1,
9 | Finished = 2,
10 | Failed = 3
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Scheduler.Quartz/Extensions/LoggerExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using Microsoft.Extensions.Logging;
5 |
6 | namespace FcmSharp.Scheduler.Quartz.Extensions
7 | {
8 | public static class LoggerExtensions
9 | {
10 | public static bool IsDebugEnabled(this ILogger logger)
11 | {
12 | return logger.IsEnabled(LogLevel.Debug);
13 | }
14 |
15 | public static bool IsCriticalEnabled(this ILogger logger)
16 | {
17 | return logger.IsEnabled(LogLevel.Critical);
18 | }
19 |
20 | public static bool IsErrorEnabled(this ILogger logger)
21 | {
22 | return logger.IsEnabled(LogLevel.Error);
23 | }
24 |
25 | public static bool IsInformationEnabled(this ILogger logger)
26 | {
27 | return logger.IsEnabled(LogLevel.Information);
28 | }
29 |
30 | public static bool IsTraceEnabled(this ILogger logger)
31 | {
32 | return logger.IsEnabled(LogLevel.Trace);
33 | }
34 |
35 | public static bool IsWarningEnabled(this ILogger logger)
36 | {
37 | return logger.IsEnabled(LogLevel.Warning);
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Scheduler.Quartz/FcmSharp.Scheduler.Quartz.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.1
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Scheduler.Quartz/Program.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using Microsoft.AspNetCore;
5 | using Microsoft.AspNetCore.Hosting;
6 |
7 | namespace FcmSharp.Scheduler.Quartz
8 | {
9 | class Program
10 | {
11 | public static void Main(string[] args)
12 | {
13 | BuildWebHost(args)
14 | .Run();
15 | }
16 |
17 | public static IWebHost BuildWebHost(string[] args) =>
18 | WebHost.CreateDefaultBuilder(args)
19 | .UseKestrel()
20 | .UseUrls("http://localhost:5000")
21 | .UseIISIntegration()
22 | .UseStartup()
23 | .Build();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Scheduler.Quartz/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "windowsAuthentication": false,
4 | "anonymousAuthentication": true,
5 | "iisExpress": {
6 | "applicationUrl": "http://localhost:61387/",
7 | "sslPort": 0
8 | }
9 | },
10 | "profiles": {
11 | "IIS Express": {
12 | "commandName": "IISExpress",
13 | "launchBrowser": true,
14 | "environmentVariables": {
15 | "ASPNETCORE_ENVIRONMENT": "Development"
16 | }
17 | },
18 | "FcmSharp.Scheduler.Quartz": {
19 | "commandName": "Project",
20 | "launchBrowser": true,
21 | "environmentVariables": {
22 | "ASPNETCORE_ENVIRONMENT": "Development"
23 | },
24 | "applicationUrl": "http://localhost:61388/"
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Scheduler.Quartz/Quartz/JobFactory/JobFactory.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System;
5 | using Quartz;
6 | using Quartz.Spi;
7 |
8 | namespace FcmSharp.Scheduler.Quartz.Quartz.JobFactory
9 | {
10 | public class JobFactory : IJobFactory
11 | {
12 | private readonly IServiceProvider container;
13 |
14 | public JobFactory(IServiceProvider container)
15 | {
16 | this.container = container;
17 | }
18 |
19 | public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
20 | {
21 | var jobType = bundle.JobDetail.JobType;
22 |
23 | return container.GetService(jobType) as IJob;
24 | }
25 |
26 | public void ReturnJob(IJob job)
27 | {
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Scheduler.Quartz/Quartz/Jobs/ProcessMessageJob.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System.Threading.Tasks;
5 | using FcmSharp.Scheduler.Quartz.Services;
6 | using Quartz;
7 |
8 | namespace FcmSharp.Scheduler.Quartz.Quartz.Jobs
9 | {
10 | public class ProcessMessageJob : IJob
11 | {
12 | public static readonly string JobDataKey = "MESSAGE_ID";
13 |
14 | private readonly IMessagingService messagingService;
15 |
16 | public ProcessMessageJob(IMessagingService messagingService)
17 | {
18 | this.messagingService = messagingService;
19 | }
20 |
21 | public async Task Execute(IJobExecutionContext context)
22 | {
23 | var cancellationToken = context.CancellationToken;
24 | var messageId = GetMessageId(context);
25 |
26 | await messagingService.SendScheduledMessageAsync(messageId, cancellationToken);
27 | }
28 |
29 | private int GetMessageId(IJobExecutionContext context)
30 | {
31 | JobDataMap jobDataMap = context.JobDetail.JobDataMap;
32 |
33 | return jobDataMap.GetIntValue(JobDataKey);
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Scheduler.Quartz/Services/Converters/MessageConverter.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using FcmSharp.Requests;
5 | using SourceType = FcmSharp.Scheduler.Quartz.Database.Model.Message;
6 | using TargetType = FcmSharp.Requests.FcmMessage;
7 |
8 | namespace FcmSharp.Scheduler.Quartz.Services.Converters
9 | {
10 | public static class MessageConverter
11 | {
12 | public static TargetType Convert(SourceType source)
13 | {
14 | if (source == null)
15 | {
16 | return null;
17 | }
18 |
19 | return new TargetType
20 | {
21 | ValidateOnly = false,
22 | Message = new Message
23 | {
24 | Topic = source.Topic,
25 | Notification = new Notification
26 | {
27 | Title = source.Title,
28 | Body = source.Body
29 | }
30 | }
31 | };
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Scheduler.Quartz/Services/MessagingService.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System;
5 | using System.Linq;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 | using FcmSharp.Scheduler.Quartz.Database;
9 | using FcmSharp.Scheduler.Quartz.Database.Model;
10 | using FcmSharp.Scheduler.Quartz.Extensions;
11 | using FcmSharp.Scheduler.Quartz.Services.Converters;
12 | using Microsoft.EntityFrameworkCore;
13 | using Microsoft.Extensions.Logging;
14 |
15 | namespace FcmSharp.Scheduler.Quartz.Services
16 | {
17 | public interface IMessagingService : IDisposable
18 | {
19 | Task SendScheduledMessageAsync(int messageId, CancellationToken cancellationToken);
20 | }
21 |
22 | public class MessagingService : IMessagingService
23 | {
24 | private readonly ILogger logger;
25 | private readonly IFcmClient client;
26 |
27 | public MessagingService(ILogger logger, IFcmClient client)
28 | {
29 | this.logger = logger;
30 | this.client = client;
31 | }
32 |
33 | public async Task SendScheduledMessageAsync(int messageId, CancellationToken cancellationToken)
34 | {
35 | if (logger.IsDebugEnabled())
36 | {
37 | logger.LogDebug($"Sending scheduled Message ID {messageId}");
38 | }
39 |
40 | var message = await GetScheduledMessageAsync(messageId, cancellationToken);
41 |
42 | await SendMessageAsync(message, cancellationToken);
43 | }
44 |
45 | private async Task SendMessageAsync(Message message, CancellationToken cancellationToken)
46 | {
47 | var target = MessageConverter.Convert(message);
48 |
49 | try
50 | {
51 | await client.SendAsync(target, cancellationToken);
52 |
53 | if (logger.IsDebugEnabled())
54 | {
55 | logger.LogDebug($"Finished sending Message ID {message.Id}");
56 | }
57 |
58 | await SetMessageStatusAsync(message, StatusEnum.Finished, cancellationToken);
59 | }
60 | catch (Exception exception)
61 | {
62 | if (logger.IsErrorEnabled())
63 | {
64 | logger.LogError(exception, $"Error sending Message ID {message.Id}");
65 | }
66 |
67 | await SetMessageStatusAsync(message, StatusEnum.Failed, cancellationToken);
68 | }
69 | }
70 |
71 | private Task GetScheduledMessageAsync(int messageId, CancellationToken cancellationToken)
72 | {
73 | using (var context = new ApplicationDbContext())
74 | {
75 | return context.Messages
76 | .Where(x => x.Status == StatusEnum.Scheduled)
77 | .Where(x => x.Id == messageId)
78 | .AsNoTracking()
79 | .FirstAsync(cancellationToken);
80 | }
81 | }
82 |
83 | private async Task SetMessageStatusAsync(Message message, StatusEnum status, CancellationToken cancellationToken)
84 | {
85 | using (var context = new ApplicationDbContext())
86 | {
87 | context.Attach(message);
88 |
89 | // Set the new Status Value:
90 | message.Status = status;
91 |
92 | // Mark the Status as modified, so it is the only updated value:
93 | context
94 | .Entry(message)
95 | .Property(x => x.Status).IsModified = true;
96 |
97 | await context.SaveChangesAsync(cancellationToken);
98 | }
99 | }
100 |
101 | public void Dispose()
102 | {
103 | client?.Dispose();
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Scheduler.Quartz/Services/SchedulerService.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 | using FcmSharp.Scheduler.Quartz.Database;
8 | using FcmSharp.Scheduler.Quartz.Database.Model;
9 | using FcmSharp.Scheduler.Quartz.Quartz.Jobs;
10 | using Quartz;
11 |
12 | namespace FcmSharp.Scheduler.Quartz.Services
13 | {
14 | public interface ISchedulerService
15 | {
16 | Task ScheduleMessageAsync(Message message, CancellationToken cancellationToken);
17 | }
18 |
19 | public class SchedulerService : ISchedulerService
20 | {
21 | private readonly IScheduler scheduler;
22 |
23 | public SchedulerService(IScheduler scheduler)
24 | {
25 | this.scheduler = scheduler;
26 | }
27 |
28 | public async Task ScheduleMessageAsync(Message message, CancellationToken cancellationToken)
29 | {
30 | await SaveJob(message, cancellationToken);
31 |
32 | IJobDetail job = JobBuilder.Create()
33 | .WithIdentity(Guid.NewGuid().ToString())
34 | .UsingJobData(ProcessMessageJob.JobDataKey, message.Id)
35 | .Build();
36 |
37 | ITrigger trigger = TriggerBuilder.Create()
38 | .WithIdentity(Guid.NewGuid().ToString())
39 | .StartAt(message.ScheduledTime)
40 | .Build();
41 |
42 | await scheduler.ScheduleJob(job, trigger, cancellationToken);
43 |
44 | return message;
45 | }
46 |
47 | private Task SaveJob(Message message, CancellationToken cancellationToken)
48 | {
49 | using (var context = new ApplicationDbContext())
50 | {
51 | context.Messages.Add(message);
52 |
53 | return context.SaveChangesAsync(cancellationToken);
54 | }
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Scheduler.Quartz/Startup.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using FcmSharp.Scheduler.Quartz.Quartz.Jobs;
5 | using FcmSharp.Scheduler.Quartz.Services;
6 | using FcmSharp.Scheduler.Quartz.Testing;
7 | using FcmSharp.Scheduler.Quartz.Web.Extensions;
8 | using Microsoft.AspNetCore.Builder;
9 | using Microsoft.AspNetCore.Hosting;
10 | using Microsoft.Extensions.Configuration;
11 | using Microsoft.Extensions.DependencyInjection;
12 |
13 | namespace FcmSharp.Scheduler.Quartz
14 | {
15 | public class Startup
16 | {
17 | public IHostingEnvironment Environment { get; set; }
18 |
19 | public IConfiguration Configuration { get; }
20 |
21 | public Startup(IHostingEnvironment env)
22 | {
23 | Environment = env;
24 |
25 | Configuration = new ConfigurationBuilder()
26 | .SetBasePath(env.ContentRootPath)
27 | .AddEnvironmentVariables()
28 | .Build();
29 | }
30 |
31 | // This method gets called by the runtime. Use this method to add services to the container.
32 | public void ConfigureServices(IServiceCollection services)
33 | {
34 | // Add a CORS Policy to allow "Everything":
35 | services.AddCors(o =>
36 | {
37 | o.AddPolicy("Everything", p =>
38 | {
39 | p.AllowAnyHeader()
40 | .AllowAnyMethod()
41 | .AllowAnyOrigin();
42 | });
43 | });
44 |
45 | services
46 | .AddOptions()
47 | .AddQuartz()
48 | .AddTransient()
49 | .AddTransient()
50 | .AddTransient()
51 | .AddTransient()
52 | .AddMvc();
53 | }
54 |
55 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
56 | public void Configure(IApplicationBuilder app, IHostingEnvironment env)
57 | {
58 | app.EnsureDatabaseCreated()
59 | .UseCors("Everything")
60 | .UseStaticFiles()
61 | .UseQuartz()
62 | .UseMvc();
63 | }
64 | }
65 | }
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Scheduler.Quartz/Testing/MockFcmClient.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 | using FcmSharp.Requests;
8 | using FcmSharp.Responses;
9 | using FcmSharp.Scheduler.Quartz.Extensions;
10 | using Microsoft.Extensions.Logging;
11 | using Newtonsoft.Json;
12 |
13 | namespace FcmSharp.Scheduler.Quartz.Testing
14 | {
15 | public class MockFcmClient : IFcmClient
16 | {
17 | private readonly ILogger logger;
18 |
19 | public MockFcmClient(ILogger logger)
20 | {
21 | this.logger = logger;
22 | }
23 |
24 | public Task SendAsync(FcmMessage message, CancellationToken cancellationToken = new CancellationToken())
25 | {
26 | if (logger.IsDebugEnabled())
27 | {
28 | var messageContent = JsonConvert.SerializeObject(message, Formatting.Indented);
29 |
30 | logger.LogDebug($"Sending Message with Content = {messageContent}");
31 | }
32 |
33 | return Task.FromResult(new FcmMessageResponse());
34 | }
35 |
36 | public Task SubscribeToTopic(TopicManagementRequest request, CancellationToken cancellationToken = new CancellationToken())
37 | {
38 | return Task.FromResult(new TopicManagementResponse());
39 | }
40 |
41 | public Task UnsubscribeFromTopic(TopicManagementRequest request, CancellationToken cancellationToken = new CancellationToken())
42 | {
43 | return Task.FromResult(new TopicManagementResponse());
44 | }
45 |
46 | public void Dispose()
47 | {
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Scheduler.Quartz/Web/Contracts/Message.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using Newtonsoft.Json;
5 | using Newtonsoft.Json.Converters;
6 | using System;
7 |
8 | namespace FcmSharp.Scheduler.Quartz.Web.Contracts
9 | {
10 | public class Message
11 | {
12 | [JsonProperty("id")]
13 | public int Id { get; set; }
14 |
15 | [JsonProperty("topic")]
16 | public string Topic { get; set; }
17 |
18 | [JsonProperty("title")]
19 | public string Title { get; set; }
20 |
21 | [JsonProperty("body")]
22 | public string Body { get; set; }
23 |
24 | [JsonProperty("status")]
25 | [JsonConverter(typeof(StringEnumConverter))]
26 | public StatusEnum Status { get; set; }
27 |
28 | [JsonProperty("scheduledTime")]
29 | public DateTime ScheduledTime { get; set; }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Scheduler.Quartz/Web/Contracts/StatusEnum.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | namespace FcmSharp.Scheduler.Quartz.Web.Contracts
5 | {
6 | public enum StatusEnum
7 | {
8 | Scheduled = 1,
9 | Finished = 2,
10 | Failed = 3
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Scheduler.Quartz/Web/Controllers/SchedulerController.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 | using FcmSharp.Scheduler.Quartz.Services;
7 | using FcmSharp.Scheduler.Quartz.Web.Contracts;
8 | using FcmSharp.Scheduler.Quartz.Web.Converters;
9 | using Microsoft.AspNetCore.Mvc;
10 |
11 | namespace FcmSharp.Scheduler.Quartz.Web.Controllers
12 | {
13 | [Controller]
14 | [Route("scheduler")]
15 | public class SchedulerController : ControllerBase
16 | {
17 | private readonly ISchedulerService schedulerService;
18 |
19 | public SchedulerController(ISchedulerService schedulerService)
20 | {
21 | this.schedulerService = schedulerService;
22 | }
23 |
24 | [HttpPost]
25 | public async Task Post([FromBody] Message message, CancellationToken cancellationToken)
26 | {
27 | // Convert into the Database Representation:
28 | var target = MessageConverter.Convert(message);
29 |
30 | // Save and Schedule:
31 | var result = await schedulerService.ScheduleMessageAsync(target, cancellationToken);
32 |
33 | return Ok(result);
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Scheduler.Quartz/Web/Converters/MessageConverter.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System;
5 |
6 | namespace FcmSharp.Scheduler.Quartz.Web.Converters
7 | {
8 | public static class MessageConverter
9 | {
10 | public static Database.Model.Message Convert(Contracts.Message source)
11 | {
12 | if (source == null)
13 | {
14 | return null;
15 | }
16 |
17 | return new Database.Model.Message
18 | {
19 | Id = source.Id,
20 | Topic = source.Topic,
21 | Title = source.Title,
22 | Body = source.Body,
23 | ScheduledTime = source.ScheduledTime,
24 | Status = Convert(source.Status)
25 | };
26 | }
27 |
28 | public static Database.Model.StatusEnum Convert(Contracts.StatusEnum source)
29 | {
30 | switch (source)
31 | {
32 | case Contracts.StatusEnum.Scheduled:
33 | return Database.Model.StatusEnum.Scheduled;
34 | case Contracts.StatusEnum.Finished:
35 | return Database.Model.StatusEnum.Finished;
36 | case Contracts.StatusEnum.Failed:
37 | return Database.Model.StatusEnum.Failed;
38 | default:
39 | throw new ArgumentException($"Unknown Source StatusEnum {source}");
40 | }
41 | }
42 |
43 | public static Contracts.Message Convert(Database.Model.Message source)
44 | {
45 | if (source == null)
46 | {
47 | return null;
48 | }
49 |
50 | return new Contracts.Message
51 | {
52 | Id = source.Id,
53 | Topic = source.Topic,
54 | Title = source.Title,
55 | Body = source.Body,
56 | ScheduledTime = source.ScheduledTime,
57 | Status = Convert(source.Status)
58 | };
59 | }
60 |
61 | public static Contracts.StatusEnum Convert(Database.Model.StatusEnum source)
62 | {
63 | switch (source)
64 | {
65 | case Database.Model.StatusEnum.Scheduled:
66 | return Contracts.StatusEnum.Scheduled;
67 | case Database.Model.StatusEnum.Finished:
68 | return Contracts.StatusEnum.Finished;
69 | case Database.Model.StatusEnum.Failed:
70 | return Contracts.StatusEnum.Failed;
71 | default:
72 | throw new ArgumentException($"Unknown Source StatusEnum {source}");
73 | }
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Scheduler.Quartz/Web/Extensions/DatabaseExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using FcmSharp.Scheduler.Quartz.Database;
5 | using Microsoft.AspNetCore.Builder;
6 |
7 | namespace FcmSharp.Scheduler.Quartz.Web.Extensions
8 | {
9 | public static class DatabaseExtensions
10 | {
11 | public static IApplicationBuilder EnsureDatabaseCreated(this IApplicationBuilder app)
12 | {
13 | using (var context = new ApplicationDbContext())
14 | {
15 | context.Database.EnsureCreated();
16 | }
17 |
18 | return app;
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Scheduler.Quartz/Web/Extensions/QuartzExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using FcmSharp.Scheduler.Quartz.Quartz.JobFactory;
5 | using Microsoft.AspNetCore.Builder;
6 | using Microsoft.Extensions.DependencyInjection;
7 |
8 | using Quartz;
9 | using Quartz.Impl;
10 | using Quartz.Spi;
11 |
12 | namespace FcmSharp.Scheduler.Quartz.Web.Extensions
13 | {
14 | public static class QuartzExtensions
15 | {
16 | public static IApplicationBuilder UseQuartz(this IApplicationBuilder app)
17 | {
18 | var scheduler = app.ApplicationServices.GetService();
19 |
20 | scheduler.Start().GetAwaiter().GetResult();
21 |
22 | return app;
23 | }
24 |
25 | public static IServiceCollection AddQuartz(this IServiceCollection services)
26 | {
27 | services.AddSingleton();
28 | services.AddSingleton(provider =>
29 | {
30 | var schedulerFactory = new StdSchedulerFactory();
31 | var scheduler = schedulerFactory.GetScheduler().GetAwaiter().GetResult();
32 |
33 | scheduler.JobFactory = provider.GetService();
34 |
35 | return scheduler;
36 | });
37 |
38 | return services;
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Scheduler.Quartz/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Debug",
5 | "System": "Debug",
6 | "Microsoft": "Debug"
7 | }
8 | }
9 | }
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Scheduler/Converters/MessageConverter.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using FcmSharp.Requests;
4 | using SourceType = FcmSharp.Scheduler.Database.Model.Message;
5 | using TargetType = FcmSharp.Requests.FcmMessage;
6 |
7 | namespace FcmSharp.Scheduler.Converters
8 | {
9 | public static class MessageConverter
10 | {
11 | public static TargetType Convert(SourceType source)
12 | {
13 | if (source == null)
14 | {
15 | return null;
16 | }
17 |
18 | return new TargetType
19 | {
20 | ValidateOnly = false,
21 | Message = new Message
22 | {
23 | Topic = source.Topic,
24 | Notification = new Notification
25 | {
26 | Title = source.Title,
27 | Body = source.Body
28 | }
29 | }
30 | };
31 | }
32 |
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Scheduler/Database/ApplicationDbContext.cs:
--------------------------------------------------------------------------------
1 | using FcmSharp.Scheduler.Database.Configuration;
2 | using FcmSharp.Scheduler.Database.Model;
3 | using Microsoft.EntityFrameworkCore;
4 |
5 | namespace FcmSharp.Scheduler.Database
6 | {
7 |
8 | public class ApplicationDbContext : DbContext
9 | {
10 | public DbSet Messages { get; set; }
11 |
12 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
13 | {
14 | optionsBuilder.UseSqlite(@"Data Source=Messaging.db");
15 | }
16 |
17 | protected override void OnModelCreating(ModelBuilder modelBuilder)
18 | {
19 | modelBuilder.ApplyConfiguration(new MessageTypeConfiguration());
20 |
21 | Seeding.SeedData(modelBuilder);
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Scheduler/Database/Configuration/Mapping.cs:
--------------------------------------------------------------------------------
1 | using FcmSharp.Scheduler.Database.Model;
2 | using Microsoft.EntityFrameworkCore;
3 | using Microsoft.EntityFrameworkCore.Metadata.Builders;
4 |
5 | namespace FcmSharp.Scheduler.Database.Configuration
6 | {
7 | public class MessageTypeConfiguration : IEntityTypeConfiguration
8 | {
9 | public void Configure(EntityTypeBuilder builder)
10 | {
11 | builder
12 | .ToTable("message")
13 | .HasKey(x => x.Id);
14 |
15 | builder
16 | .Property(x => x.Id)
17 | .HasColumnName("message_id")
18 | .ValueGeneratedOnAdd();
19 |
20 | builder
21 | .Property(x => x.Topic)
22 | .HasColumnName("topic")
23 | .IsRequired();
24 |
25 | builder
26 | .Property(x => x.Title)
27 | .HasColumnName("title")
28 | .IsRequired();
29 |
30 | builder
31 | .Property(x => x.Body)
32 | .HasColumnName("body")
33 | .IsRequired();
34 |
35 | builder
36 | .Property(x => x.ScheduledTime)
37 | .HasColumnName("scheduled_time")
38 | .IsRequired();
39 |
40 | builder
41 | .Property(x => x.Status)
42 | .HasConversion()
43 | .HasColumnName("status_id");
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Scheduler/Database/Configuration/Seeding.cs:
--------------------------------------------------------------------------------
1 | using FcmSharp.Scheduler.Database.Model;
2 | using Microsoft.EntityFrameworkCore;
3 |
4 | namespace FcmSharp.Scheduler.Database.Configuration
5 | {
6 | public static class Seeding
7 | {
8 | public static void SeedData(ModelBuilder modelBuilder)
9 | {
10 | // TODO Seed Data
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Scheduler/Database/Model/Message.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace FcmSharp.Scheduler.Database.Model
6 | {
7 | public class Message
8 | {
9 | public int Id { get; set; }
10 |
11 | public string Topic { get; set; }
12 |
13 | public string Title { get; set; }
14 |
15 | public string Body { get; set; }
16 |
17 | public StatusEnum Status { get; set; }
18 |
19 | public DateTime ScheduledTime { get; set; }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Scheduler/Database/Model/StatusEnum.cs:
--------------------------------------------------------------------------------
1 | namespace FcmSharp.Scheduler.Database.Model
2 | {
3 | public enum StatusEnum
4 | {
5 | Scheduled = 1,
6 |
7 | Finished = 2,
8 |
9 | Failed = 3
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Scheduler/FcmSharp.Scheduler.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp2.1
6 | 7.1
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Scheduler/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using FcmSharp.Scheduler.Database;
5 | using FcmSharp.Scheduler.Services;
6 | using FcmSharp.Settings;
7 |
8 | namespace FcmSharp.Scheduler
9 | {
10 | class Program
11 | {
12 | private static readonly TimeSpan PollingInterval = TimeSpan.FromMinutes(1);
13 |
14 | static async Task Main(string[] args)
15 | {
16 | var cancellationToken = CancellationToken.None;
17 |
18 | // Initializes the Database:
19 | await CreateDatabase(cancellationToken);
20 |
21 | // Starts the Scheduling Loop for Message Scheduling:
22 | await ProcessMessages(cancellationToken);
23 | }
24 |
25 |
26 | public static async Task ProcessMessages(CancellationToken cancellationToken)
27 | {
28 | var service = CreateSchedulerService();
29 |
30 | while (!cancellationToken.IsCancellationRequested)
31 | {
32 | await Task.Delay(PollingInterval, cancellationToken);
33 |
34 | if (!cancellationToken.IsCancellationRequested)
35 | {
36 | DateTime scheduledTime = DateTime.UtcNow;
37 |
38 | Console.WriteLine($"[${DateTime.Now}] [INFO] Sending Messages scheduled at {scheduledTime}");
39 |
40 | await service.SendScheduledMessagesAsync(scheduledTime, cancellationToken);
41 | }
42 | }
43 |
44 | service.Dispose();
45 | }
46 |
47 | private static Task CreateDatabase(CancellationToken cancellationToken)
48 | {
49 | using (var context = new ApplicationDbContext())
50 | {
51 | return context.Database.EnsureCreatedAsync(cancellationToken);
52 | }
53 | }
54 |
55 | private static ISchedulerService CreateSchedulerService()
56 | {
57 | // Read the Credentials from a File, which is not under Version Control:
58 | var settings = FileBasedFcmClientSettings.CreateFromFile(@"D:\serviceAccountKey.json");
59 |
60 | // Construct the Client:
61 | var client = new FcmClient(settings);
62 |
63 | return new SchedulerService(client);
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Scheduler/Services/SchedulerService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 | using FcmSharp.Scheduler.Converters;
8 | using FcmSharp.Scheduler.Database;
9 | using FcmSharp.Scheduler.Database.Model;
10 | using Microsoft.EntityFrameworkCore;
11 |
12 | namespace FcmSharp.Scheduler.Services
13 | {
14 | public interface ISchedulerService :IDisposable
15 | {
16 | Task SendScheduledMessagesAsync(DateTime scheduledTime, CancellationToken cancellationToken);
17 | }
18 |
19 | public class SchedulerService : ISchedulerService
20 | {
21 | private readonly IFcmClient client;
22 |
23 | public SchedulerService(IFcmClient client)
24 | {
25 | this.client = client;
26 | }
27 |
28 | public async Task SendScheduledMessagesAsync(DateTime scheduledTime, CancellationToken cancellationToken)
29 | {
30 | var messages = await GetScheduledMessagesAsync(scheduledTime, cancellationToken);
31 |
32 | await SendMessagesAsync(messages, cancellationToken);
33 | }
34 |
35 | private async Task SendMessagesAsync(List messages, CancellationToken cancellationToken)
36 | {
37 | foreach (var message in messages)
38 | {
39 | var target = MessageConverter.Convert(message);
40 |
41 | try
42 | {
43 | await client.SendAsync(target, cancellationToken);
44 | await SetMessageStatusAsync(message, StatusEnum.Finished, cancellationToken);
45 | }
46 | catch (Exception e)
47 | {
48 | Console.WriteLine($"[Error] {e.StackTrace}");
49 |
50 | await SetMessageStatusAsync(message, StatusEnum.Failed, cancellationToken);
51 | }
52 | }
53 | }
54 |
55 | private Task> GetScheduledMessagesAsync(DateTime scheduledTime, CancellationToken cancellationToken)
56 | {
57 | using (var context = new ApplicationDbContext())
58 | {
59 | return context.Messages
60 | .Where(x => x.Status == StatusEnum.Scheduled)
61 | .Where(x => x.ScheduledTime <= scheduledTime)
62 | .AsNoTracking()
63 | .ToListAsync(cancellationToken);
64 | }
65 | }
66 |
67 | private async Task SetMessageStatusAsync(Message message, StatusEnum status, CancellationToken cancellationToken)
68 | {
69 | using (var context = new ApplicationDbContext())
70 | {
71 | context.Attach(message);
72 |
73 | // Set the new Status Value:
74 | message.Status = status;
75 |
76 | // Mark the Status as modified, so it is the only updated value:
77 | context
78 | .Entry(message)
79 | .Property(x => x.Status).IsModified = true;
80 |
81 | await context.SaveChangesAsync(cancellationToken);
82 | }
83 | }
84 |
85 | public void Dispose()
86 | {
87 | client?.Dispose();
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/CSharp/FcmSharp.Scheduler/Testing/MockFcmClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Globalization;
4 | using System.Text;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 | using FcmSharp.Requests;
8 | using FcmSharp.Responses;
9 | using Newtonsoft.Json;
10 |
11 | namespace FcmSharp.Scheduler.Testing
12 | {
13 | public class MockFcmClient : IFcmClient
14 | {
15 | public Task SendAsync(FcmMessage message, CancellationToken cancellationToken = new CancellationToken())
16 | {
17 | var content = JsonConvert.SerializeObject(message, Formatting.Indented);
18 |
19 | Console.WriteLine($"[{DateTime.Now}] [SendAsync] {content} ...");
20 |
21 | return Task.FromResult(new FcmMessageResponse());
22 | }
23 |
24 | public Task SubscribeToTopic(TopicManagementRequest request, CancellationToken cancellationToken = new CancellationToken())
25 | {
26 | return Task.FromResult(new TopicManagementResponse());
27 | }
28 |
29 | public Task UnsubscribeFromTopic(TopicManagementRequest request, CancellationToken cancellationToken = new CancellationToken())
30 | {
31 | return Task.FromResult(new TopicManagementResponse());
32 | }
33 |
34 | public void Dispose()
35 | {
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/FcmSharp/Examples/Images/Screenshot_20181118-100555.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/Examples/Images/Screenshot_20181118-100555.png
--------------------------------------------------------------------------------
/FcmSharp/Examples/Images/Screenshot_20181118-100611.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/Examples/Images/Screenshot_20181118-100611.png
--------------------------------------------------------------------------------
/FcmSharp/Examples/Images/Screenshot_20181118-101026.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/Examples/Images/Screenshot_20181118-101026.png
--------------------------------------------------------------------------------
/FcmSharp/Examples/Images/Screenshot_20181118-103856.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/Examples/Images/Screenshot_20181118-103856.png
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp.Console/FcmSharp.ConsoleApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp2.0
6 | FcmSharp.ConsoleApp
7 | FcmSharp.ConsoleApp
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp.Console/Program.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System;
5 | using System.Threading;
6 | using FcmSharp.Requests;
7 | using FcmSharp.Settings;
8 |
9 | namespace FcmSharp.ConsoleApp
10 | {
11 | public class Program
12 | {
13 | public static void Main(string[] args)
14 | {
15 | // Read the Credentials from a File, which is not under Version Control:
16 | var settings = FileBasedFcmClientSettings.CreateFromFile(@"D:\serviceAccountKey.json");
17 |
18 | // Construct the Client:
19 | using (var client = new FcmClient(settings))
20 | {
21 | var notification = new Notification
22 | {
23 | Title = "Notification Title",
24 | Body = "Notification Body Text"
25 | };
26 |
27 | // The Message should be sent to the News Topic:
28 | var message = new FcmMessage()
29 | {
30 | ValidateOnly = false,
31 | Message = new Message
32 | {
33 | Topic = "news",
34 | Notification = notification
35 | }
36 | };
37 |
38 | // Finally send the Message and wait for the Result:
39 | CancellationTokenSource cts = new CancellationTokenSource();
40 |
41 | // Send the Message and wait synchronously:
42 | var result = client.SendAsync(message, cts.Token).GetAwaiter().GetResult();
43 |
44 | // Print the Result to the Console:
45 | Console.WriteLine("Data Message ID = {0}", result.Name);
46 |
47 | Console.WriteLine("Press Enter to exit ...");
48 | Console.ReadLine();
49 | }
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp.Test/FcmSharp.Test.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.1
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp.Test/Integration/BatchMessageIntegrationTest.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System.IO;
5 | using System.Threading;
6 | using FcmSharp.Requests;
7 | using FcmSharp.Settings;
8 | using NUnit.Framework;
9 |
10 | namespace FcmSharp.Test.Integration
11 | {
12 | [TestFixture]
13 | class BatchMessageIntegrationTest
14 | {
15 | [Test]
16 | public void SendBatchMessages()
17 | {
18 | // This is a list of Tokens I want to send the Batch Message to:
19 | var tokens = File.ReadAllLines(@"D:\device_tokens.txt");
20 |
21 | // Read the Credentials from a File, which is not under Version Control:
22 | var settings = FileBasedFcmClientSettings.CreateFromFile(@"D:\serviceAccountKey.json");
23 |
24 | // Construct the Client:
25 | using (var client = new FcmClient(settings))
26 | {
27 |
28 | Message message = new Message
29 | {
30 | Notification = new Notification
31 | {
32 | Title = "Notification Title",
33 | Body = "Notification Body Text"
34 | }
35 | };
36 |
37 | // Finally send the Message and wait for the Result:
38 | CancellationTokenSource cts = new CancellationTokenSource();
39 |
40 | // Send the Message and wait synchronously:
41 | var result = client.SendMulticastMessage(tokens, message, false, cts.Token).GetAwaiter().GetResult();
42 |
43 | // Print the Result to the Console:
44 | foreach (var response in result.Responses)
45 | {
46 | Assert.IsNotNull(response.Name);
47 | }
48 | }
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp.Test/Integration/IntegrationTest.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System;
5 | using Microsoft.AspNetCore.Builder;
6 | using Microsoft.AspNetCore.Hosting;
7 | using Microsoft.Extensions.DependencyInjection;
8 | using NUnit.Framework;
9 | using System.IO;
10 | using System.Net.Http;
11 | using System.Threading;
12 | using System.Threading.Tasks;
13 | using FcmSharp.BackOff;
14 | using FcmSharp.Exceptions;
15 | using FcmSharp.Http.Builder;
16 | using FcmSharp.Http.Client;
17 | using FcmSharp.Settings;
18 | using Google.Apis.Http;
19 | using Microsoft.AspNetCore.Mvc;
20 |
21 | namespace FcmSharp.Test.Integration
22 | {
23 | public static class GlobalState
24 | {
25 | public static int RequestNumber = 0;
26 | }
27 |
28 | public class Startup
29 | {
30 | public void ConfigureServices(IServiceCollection services)
31 | {
32 | services.AddMvc();
33 | }
34 |
35 | public void Configure(IApplicationBuilder app)
36 | {
37 | app.UseMvc();
38 | }
39 | }
40 |
41 | ///
42 | /// A Controller, which "simulates" the FCM Server. It isn't a beauty, but works.
43 | ///
44 | public class RetryableSampleController : Controller
45 | {
46 | public RetryableSampleController()
47 | {
48 | }
49 |
50 | [HttpGet]
51 | [Route("return503_UntilRequestFour")]
52 | public IActionResult Returns503Until()
53 | {
54 | // Request received:
55 | GlobalState.RequestNumber = GlobalState.RequestNumber + 1;
56 |
57 | // If this is Request 4, return HTTP Status 200:
58 | if (GlobalState.RequestNumber % 4 == 0)
59 | {
60 | return Ok();
61 | }
62 |
63 | return StatusCode(503);
64 | }
65 | }
66 |
67 | [TestFixture]
68 | public class IntegrationTest
69 | {
70 | private IWebHost host;
71 |
72 | [SetUp]
73 | public void SetUp()
74 | {
75 | // Reset Global Request Counter:
76 | GlobalState.RequestNumber = 0;
77 |
78 | // Use Kestrel to Host the Controller:
79 | var builder = new WebHostBuilder()
80 | .UseKestrel()
81 | .UseStartup()
82 | .UseUrls("http://localhost:8081")
83 | .UseContentRoot(Directory.GetCurrentDirectory());
84 |
85 | // Build the Host:
86 | this.host = builder.Build();
87 |
88 | // And... Ignite!
89 | host.Start();
90 | }
91 |
92 | [Test]
93 | [Description("This Test uses the Default Settings and should run for approximately 8 Seconds!")]
94 | public async Task ExponentialBackoff503Test()
95 | {
96 | // This needs to be a valid Service Account Credentials File. Can't mock it away:
97 | var settings = FileBasedFcmClientSettings.CreateFromFile(@"D:\serviceAccountKey.json");
98 |
99 | // Initialize a new FcmHttpClient to send to localhost:
100 | var client = new FcmHttpClient(settings);
101 |
102 | // Construct a Fake Message:
103 | var builder = new HttpRequestMessageBuilder("http://localhost:8081/return503_UntilRequestFour", HttpMethod.Get);
104 |
105 | CancellationToken longLivingCancellationToken = new CancellationTokenSource(TimeSpan.FromMinutes(5)).Token;
106 |
107 | await client.SendAsync(builder, longLivingCancellationToken);
108 | }
109 |
110 | [Test]
111 | [Description("This Test configures only 2 Retries for a 503 HTTP Status Code. " +
112 | "It should fail, because the mock endpoint only reports success " +
113 | "after 4 requests to the API.")]
114 | public async Task ExponentialBackoff503TooFewRetriesTest()
115 | {
116 | // Construct new ExponentialBackOffSettings:
117 | var exponentialBackOffSettings = new ExponentialBackOffSettings(2, TimeSpan.FromMilliseconds(250), TimeSpan.FromSeconds(30));
118 |
119 | // This needs to be a valid Service Account Credentials File. Can't mock it away:
120 | var settings = FileBasedFcmClientSettings.CreateFromFile(@"D:\serviceAccountKey.json", exponentialBackOffSettings);
121 |
122 | // Initialize a new FcmHttpClient to send to localhost:
123 | var client = new FcmHttpClient(settings);
124 |
125 | // Construct a Fake Message:
126 | var builder = new HttpRequestMessageBuilder("http://localhost:8081/return503_UntilRequestFour", HttpMethod.Get);
127 |
128 | CancellationToken longLivingCancellationToken = new CancellationTokenSource(TimeSpan.FromMinutes(5)).Token;
129 |
130 | bool fcmHttpExceptionWasThrown = false;
131 |
132 | try
133 | {
134 | await client.SendAsync(builder, longLivingCancellationToken);
135 | }
136 | catch (FcmHttpException)
137 | {
138 | fcmHttpExceptionWasThrown = true;
139 | }
140 |
141 | Assert.IsTrue(fcmHttpExceptionWasThrown);
142 | }
143 |
144 |
145 | [TearDown]
146 | public void TearDown()
147 | {
148 | host.Dispose();
149 | }
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp.Test/Integration/ProxyIntegrationTest.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Net;
7 | using NUnit.Framework;
8 | using System.Threading;
9 | using FcmSharp.Http.Client;
10 | using FcmSharp.Http.Proxy;
11 | using FcmSharp.Requests;
12 | using FcmSharp.Settings;
13 |
14 | namespace FcmSharp.Test.Integration
15 | {
16 | [TestFixture]
17 | public class ProxyIntegrationTest
18 | {
19 | ///
20 | ///
21 | /// Test Instructions for Windows 10 (https://superuser.com/questions/180480/how-to-simulate-corporate-proxy-server-on-my-development-machine):
22 | ///
23 | /// > 1. Download and run Fiddler proxy (it's free). It will automatically set itself as a system proxy in Windows on
24 | /// > each run. Also click Rules -> Require Proxy Authentication in the top menu if you want to test authentication
25 | /// > to the proxy (username and password are "1").
26 | /// >
27 | /// > 2. Open Windows Firewall, then Advanced settings -> Windows Firewall Properties. Block all outbound connections
28 | /// > for all profiles you need (domain, private, public) and click OK.
29 | /// >
30 | /// > 3. Add new outbound firewall rule to allow all access for 8888 port (default Fiddler port) or "%LocalAppData%\Programs\Fiddler\Fiddler.exe" app.
31 | /// >
32 | /// > That's it, only the programs which use your proxy settings (http://1:1@127.0.0.1:8888) will work.
33 | ///
34 | [Test]
35 | [Description("This Test uses Fiddler to enforce a Proxy and sends a Message using the Proxy settings")]
36 | [Ignore("This Test uses Fiddler Proxy to test Proxy Functionality")]
37 | public void SendFcmMessageUsingProxyTest()
38 | {
39 | // This needs to be a valid Service Account Credentials File. Can't mock it away:
40 | var settings = FileBasedFcmClientSettings.CreateFromFile("your_project_id", @"D:\serviceAccountKey.json");
41 |
42 | // Define the Proxy URI to be used:
43 | var proxy = new Uri("http://localhost:8888");
44 |
45 | // Define the Username and Password ("1", because I am using Fiddler for Testing):
46 | var credentials = new NetworkCredential("1", "1");
47 |
48 | // Build the HTTP Client Factory:
49 | var httpClientFactory = new ProxyHttpClientFactory(proxy, credentials);
50 |
51 | // Initialize a new FcmHttpClient to send to localhost:
52 | var fcmHttpClient = new FcmHttpClient(settings, httpClientFactory);
53 |
54 | // Construct the Firebase Client:
55 | using (var client = new FcmClient(settings, fcmHttpClient))
56 | {
57 | // Construct the Notification Payload to send:
58 | var notification = new Notification
59 | {
60 | Title = "Title Text",
61 | Body = "Notification Body Text"
62 | };
63 |
64 | // The Message should be sent to the News Topic:
65 | var message = new FcmMessage()
66 | {
67 | ValidateOnly = false,
68 | Message = new Message
69 | {
70 | Topic = "news",
71 | Notification = notification
72 | }
73 | };
74 |
75 | // Finally send the Message and wait for the Result:
76 | CancellationTokenSource cts = new CancellationTokenSource();
77 |
78 | // Send the Message and wait synchronously:
79 | var result = client.SendAsync(message, cts.Token).GetAwaiter().GetResult();
80 |
81 | Console.WriteLine(result);
82 | }
83 | }
84 | }
85 | }
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp.Test/Requests/AndroidConfigTest.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System;
5 | using System.Collections.Generic;
6 | using FcmSharp.Requests;
7 | using FcmSharp.Serializer;
8 | using NUnit.Framework;
9 |
10 | namespace FcmSharp.Test.Requests
11 | {
12 | [TestFixture]
13 | public class AndroidConfigTest
14 | {
15 | private static readonly IJsonSerializer serializer = JsonSerializer.Default;
16 |
17 | [Test]
18 | public void AndroidConfigSerializationDeserializationTest()
19 | {
20 | AndroidConfig config = new AndroidConfig()
21 | {
22 | CollapseKey = "collapse_key",
23 | Data = new Dictionary() { { "A", "B" } },
24 | Priority = AndroidMessagePriorityEnum.HIGH,
25 | Notification = new AndroidNotification()
26 | {
27 | BodyLocArgs = new[] { "1", "2"},
28 | Body = "body",
29 | Color = "color",
30 | Tag = "tag",
31 | BodyLocKey = "body_loc_key",
32 | ClickAction = "click_action",
33 | Sound = "sound",
34 | Icon = "icon",
35 | Title = "title",
36 | TitleLocArgs = new [] { "3", "4"},
37 | TitleLocKey = "title_loc_key"
38 | },
39 | TimeToLive = TimeSpan.FromSeconds(10),
40 | RestrictedPackageName = "restricted_package_name"
41 | };
42 |
43 | var result = serializer.SerializeObject(config);
44 |
45 | Assert.IsNotNull(result);
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp.Test/Requests/ApnsConfigTest.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System.Collections.Generic;
5 | using FcmSharp.Requests;
6 | using FcmSharp.Serializer;
7 | using NUnit.Framework;
8 |
9 | namespace FcmSharp.Test.Requests
10 | {
11 | [TestFixture]
12 | public class ApnsConfigTest
13 | {
14 | private static readonly IJsonSerializer serializer = JsonSerializer.Default;
15 |
16 | [Test]
17 | public void ApnsConfigSerializationTest()
18 | {
19 | ApnsConfig config = new ApnsConfig()
20 | {
21 | Payload = new ApnsConfigPayload()
22 | {
23 | Aps = new Aps()
24 | {
25 | Badge = 1,
26 | Alert = new ApsAlert()
27 | {
28 | TitleLocKey = "title_loc_key",
29 | ActionLocKey = "action_loc_key",
30 | TitleLocArgs = new[] { "1", "2" },
31 | Title = "Title",
32 | Body = "Body",
33 | LaunchImage = "LaunchImage",
34 | LocArgs = new[] { "3", "4" },
35 | LocKey = "LocKey"
36 | },
37 | Category = "category",
38 | Sound = "sound",
39 | CustomData = new Dictionary()
40 | {
41 | {"CustomKey1", "CustomValue1"}
42 | },
43 | ContentAvailable = true,
44 | MutableContent = true,
45 | ThreadId = "1"
46 | },
47 | CustomData = new Dictionary()
48 | {
49 | {"CustomKey2", "CustomValue2"}
50 | }
51 | }
52 | };
53 |
54 | string result = serializer.SerializeObject(config);
55 |
56 | Assert.IsNotNull(result);
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.27130.2010
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FcmSharp.Test", "FcmSharp.Test\FcmSharp.Test.csproj", "{1303F957-D15D-46F4-B589-9014499F0C36}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FcmSharp.ConsoleApp", "FcmSharp.Console\FcmSharp.ConsoleApp.csproj", "{C6F25AD7-4919-4F3F-AD7C-2A2D40C8ABCB}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FcmSharp", "FcmSharp\FcmSharp.csproj", "{8C9F9383-603C-4045-B0FE-D9D04102FB7F}"
11 | EndProject
12 | Global
13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
14 | Debug|Any CPU = Debug|Any CPU
15 | Debug|x64 = Debug|x64
16 | Release|Any CPU = Release|Any CPU
17 | Release|x64 = Release|x64
18 | EndGlobalSection
19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
20 | {1303F957-D15D-46F4-B589-9014499F0C36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {1303F957-D15D-46F4-B589-9014499F0C36}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {1303F957-D15D-46F4-B589-9014499F0C36}.Debug|x64.ActiveCfg = Debug|Any CPU
23 | {1303F957-D15D-46F4-B589-9014499F0C36}.Debug|x64.Build.0 = Debug|Any CPU
24 | {1303F957-D15D-46F4-B589-9014499F0C36}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {1303F957-D15D-46F4-B589-9014499F0C36}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {1303F957-D15D-46F4-B589-9014499F0C36}.Release|x64.ActiveCfg = Release|Any CPU
27 | {1303F957-D15D-46F4-B589-9014499F0C36}.Release|x64.Build.0 = Release|Any CPU
28 | {C6F25AD7-4919-4F3F-AD7C-2A2D40C8ABCB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29 | {C6F25AD7-4919-4F3F-AD7C-2A2D40C8ABCB}.Debug|Any CPU.Build.0 = Debug|Any CPU
30 | {C6F25AD7-4919-4F3F-AD7C-2A2D40C8ABCB}.Debug|x64.ActiveCfg = Debug|Any CPU
31 | {C6F25AD7-4919-4F3F-AD7C-2A2D40C8ABCB}.Debug|x64.Build.0 = Debug|Any CPU
32 | {C6F25AD7-4919-4F3F-AD7C-2A2D40C8ABCB}.Release|Any CPU.ActiveCfg = Release|Any CPU
33 | {C6F25AD7-4919-4F3F-AD7C-2A2D40C8ABCB}.Release|Any CPU.Build.0 = Release|Any CPU
34 | {C6F25AD7-4919-4F3F-AD7C-2A2D40C8ABCB}.Release|x64.ActiveCfg = Release|Any CPU
35 | {C6F25AD7-4919-4F3F-AD7C-2A2D40C8ABCB}.Release|x64.Build.0 = Release|Any CPU
36 | {8C9F9383-603C-4045-B0FE-D9D04102FB7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
37 | {8C9F9383-603C-4045-B0FE-D9D04102FB7F}.Debug|Any CPU.Build.0 = Debug|Any CPU
38 | {8C9F9383-603C-4045-B0FE-D9D04102FB7F}.Debug|x64.ActiveCfg = Debug|Any CPU
39 | {8C9F9383-603C-4045-B0FE-D9D04102FB7F}.Debug|x64.Build.0 = Debug|Any CPU
40 | {8C9F9383-603C-4045-B0FE-D9D04102FB7F}.Release|Any CPU.ActiveCfg = Release|Any CPU
41 | {8C9F9383-603C-4045-B0FE-D9D04102FB7F}.Release|Any CPU.Build.0 = Release|Any CPU
42 | {8C9F9383-603C-4045-B0FE-D9D04102FB7F}.Release|x64.ActiveCfg = Release|Any CPU
43 | {8C9F9383-603C-4045-B0FE-D9D04102FB7F}.Release|x64.Build.0 = Release|Any CPU
44 | EndGlobalSection
45 | GlobalSection(SolutionProperties) = preSolution
46 | HideSolutionNode = FALSE
47 | EndGlobalSection
48 | GlobalSection(ExtensibilityGlobals) = postSolution
49 | SolutionGuid = {461F3388-F870-459C-9973-CE7AFE37ABA7}
50 | EndGlobalSection
51 | EndGlobal
52 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/BackOff/ExponentialBackOffSettings.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System;
5 |
6 | namespace FcmSharp.BackOff
7 | {
8 | public class ExponentialBackOffSettings
9 | {
10 | /// The maximum allowed number of retries (20 Retries).
11 | public static readonly int MaxAllowedNumRetries = 20;
12 |
13 | ///
14 | /// The maximum allowed Delta Backoff (1 Second).
15 | ///
16 | public static readonly TimeSpan MaxAllowedDeltaBackOff = TimeSpan.FromSeconds(1);
17 |
18 | ///
19 | /// The Maximum Number of Retries. The default value is 10.
20 | ///
21 | public readonly int MaxNumberOfRetries;
22 |
23 | ///
24 | /// Gets the delta time span used to generate a random milliseconds to add to the next back-off.
25 | /// If the value is then the generated back-off will be exactly 1, 2, 4,
26 | /// 8, 16, etc. seconds. A valid value is between zero and one second. The default value is 250ms, which means
27 | /// that the generated back-off will be [0.75-1.25]sec, [1.75-2.25]sec, [3.75-4.25]sec, and so on.
28 | ///
29 | public readonly TimeSpan DeltaBackOff;
30 |
31 | ///
32 | /// Gets or sets the maximum time span to wait. If the back-off instance returns a greater time span than
33 | /// this value, the Request is cancelled. The default value is 16 seconds per a retry request.
34 | ///
35 | public readonly TimeSpan MaxTimeSpan;
36 |
37 | public ExponentialBackOffSettings(int maximumNumberRetries, TimeSpan deltaBackOff, TimeSpan maxTimeSpan)
38 | {
39 | if (deltaBackOff < TimeSpan.Zero || deltaBackOff > MaxAllowedDeltaBackOff)
40 | {
41 | throw new ArgumentOutOfRangeException("deltaBackOff", $"The Delta Backoff TimeSpan must be between 0 and {MaxAllowedDeltaBackOff.TotalSeconds} Seconds.");
42 | }
43 |
44 | if (maximumNumberRetries < 0 || maximumNumberRetries > MaxAllowedNumRetries)
45 | {
46 | throw new ArgumentOutOfRangeException("maximumNumberRetries", $"The Number of Retries must be between 0 and {MaxAllowedNumRetries}");
47 | }
48 |
49 | MaxNumberOfRetries = maximumNumberRetries;
50 | DeltaBackOff = deltaBackOff;
51 | MaxTimeSpan = maxTimeSpan;
52 | }
53 |
54 | public static ExponentialBackOffSettings Default
55 | {
56 | get
57 | {
58 | return new ExponentialBackOffSettings(maximumNumberRetries: 10, deltaBackOff: TimeSpan.FromMilliseconds(250), maxTimeSpan: TimeSpan.FromSeconds(16));
59 | }
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Batch/BatchMessageBuilder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Net.Http;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using FcmSharp.Http.Builder;
8 | using FcmSharp.Http.Constants;
9 | using FcmSharp.Requests;
10 | using FcmSharp.Serializer;
11 | using Newtonsoft.Json;
12 |
13 | namespace FcmSharp.Batch
14 | {
15 | public class SubRequestBody
16 | {
17 | [JsonProperty("message")]
18 | public Message Message { get; set; }
19 |
20 | [JsonProperty("validate_only")]
21 | public bool? ValidateOnly { get; set; }
22 | }
23 |
24 | public class SubRequest
25 | {
26 | public string Url { get; set; }
27 |
28 | public SubRequestBody Body { get; set; }
29 |
30 | public IDictionary Headers { get; set; }
31 | }
32 |
33 | ///
34 | /// This Builder for Batch Messages is the implementation of the FCM Node SDK:
35 | ///
36 | /// https://github.com/firebase/firebase-admin-node/blob/bf3dbd117ffc3c895a150eee2169eb7c02f4f4cc/src/messaging/batch-request.ts
37 | ///
38 | /// All Credit for the implementation goes to the original authors.
39 | ///
40 | public class BatchMessageBuilder
41 | {
42 | private const string PART_BOUNDARY = "__END_OF_PART__";
43 |
44 | private readonly IJsonSerializer serializer;
45 |
46 | public BatchMessageBuilder(IJsonSerializer serializer)
47 | {
48 | this.serializer = serializer;
49 | }
50 |
51 | public HttpRequestMessageBuilder Build(SubRequest[] requests)
52 | {
53 | byte[] multipartPayload = GetMultipartPayload(requests);
54 |
55 | var byteArrayContent = new ByteArrayContent(multipartPayload);
56 |
57 | byteArrayContent.Headers.Remove("Content-Type");
58 | byteArrayContent.Headers.Add("Content-Type", $"multipart/mixed; boundary={PART_BOUNDARY}");
59 |
60 | return new HttpRequestMessageBuilder("https://fcm.googleapis.com/batch", HttpMethod.Post)
61 | .AddHeader("access_token_auth", "true")
62 | .SetHttpContent(byteArrayContent);
63 | }
64 |
65 | private byte[] GetMultipartPayload(SubRequest[] requests)
66 | {
67 | StringBuilder stringBuilder = new StringBuilder();
68 |
69 | for (int requestIdx = 0; requestIdx < requests.Length; requestIdx++)
70 | {
71 | var request = requests[requestIdx];
72 | var part = CreatePart(request, requestIdx);
73 |
74 | stringBuilder.Append(part);
75 | }
76 |
77 | stringBuilder.Append($"--{PART_BOUNDARY}--\r\n");
78 |
79 | string multiPartPayload = stringBuilder.ToString();
80 |
81 | return Encoding.UTF8.GetBytes(multiPartPayload);
82 | }
83 |
84 | private string CreatePart(SubRequest request, int index)
85 | {
86 | string serializedRequest = SerializeSubRequest(request);
87 |
88 | StringBuilder part = new StringBuilder()
89 | .Append($"--{PART_BOUNDARY}\r\n")
90 | .Append($"Content-Length: {serializedRequest.Length}\r\n")
91 | .Append("Content-Type: application/http\r\n")
92 | .Append($"content-id: {index + 1}\r\n")
93 | .Append("content-transfer-encoding: binary\r\n")
94 | .Append("\r\n")
95 | .Append($"{serializedRequest}\r\n");
96 |
97 | return part.ToString();
98 | }
99 |
100 | public string SerializeSubRequest(SubRequest request)
101 | {
102 | string requestBody = serializer.SerializeObject(request.Body);
103 |
104 | StringBuilder messagePayload = new StringBuilder()
105 | .Append($"POST {request.Url} HTTP/1.1\r\n")
106 | .Append($"Content-Length: {requestBody.Length}\r\n")
107 | .Append("Content-Type: application/json; charset=UTF-8\r\n");
108 |
109 | if (request.Headers != null)
110 | {
111 | foreach (var header in request.Headers)
112 | {
113 | messagePayload.Append($"{header.Key}: {header.Value}\r\n");
114 | }
115 | }
116 | messagePayload.Append("\r\n");
117 | messagePayload.Append(requestBody);
118 |
119 | return messagePayload.ToString();
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Exceptions/FcmHttpException.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System;
5 | using System.Net.Http;
6 |
7 | namespace FcmSharp.Exceptions
8 | {
9 | public class FcmHttpException : Exception
10 | {
11 | public readonly HttpResponseMessage HttpResponseMessage;
12 |
13 | public FcmHttpException(HttpResponseMessage httpResponseMessage)
14 | {
15 | HttpResponseMessage = httpResponseMessage;
16 | }
17 |
18 | public FcmHttpException(HttpResponseMessage httpResponseMessage, string message)
19 | : base(message)
20 | {
21 | HttpResponseMessage = httpResponseMessage;
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Exceptions/FcmMessageException.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System;
5 | using FcmSharp.Responses;
6 |
7 | namespace FcmSharp.Exceptions
8 | {
9 | public class FcmMessageException : Exception
10 | {
11 | public readonly FcmMessageErrorResponse Error;
12 |
13 | public FcmMessageException(FcmMessageErrorResponse error, string message)
14 | : base(message)
15 | {
16 | Error = error;
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Exceptions/FcmTopicManagementException.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System;
5 | using FcmSharp.Responses;
6 |
7 | namespace FcmSharp.Exceptions
8 | {
9 | public class FcmTopicManagementException : Exception
10 | {
11 | public readonly TopicMessageResponseError Error;
12 |
13 | public FcmTopicManagementException(TopicMessageResponseError error, string message)
14 | : base(message)
15 | {
16 | Error = error;
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Extensions/FcmClientExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using FcmSharp.Requests;
6 | using FcmSharp.Responses;
7 |
8 | namespace FcmSharp
9 | {
10 | public static class FcmClientExtensions
11 | {
12 | public static Task SendMulticastMessage(this IFcmClient client, string[] tokens, Message message, bool dryRun = false, CancellationToken cancellationToken = default(CancellationToken))
13 | {
14 | if (tokens == null)
15 | {
16 | throw new ArgumentNullException(nameof(tokens));
17 | }
18 |
19 | if (message == null)
20 | {
21 | throw new ArgumentNullException(nameof(message));
22 | }
23 |
24 | var messages = tokens.Select(token => BuildMessage(token, message)).ToArray();
25 |
26 | return client.SendBatchAsync(messages, dryRun, cancellationToken);
27 | }
28 |
29 | private static Message BuildMessage(string token, Message message)
30 | {
31 | return new Message
32 | {
33 | Token = token,
34 | AndroidConfig = message.AndroidConfig,
35 | ApnsConfig = message.ApnsConfig,
36 | Condition = message.Condition,
37 | Data = message.Data,
38 | Notification = message.Notification,
39 | WebpushConfig = message.WebpushConfig,
40 | Topic = null
41 | };
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/FcmClient.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System;
5 | using System.Linq;
6 | using System.Net.Http;
7 | using System.Text;
8 | using System.Threading;
9 | using System.Threading.Tasks;
10 | using FcmSharp.Batch;
11 | using FcmSharp.Exceptions;
12 | using FcmSharp.Http.Builder;
13 | using FcmSharp.Http.Client;
14 | using FcmSharp.Http.Constants;
15 | using FcmSharp.Requests;
16 | using FcmSharp.Responses;
17 | using FcmSharp.Serializer;
18 | using FcmSharp.Settings;
19 |
20 | namespace FcmSharp
21 | {
22 | public class FcmClient : IFcmClient
23 | {
24 | private readonly IFcmClientSettings settings;
25 |
26 | private readonly IJsonSerializer serializer;
27 |
28 | private readonly IFcmHttpClient httpClient;
29 |
30 | public FcmClient(IFcmClientSettings settings)
31 | : this(settings, JsonSerializer.Default)
32 | {
33 | }
34 |
35 | public FcmClient(IFcmClientSettings settings, IFcmHttpClient fcmHttpClient)
36 | : this(settings, JsonSerializer.Default, fcmHttpClient)
37 | {
38 | }
39 |
40 |
41 | public FcmClient(IFcmClientSettings settings, IJsonSerializer serializer)
42 | : this(settings, serializer, new FcmHttpClient(settings))
43 | {
44 | }
45 |
46 |
47 | public FcmClient(IFcmClientSettings settings, IJsonSerializer serializer, IFcmHttpClient httpClient)
48 | {
49 | if (settings == null)
50 | {
51 | throw new ArgumentNullException(nameof(settings));
52 | }
53 |
54 | if (httpClient == null)
55 | {
56 | throw new ArgumentNullException(nameof(httpClient));
57 | }
58 |
59 | this.serializer = serializer;
60 | this.settings = settings;
61 | this.httpClient = httpClient;
62 | }
63 |
64 | public async Task SendAsync(FcmMessage message, CancellationToken cancellationToken = default(CancellationToken))
65 | {
66 | if (message == null)
67 | {
68 | throw new ArgumentNullException(nameof(message));
69 | }
70 |
71 | string url = $"https://fcm.googleapis.com/v1/projects/{settings.Project}/messages:send";
72 |
73 | // Construct the HTTP Message:
74 | HttpRequestMessageBuilder httpRequestMessageBuilder = new HttpRequestMessageBuilder(url, HttpMethod.Post)
75 | .SetStringContent(serializer.SerializeObject(message), Encoding.UTF8, MediaTypeNames.ApplicationJson);
76 |
77 | try
78 | {
79 | return await httpClient
80 | .SendAsync(httpRequestMessageBuilder, cancellationToken)
81 | .ConfigureAwait(false);
82 | }
83 | catch (FcmHttpException exception)
84 | {
85 | // Get the Original HTTP Response:
86 | var response = exception.HttpResponseMessage;
87 |
88 | // Read the Content:
89 | var content = await response.Content
90 | .ReadAsStringAsync()
91 | .ConfigureAwait(false);
92 |
93 | // Parse the Error:
94 | var error = serializer.DeserializeObject(content);
95 |
96 | // Throw the Exception:
97 | throw new FcmMessageException(error, content);
98 | }
99 | }
100 |
101 | public Task SubscribeToTopic(TopicManagementRequest request, CancellationToken cancellationToken = default(CancellationToken))
102 | {
103 | string iidSubscribePath = "iid/v1:batchAdd";
104 |
105 | return SendAsync(iidSubscribePath, request, cancellationToken);
106 | }
107 |
108 | public Task UnsubscribeFromTopic(TopicManagementRequest request, CancellationToken cancellationToken = default(CancellationToken))
109 | {
110 | string iidUnsubscribePath = "iid/v1:batchRemove";
111 |
112 | return SendAsync(iidUnsubscribePath, request, cancellationToken);
113 | }
114 |
115 | private async Task SendAsync(string path, TopicManagementRequest request, CancellationToken cancellationToken)
116 | {
117 | if (request == null)
118 | {
119 | throw new ArgumentNullException(nameof(request));
120 | }
121 |
122 | // Build the URL:
123 | string url = $"https://iid.googleapis.com/{path}";
124 |
125 | // Construct the HTTP Message:
126 | HttpRequestMessageBuilder httpRequestMessageBuilder = new HttpRequestMessageBuilder(url, HttpMethod.Post)
127 | // Add Option to use the Access Token Auth Header:
128 | .AddHeader("access_token_auth", "true")
129 | // Add the Serialized Request Message:
130 | .SetStringContent(serializer.SerializeObject(request), Encoding.UTF8, MediaTypeNames.ApplicationJson);
131 |
132 | try
133 | {
134 | return await httpClient
135 | .SendAsync(httpRequestMessageBuilder, cancellationToken)
136 | .ConfigureAwait(false);
137 | }
138 | catch (FcmHttpException exception)
139 | {
140 | // Get the Original HTTP Response:
141 | var response = exception.HttpResponseMessage;
142 |
143 | // Read the Content:
144 | var content = await response.Content
145 | .ReadAsStringAsync()
146 | .ConfigureAwait(false);
147 |
148 | // Parse the Error:
149 | var error = serializer.DeserializeObject(content);
150 |
151 | // Throw the Exception:
152 | throw new FcmTopicManagementException(error, content);
153 | }
154 | }
155 |
156 | public async Task SendBatchAsync(Message[] messages, bool dryRun = false, CancellationToken cancellationToken = default(CancellationToken))
157 | {
158 | if (messages.Length > 1000)
159 | {
160 | throw new ArgumentException("Only up 1000 messages are supported by Batch operations", nameof(messages));
161 | }
162 |
163 | // Build Sub Requests, which are contained in a Batch:
164 | var requests = messages.Select(message => new SubRequest
165 | {
166 | Body = new SubRequestBody
167 | {
168 | Message = message,
169 | ValidateOnly = dryRun
170 | },
171 | Url = $"https://fcm.googleapis.com/v1/projects/{settings.Project}/messages:send"
172 | })
173 | .ToArray();
174 |
175 | var httpRequestMessageBuilder = new BatchMessageBuilder(serializer)
176 | .Build(requests);
177 |
178 | try
179 | {
180 | var responses = await httpClient
181 | .SendBatchAsync(httpRequestMessageBuilder, cancellationToken)
182 | .ConfigureAwait(false);
183 |
184 | return new FcmBatchResponse
185 | {
186 | Responses = responses
187 | };
188 | }
189 | catch (FcmHttpException exception)
190 | {
191 | // Get the Original HTTP Response:
192 | var response = exception.HttpResponseMessage;
193 |
194 | // Read the Content:
195 | var content = await response.Content
196 | .ReadAsStringAsync()
197 | .ConfigureAwait(false);
198 |
199 | // Parse the Error:
200 | var error = serializer.DeserializeObject(content);
201 |
202 | // Throw the Exception:
203 | throw new FcmMessageException(error, content);
204 | }
205 | }
206 |
207 | public void Dispose()
208 | {
209 | Dispose(true);
210 | GC.SuppressFinalize(this);
211 | }
212 |
213 | protected virtual void Dispose(bool disposing)
214 | {
215 | // Make sure we Dispose the HttpClient, when we finish:
216 | if (disposing)
217 | {
218 | httpClient?.Dispose();
219 | }
220 | }
221 | }
222 | }
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/FcmSharp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net45;net46;netstandard2.0
5 | Philipp Wagner
6 |
7 |
8 | A library for the Firebase Cloud Messaging (FCM) API.
9 | fcm, firebase
10 | https://github.com/bytefish/FcmSharp
11 | https://github.com/bytefish/FcmSharp
12 | MIT
13 | Copyright © 2019 Philipp Wagner
14 | True
15 | 3.0.1
16 | 3.0.1.0
17 | 3.0.1.0
18 | true
19 | false
20 | FcmSharp.snk
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/FcmSharp.snk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/FcmSharp/FcmSharp.snk
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Http/Builder/HttpRequestMessageBuilder.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Net.Http;
8 | using System.Text;
9 |
10 | namespace FcmSharp.Http.Builder
11 | {
12 | public class HttpRequestMessageBuilder
13 | {
14 | private class Header
15 | {
16 | public readonly string Name;
17 | public readonly string Value;
18 |
19 | public Header(string name, string value)
20 | {
21 | Name = name;
22 | Value = value;
23 | }
24 | }
25 |
26 | private string url;
27 | private HttpMethod httpMethod;
28 | private IDictionary parameters;
29 | private IList headers;
30 | private IList segments;
31 | private HttpContent content;
32 |
33 | public HttpRequestMessageBuilder(string url, HttpMethod httpMethod)
34 | {
35 | if (url == null)
36 | {
37 | throw new ArgumentNullException("url");
38 | }
39 |
40 | this.url = url;
41 | this.httpMethod = httpMethod;
42 | this.headers = new List();
43 | this.segments = new List();
44 | this.content = null;
45 | this.parameters = new Dictionary();
46 | }
47 |
48 | public HttpRequestMessageBuilder HttpMethod(HttpMethod httpMethod)
49 | {
50 | this.httpMethod = httpMethod;
51 |
52 | return this;
53 | }
54 |
55 | public HttpRequestMessageBuilder AddHeader(string name, string value)
56 | {
57 | this.headers.Add(new Header(name, value));
58 |
59 | return this;
60 | }
61 |
62 | public HttpRequestMessageBuilder SetHeader(string name, string value)
63 | {
64 | var header = this.headers.FirstOrDefault(x => x.Name == name);
65 |
66 | if (header != null)
67 | {
68 | this.headers.Remove(header);
69 | }
70 |
71 | AddHeader(name, value);
72 |
73 | return this;
74 | }
75 |
76 | public HttpRequestMessageBuilder SetStringContent(string content, Encoding encoding, string mediaType)
77 | {
78 | this.content = new StringContent(content, encoding, mediaType);
79 |
80 | return this;
81 | }
82 |
83 | public HttpRequestMessageBuilder SetHttpContent(HttpContent httpContent)
84 | {
85 | this.content = httpContent;
86 |
87 | return this;
88 | }
89 |
90 | public HttpRequestMessageBuilder AddUrlSegment(string name, string value)
91 | {
92 | this.segments.Add(new UrlSegment(name, value));
93 |
94 | return this;
95 | }
96 |
97 | public HttpRequestMessageBuilder AddQueryString(string key, string value)
98 | {
99 | this.parameters.Add(key, value);
100 |
101 | return this;
102 | }
103 |
104 | public HttpRequestMessage Build()
105 | {
106 | string resourceUrl = HttpRequestUtils.ReplaceSegments(url, segments);
107 | string queryString = HttpRequestUtils.BuildQueryString(resourceUrl, parameters);
108 | string resourceUrlWithQueryString = string.Format("{0}{1}", resourceUrl, queryString);
109 |
110 | HttpRequestMessage httpRequestMessage = new HttpRequestMessage(httpMethod, resourceUrlWithQueryString);
111 |
112 | foreach (var header in headers)
113 | {
114 | httpRequestMessage.Headers.TryAddWithoutValidation(header.Name, header.Value);
115 | }
116 |
117 | if (content != null)
118 | {
119 | httpRequestMessage.Content = content;
120 | }
121 |
122 | return httpRequestMessage;
123 | }
124 | }
125 | }
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Http/Builder/HttpRequestUtils.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System.Collections.Generic;
5 | using System.Text;
6 |
7 | namespace FcmSharp.Http.Builder
8 | {
9 | public static class HttpRequestUtils
10 | {
11 | public static string ReplaceSegments(string resource, IList segments)
12 | {
13 | string url = new string(resource.ToCharArray());
14 |
15 | foreach (var segment in segments)
16 | {
17 | url = url.Replace(segment.name, segment.value);
18 | }
19 |
20 | return url;
21 | }
22 |
23 | public static string BuildQueryString(string resource, IDictionary parameters)
24 | {
25 | var builder = new StringBuilder();
26 |
27 | bool first = true;
28 | foreach (var parameter in parameters)
29 | {
30 | builder.Append(first ? "?" : "&");
31 |
32 | first = false;
33 |
34 | builder.Append(parameter.Key);
35 | builder.Append("=");
36 | builder.Append(parameter.Value);
37 | }
38 |
39 | return builder.ToString();
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Http/Builder/UrlSegment.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | namespace FcmSharp.Http.Builder
5 | {
6 | public class UrlSegment
7 | {
8 | public readonly string name;
9 | public readonly string value;
10 |
11 | public UrlSegment(string name, string value)
12 | {
13 | this.name = name;
14 | this.value = value;
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Http/Client/FcmHttpClient.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Net.Http;
9 | using System.Threading;
10 | using System.Threading.Tasks;
11 | using System.Net;
12 | using System.Reflection;
13 | using System.Text;
14 | using FcmSharp.Batch;
15 | using FcmSharp.Exceptions;
16 | using FcmSharp.Http.Builder;
17 | using FcmSharp.Http.Constants;
18 | using FcmSharp.Responses;
19 | using FcmSharp.Serializer;
20 | using FcmSharp.Settings;
21 | using Google.Apis.Auth.OAuth2;
22 | using Google.Apis.Http;
23 | using Google.Apis.Util;
24 |
25 | namespace FcmSharp.Http.Client
26 | {
27 | public class FcmHttpClient : IFcmHttpClient
28 | {
29 | private readonly ConfigurableHttpClient client;
30 | private readonly IFcmClientSettings settings;
31 | private readonly IJsonSerializer serializer;
32 | private readonly ServiceAccountCredential credential;
33 |
34 | public FcmHttpClient(IFcmClientSettings settings)
35 | : this(settings, JsonSerializer.Default, new Google.Apis.Http.HttpClientFactory(), CreateDefaultHttpClientArgs(settings))
36 | {
37 | }
38 |
39 | public FcmHttpClient(IFcmClientSettings settings, Google.Apis.Http.IHttpClientFactory httpClientFactory)
40 | : this(settings, JsonSerializer.Default, httpClientFactory, CreateDefaultHttpClientArgs(settings))
41 | {
42 | }
43 |
44 | public FcmHttpClient(IFcmClientSettings settings, Google.Apis.Http.IHttpClientFactory httpClientFactory, CreateHttpClientArgs httpClientArgs)
45 | : this(settings, JsonSerializer.Default, httpClientFactory, httpClientArgs)
46 | {
47 | }
48 |
49 | public FcmHttpClient(IFcmClientSettings settings, IJsonSerializer serializer, Google.Apis.Http.IHttpClientFactory httpClientFactory, CreateHttpClientArgs httpClientArgs)
50 | {
51 | if (settings == null)
52 | {
53 | throw new ArgumentNullException(nameof(settings));
54 | }
55 |
56 | if (serializer == null)
57 | {
58 | throw new ArgumentNullException(nameof(serializer));
59 | }
60 |
61 | this.settings = settings;
62 | this.client = httpClientFactory.CreateHttpClient(httpClientArgs);
63 | this.serializer = serializer;
64 | this.credential = CreateServiceAccountCredential(httpClientFactory, settings);
65 |
66 | InitializeExponentialBackOff(client, settings);
67 | }
68 |
69 | public Task SendAsync(HttpRequestMessageBuilder builder, CancellationToken cancellationToken)
70 | {
71 | return SendAsync(builder, default(HttpCompletionOption), cancellationToken);
72 | }
73 |
74 | public async Task SendAsync(HttpRequestMessageBuilder builder, HttpCompletionOption completionOption, CancellationToken cancellationToken)
75 | {
76 | // Add Authorization Header:
77 | var accessToken = await CreateAccessTokenAsync(cancellationToken).ConfigureAwait(false);
78 |
79 | builder.AddHeader("Authorization", $"Bearer {accessToken}");
80 |
81 | // Build the Request Message:
82 | var httpRequestMessage = builder.Build();
83 |
84 | // Invoke actions before the Request:
85 | OnBeforeRequest(httpRequestMessage);
86 |
87 | // Invoke the Request:
88 | HttpResponseMessage httpResponseMessage = await client
89 | .SendAsync(httpRequestMessage, completionOption, cancellationToken)
90 | .ConfigureAwait(false);
91 |
92 | // Invoke actions after the Request:
93 | OnAfterResponse(httpRequestMessage, httpResponseMessage);
94 |
95 | // Apply the Response Interceptors:
96 | EvaluateResponse(httpResponseMessage);
97 |
98 | // Now read the Response Content as String:
99 | string httpResponseContentAsString = await httpResponseMessage.Content
100 | .ReadAsStringAsync()
101 | .ConfigureAwait(false);
102 |
103 | // And finally return the Object:
104 | return serializer.DeserializeObject(httpResponseContentAsString);
105 | }
106 |
107 | public async Task SendBatchAsync(HttpRequestMessageBuilder builder, CancellationToken cancellationToken)
108 | {
109 | // Add Authorization Header:
110 | var accessToken = await CreateAccessTokenAsync(cancellationToken).ConfigureAwait(false);
111 |
112 | builder.AddHeader("Authorization", $"Bearer {accessToken}");
113 |
114 | // Build the Request Message:
115 | var httpRequestMessage = builder.Build();
116 |
117 | // Invoke actions before the Request:
118 | OnBeforeRequest(httpRequestMessage);
119 |
120 | // Invoke the Request:
121 | HttpResponseMessage httpResponseMessage = await client
122 | .SendAsync(httpRequestMessage, cancellationToken)
123 | .ConfigureAwait(false);
124 |
125 | // Invoke actions after the Request:
126 | OnAfterResponse(httpRequestMessage, httpResponseMessage);
127 |
128 | // Apply the Response Interceptors:
129 | EvaluateResponse(httpResponseMessage);
130 |
131 | var multipart = await httpResponseMessage.Content.ReadAsMultipartAsync(cancellationToken);
132 |
133 | List result = new List();
134 |
135 | foreach (var content in multipart.Contents)
136 | {
137 | string part = await content.ReadAsStringAsync();
138 |
139 | // This is quite a hack approach, which might or might not work for all scenarios.
140 | // I am splitting the multipart response into lines, which in turn is skipped until
141 | // we hit a line with a single "{", which indicates we have found some JSON:
142 | IEnumerable jsonLines = part.Split('\n').SkipWhile(x => !string.Equals(x.Trim(), "{"));
143 |
144 | // Then we turn the lines into a String again:
145 | var jsonString = string.Join("\n", jsonLines);
146 |
147 | // So Newtonsoft.JSON can deserialize it again:
148 | var response = serializer.DeserializeObject(jsonString);
149 |
150 | // And add it to the result:
151 | result.Add(response);
152 | }
153 |
154 | // And finally return the Object:
155 | return result.ToArray();
156 | }
157 |
158 | public Task SendAsync(HttpRequestMessageBuilder builder, CancellationToken cancellationToken)
159 | {
160 | return SendAsync(builder, default(HttpCompletionOption), cancellationToken);
161 | }
162 |
163 | public async Task SendAsync(HttpRequestMessageBuilder builder, HttpCompletionOption completionOption, CancellationToken cancellationToken)
164 | {
165 | // Add Authorization Header:
166 | var accessToken = await CreateAccessTokenAsync(cancellationToken)
167 | .ConfigureAwait(false);
168 |
169 | builder.AddHeader("Authorization", $"Bearer {accessToken}");
170 |
171 | // Build the Request Message:
172 | var httpRequestMessage = builder.Build();
173 |
174 | // Invoke actions before the Request:
175 | OnBeforeRequest(httpRequestMessage);
176 |
177 | // Invoke the Request:
178 | HttpResponseMessage httpResponseMessage = await client.SendAsync(httpRequestMessage, completionOption, cancellationToken).ConfigureAwait(false);
179 |
180 | // Invoke actions after the Request:
181 | OnAfterResponse(httpRequestMessage, httpResponseMessage);
182 |
183 | // Apply the Response Interceptors:
184 | EvaluateResponse(httpResponseMessage);
185 | }
186 |
187 | protected virtual void OnBeforeRequest(HttpRequestMessage httpRequestMessage)
188 | {
189 | }
190 |
191 | protected virtual void OnAfterResponse(HttpRequestMessage httpRequestMessage, HttpResponseMessage httpResponseMessage)
192 | {
193 | }
194 |
195 | public void EvaluateResponse(HttpResponseMessage response)
196 | {
197 | if (response == null)
198 | {
199 | return;
200 | }
201 |
202 | HttpStatusCode httpStatusCode = response.StatusCode;
203 |
204 | if (httpStatusCode == HttpStatusCode.OK)
205 | {
206 | return;
207 | }
208 |
209 | if ((int) httpStatusCode >= 400)
210 | {
211 | throw new FcmHttpException(response);
212 | }
213 | }
214 |
215 | private ServiceAccountCredential CreateServiceAccountCredential(IHttpClientFactory httpClientFactory, IFcmClientSettings settings)
216 | {
217 | var serviceAccountCredential = GoogleCredential.FromJson(settings.Credentials)
218 | // We need the Messaging Scope:
219 | .CreateScoped("https://www.googleapis.com/auth/firebase.messaging")
220 | // Cast to the ServiceAccountCredential:
221 | .UnderlyingCredential as ServiceAccountCredential;
222 |
223 | if (serviceAccountCredential == null)
224 | {
225 | throw new Exception($"Error creating ServiceAccountCredential from JSON File {settings.Credentials}");
226 | }
227 |
228 | var initializer = new ServiceAccountCredential.Initializer(serviceAccountCredential.Id, serviceAccountCredential.TokenServerUrl)
229 | {
230 | User = serviceAccountCredential.User,
231 | AccessMethod = serviceAccountCredential.AccessMethod,
232 | Clock = serviceAccountCredential.Clock,
233 | Key = serviceAccountCredential.Key,
234 | Scopes = serviceAccountCredential.Scopes,
235 | HttpClientFactory = httpClientFactory
236 | };
237 |
238 | return new ServiceAccountCredential(initializer);
239 | }
240 |
241 | private async Task CreateAccessTokenAsync(CancellationToken cancellationToken)
242 | {
243 | // Execute the Request:
244 | var accessToken = await credential
245 | .GetAccessTokenForRequestAsync(cancellationToken: cancellationToken)
246 | .ConfigureAwait(false);
247 |
248 | if (accessToken == null)
249 | {
250 | throw new Exception("Failed to obtain Access Token for Request");
251 | }
252 |
253 | return accessToken;
254 | }
255 |
256 | private static CreateHttpClientArgs CreateDefaultHttpClientArgs(IFcmClientSettings settings)
257 | {
258 | if (settings == null)
259 | {
260 | throw new ArgumentNullException("settings", "Settings are needed to create the Default HttpClientArgs");
261 | }
262 |
263 | var args = new CreateHttpClientArgs();
264 |
265 | // Create the Default BackOff Algorithm:
266 | var backoff = new ExponentialBackOff(settings.ExponentialBackOffSettings.DeltaBackOff, settings.ExponentialBackOffSettings.MaxNumberOfRetries);
267 |
268 | // Create the Initializer. Make sure to set the Maximum Timespan between two Requests. It is 16 Seconds per Default:
269 | var backoffInitializer = new BackOffHandler.Initializer(backoff)
270 | {
271 | MaxTimeSpan = settings.ExponentialBackOffSettings.MaxTimeSpan
272 | };
273 |
274 | args.Initializers.Add(new ExponentialBackOffInitializer(ExponentialBackOffPolicy.UnsuccessfulResponse503, () => new BackOffHandler(backoffInitializer)));
275 |
276 | return args;
277 | }
278 |
279 | private void InitializeExponentialBackOff(ConfigurableHttpClient client, IFcmClientSettings settings)
280 | {
281 | // The Maximum Number of Retries is limited to 3 per default for a ConfigurableHttpClient. This is
282 | // somewhat weird, because the ExponentialBackOff Algorithm is initialized with 10 Retries per default.
283 | //
284 | // Somehow the NumTries seems to be the limiting factor here, so it basically overrides anything you
285 | // are going to write in the Exponential Backoff Handler.
286 | client.MessageHandler.NumTries = settings.ExponentialBackOffSettings.MaxNumberOfRetries;
287 | }
288 |
289 |
290 | public void Dispose()
291 | {
292 | client?.Dispose();
293 | }
294 | }
295 | }
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Http/Client/IFcmHttpClient.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System;
5 | using System.Net.Http;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 | using FcmSharp.Http.Builder;
9 |
10 | namespace FcmSharp.Http.Client
11 | {
12 | public interface IFcmHttpClient : IDisposable
13 | {
14 | Task SendAsync(HttpRequestMessageBuilder builder, CancellationToken cancellationToken);
15 |
16 | Task SendAsync(HttpRequestMessageBuilder builder, HttpCompletionOption completionOption, CancellationToken cancellationToken);
17 |
18 | Task SendAsync(HttpRequestMessageBuilder builder, CancellationToken cancellationToken);
19 |
20 | Task SendAsync(HttpRequestMessageBuilder builder, HttpCompletionOption completionOption, CancellationToken cancellationToken);
21 |
22 | Task SendBatchAsync(HttpRequestMessageBuilder builder, CancellationToken cancellationToken);
23 | }
24 | }
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Http/Constants/HttpHeaderNames.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | namespace FcmSharp.Http.Constants
5 | {
6 | public class HttpHeaderNames
7 | {
8 | public const string ContentType = "Content-Type";
9 |
10 | public const string Authorization = "Authorization";
11 |
12 | public const string RetryAfter = "Retry-After";
13 | }
14 | }
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Http/Proxy/ProxyHttpClientFactory.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System;
5 | using System.Net;
6 | using System.Net.Http;
7 | using Google.Apis.Http;
8 |
9 | namespace FcmSharp.Http.Proxy
10 | {
11 | public class ProxyHttpClientFactory : IHttpClientFactory
12 | {
13 | private readonly IWebProxy webProxy;
14 |
15 | public ProxyHttpClientFactory(IWebProxy webProxy)
16 | {
17 | this.webProxy = webProxy;
18 | }
19 |
20 | public ProxyHttpClientFactory(Uri proxy, ICredentials credentials)
21 | {
22 | this.webProxy = new WebProxy(proxy, credentials);
23 | }
24 |
25 | public ConfigurableHttpClient CreateHttpClient(CreateHttpClientArgs args)
26 | {
27 | HttpClientHandler httpClientHandler = new HttpClientHandler()
28 | {
29 | UseProxy = true,
30 | Proxy = webProxy
31 | };
32 |
33 | ConfigurableMessageHandler httpMessageHandler = new ConfigurableMessageHandler(httpClientHandler);
34 |
35 | return new ConfigurableHttpClient(httpMessageHandler);
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Http/Proxy/WebProxy.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System;
5 | using System.Net;
6 |
7 | namespace FcmSharp.Http.Proxy
8 | {
9 | public class WebProxy : IWebProxy
10 | {
11 | public WebProxy(Uri proxy, ICredentials credentials)
12 | {
13 | Proxy = proxy;
14 | Credentials = credentials;
15 | }
16 |
17 | public Uri GetProxy(Uri destination)
18 | {
19 | return Proxy;
20 | }
21 |
22 | public bool IsBypassed(Uri host)
23 | {
24 | return false;
25 | }
26 |
27 | public Uri Proxy { get; set; }
28 |
29 | public ICredentials Credentials { get; set; }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/IFcmClient.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 | using FcmSharp.Requests;
8 | using FcmSharp.Responses;
9 |
10 | namespace FcmSharp
11 | {
12 | public interface IFcmClient : IDisposable
13 | {
14 | Task SendAsync(FcmMessage message, CancellationToken cancellationToken = default(CancellationToken));
15 |
16 | Task SendBatchAsync(Message[] messages, bool dryRun = false, CancellationToken cancellationToken = default(CancellationToken));
17 |
18 | Task SubscribeToTopic(TopicManagementRequest request, CancellationToken cancellationToken = default(CancellationToken));
19 |
20 | Task UnsubscribeFromTopic(TopicManagementRequest request, CancellationToken cancellationToken = default(CancellationToken));
21 | }
22 | }
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Requests/AndroidConfig.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System;
5 | using System.Collections.Generic;
6 | using FcmSharp.Requests.Converters;
7 | using Newtonsoft.Json;
8 |
9 | namespace FcmSharp.Requests
10 | {
11 | public class AndroidConfig
12 | {
13 | [JsonProperty("collapse_key")]
14 | public string CollapseKey { get; set; }
15 |
16 | [JsonProperty("priority")]
17 | [JsonConverter(typeof(AndroidMessagePriorityEnumConverter))]
18 | public AndroidMessagePriorityEnum Priority { get; set; }
19 |
20 | [JsonProperty("ttl")]
21 | [JsonConverter(typeof(DurationFormatConverter))]
22 | public TimeSpan? TimeToLive { get; set; }
23 |
24 | [JsonProperty("restricted_package_name")]
25 | public string RestrictedPackageName { get; set; }
26 |
27 | [JsonProperty("data")]
28 | public IDictionary Data { get; set; }
29 |
30 | [JsonProperty("notification")]
31 | public AndroidNotification Notification { get; set; }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Requests/AndroidMessagePriorityEnum.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | namespace FcmSharp.Requests
5 | {
6 | public enum AndroidMessagePriorityEnum
7 | {
8 | NORMAL,
9 | HIGH
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Requests/AndroidNotification.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using Newtonsoft.Json;
5 |
6 | namespace FcmSharp.Requests
7 | {
8 | public class AndroidNotification
9 | {
10 | [JsonProperty("title")]
11 | public string Title { get; set; }
12 |
13 | [JsonProperty("body")]
14 | public string Body { get; set; }
15 |
16 | [JsonProperty("icon")]
17 | public string Icon { get; set; }
18 |
19 | [JsonProperty("color")]
20 | public string Color { get; set; }
21 |
22 | [JsonProperty("sound")]
23 | public string Sound { get; set; }
24 |
25 | [JsonProperty("tag")]
26 | public string Tag { get; set; }
27 |
28 | [JsonProperty("click_action")]
29 | public string ClickAction { get; set; }
30 |
31 | [JsonProperty("body_loc_key")]
32 | public string BodyLocKey { get; set; }
33 |
34 | [JsonProperty("body_loc_args")]
35 | public string[] BodyLocArgs { get; set; }
36 |
37 | [JsonProperty("title_loc_key")]
38 | public string TitleLocKey { get; set; }
39 |
40 | [JsonProperty("title_loc_args")]
41 | public string[] TitleLocArgs { get; set; }
42 |
43 | [JsonProperty("channel_id")]
44 | public string ChannelId { get; set; }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Requests/ApnsConfig.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System.Collections.Generic;
5 | using Newtonsoft.Json;
6 |
7 | namespace FcmSharp.Requests
8 | {
9 | public class ApnsConfig
10 | {
11 | [JsonProperty("headers")]
12 | public IDictionary Headers { get; set; }
13 |
14 | [JsonProperty("payload")]
15 | public ApnsConfigPayload Payload { get; set; }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Requests/ApnsConfigPayload.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System.Collections.Generic;
5 | using Newtonsoft.Json;
6 |
7 | namespace FcmSharp.Requests
8 | {
9 | public class ApnsConfigPayload
10 | {
11 | [JsonProperty("aps")]
12 | public Aps Aps { get; set; }
13 |
14 | [JsonExtensionData]
15 | public IDictionary CustomData { get; set; }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Requests/Aps.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System.Collections.Generic;
5 | using FcmSharp.Requests.Converters;
6 | using Newtonsoft.Json;
7 |
8 | namespace FcmSharp.Requests
9 | {
10 | public class Aps
11 | {
12 | [JsonProperty("alert")]
13 | public ApsAlert Alert { get; set; }
14 |
15 | [JsonProperty("badge")]
16 | public int? Badge { get; set; }
17 |
18 | [JsonProperty("sound")]
19 | public string Sound { get; set; }
20 |
21 | [JsonProperty("content-available")]
22 | [JsonConverter(typeof(BoolToIntConverter))]
23 | public bool ContentAvailable { get; set; }
24 |
25 | [JsonProperty("mutable-content")]
26 | [JsonConverter(typeof(BoolToIntConverter))]
27 | public bool MutableContent { get; set; }
28 |
29 | [JsonProperty("category")]
30 | public string Category { get; set; }
31 |
32 | [JsonProperty("thread-id")]
33 | public string ThreadId { get; set; }
34 |
35 | [JsonExtensionData]
36 | public IDictionary CustomData { get; set; }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Requests/ApsAlert.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using Newtonsoft.Json;
5 |
6 | namespace FcmSharp.Requests
7 | {
8 | public class ApsAlert
9 | {
10 | [JsonProperty("title")]
11 | public string Title { get; set; }
12 |
13 | [JsonProperty("subtitle")]
14 | public string Subtitle { get; set; }
15 |
16 | [JsonProperty("body")]
17 | public string Body { get; set; }
18 |
19 | [JsonProperty("loc-key")]
20 | public string LocKey { get; set; }
21 |
22 | [JsonProperty("loc-args")]
23 | public string[] LocArgs { get; set; }
24 |
25 | [JsonProperty("title-loc-key")]
26 | public string TitleLocKey { get; set; }
27 |
28 | [JsonProperty("title-loc-args")]
29 | public string[] TitleLocArgs { get; set; }
30 |
31 | [JsonProperty("action-loc-key")]
32 | public string ActionLocKey { get; set; }
33 |
34 | [JsonProperty("launch-image")]
35 | public string LaunchImage { get; set; }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Requests/Converters/AndroidMessagePriorityEnumConverter.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System;
5 | using Newtonsoft.Json;
6 |
7 | namespace FcmSharp.Requests.Converters
8 | {
9 | public class AndroidMessagePriorityEnumConverter : JsonConverter
10 | {
11 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
12 | {
13 | AndroidMessagePriorityEnum operation = (AndroidMessagePriorityEnum)value;
14 |
15 | switch (operation)
16 | {
17 | case AndroidMessagePriorityEnum.HIGH:
18 | writer.WriteValue("HIGH");
19 | break;
20 | case AndroidMessagePriorityEnum.NORMAL:
21 | writer.WriteValue("NORMAL");
22 | break;
23 | }
24 | }
25 |
26 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
27 | {
28 | var enumString = (string)reader.Value;
29 |
30 | if (string.IsNullOrWhiteSpace(enumString))
31 | {
32 | return null;
33 | }
34 |
35 | return Enum.Parse(typeof(AndroidMessagePriorityEnum), enumString, true);
36 | }
37 |
38 | public override bool CanConvert(Type objectType)
39 | {
40 | return objectType == typeof(AndroidMessagePriorityEnum);
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Requests/Converters/BoolToIntConverter.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System;
5 | using Newtonsoft.Json;
6 |
7 | namespace FcmSharp.Requests.Converters
8 | {
9 | public class BoolToIntConverter : JsonConverter
10 | {
11 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
12 | {
13 | bool booleanValue = (bool)value;
14 |
15 | writer.WriteValue(Convert.ToInt32(booleanValue));
16 | }
17 |
18 |
19 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
20 | {
21 | return Convert.ToBoolean(reader.Value);
22 | }
23 |
24 | public override bool CanConvert(Type objectType)
25 | {
26 | return typeof(bool) == objectType;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Requests/Converters/DurationFormatConverter.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System;
5 | using Newtonsoft.Json;
6 |
7 | namespace FcmSharp.Requests.Converters
8 | {
9 | public class DurationFormatConverter : JsonConverter
10 | {
11 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
12 | {
13 | TimeSpan? timeSpan = (TimeSpan?)value;
14 |
15 | if (!timeSpan.HasValue)
16 | {
17 | return;
18 | }
19 |
20 | string timeToLiveInSeconds = string.Format("{0}s", (int) timeSpan.Value.TotalSeconds);
21 |
22 | writer.WriteValue(timeToLiveInSeconds);
23 |
24 | }
25 |
26 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
27 | {
28 | throw new NotImplementedException();
29 | }
30 |
31 | public override bool CanConvert(Type objectType)
32 | {
33 | return typeof(TimeSpan?) == objectType;
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Requests/FcmMessage.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using Newtonsoft.Json;
5 |
6 | namespace FcmSharp.Requests
7 | {
8 | public class FcmMessage
9 | {
10 | [JsonProperty("validate_only")]
11 | public bool ValidateOnly { get; set; }
12 |
13 | [JsonProperty("message")]
14 | public Message Message { get; set; }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Requests/Message.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System.Collections.Generic;
5 | using Newtonsoft.Json;
6 |
7 | namespace FcmSharp.Requests
8 | {
9 | public class Message
10 | {
11 | [JsonProperty("data")]
12 | public IDictionary Data { get; set; }
13 |
14 | [JsonProperty("notification")]
15 | public Notification Notification { get; set; }
16 |
17 | [JsonProperty("android")]
18 | public AndroidConfig AndroidConfig { get; set; }
19 |
20 | [JsonProperty("webpush")]
21 | public WebpushConfig WebpushConfig { get; set; }
22 |
23 | [JsonProperty("apns")]
24 | public ApnsConfig ApnsConfig { get; set; }
25 |
26 | [JsonProperty("token")]
27 | public string Token { get; set; }
28 |
29 | [JsonProperty("topic")]
30 | public string Topic { get; set; }
31 |
32 | [JsonProperty("condition")]
33 | public string Condition { get; set; }
34 | }
35 | }
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Requests/Notification.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using Newtonsoft.Json;
5 |
6 | namespace FcmSharp.Requests
7 | {
8 | public class Notification
9 | {
10 | [JsonProperty("title")]
11 | public string Title { get; set; }
12 |
13 | [JsonProperty("body")]
14 | public string Body { get; set; }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Requests/TopicManagementRequest.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using Newtonsoft.Json;
5 |
6 | namespace FcmSharp.Requests
7 | {
8 | public class TopicManagementRequest
9 | {
10 | [JsonProperty("to")]
11 | public string Topic { get; set; }
12 |
13 | [JsonProperty("registration_tokens")]
14 | public string[] RegistrationTokens { get; set; }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Requests/WebpushConfig.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System.Collections.Generic;
5 | using Newtonsoft.Json;
6 |
7 | namespace FcmSharp.Requests
8 | {
9 | public class WebpushConfig
10 | {
11 | [JsonProperty("headers")]
12 | public IDictionary Headers { get; set; }
13 |
14 | [JsonProperty("data")]
15 | public IDictionary Data { get; set; }
16 |
17 | [JsonProperty("notification")]
18 | public WebpushNotification Notification { get; set; }
19 |
20 | [JsonProperty("fcm_options")]
21 | public WebpushFcmOptions FcmOptions { get; set; }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Requests/WebpushFcmOptions.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | namespace FcmSharp.Requests
4 | {
5 | public class WebpushFcmOptions
6 | {
7 | [JsonProperty("link")]
8 | public string Link { get; set; }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Requests/WebpushNotification.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using Newtonsoft.Json;
5 |
6 | namespace FcmSharp.Requests
7 | {
8 | public class WebpushNotification
9 | {
10 | [JsonProperty("title")]
11 | public string Title { get; set; }
12 |
13 | [JsonProperty("body")]
14 | public string Body { get; set; }
15 |
16 | [JsonProperty("icon")]
17 | public string Icon { get; set; }
18 | }
19 | }
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Responses/Converters/TopicErrorEnumConverter.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System;
5 | using Newtonsoft.Json;
6 |
7 | namespace FcmSharp.Responses.Converters
8 | {
9 | public class TopicErrorEnumConverter : JsonConverter
10 | {
11 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
12 | {
13 | throw new NotImplementedException();
14 | }
15 |
16 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
17 | {
18 | var errorString = (string) reader.Value;
19 |
20 | switch (errorString)
21 | {
22 | case "INVALID_ARGUMENT":
23 | return TopicManagementResponse.Error.InvalidArgument;
24 | case "NOT_FOUND":
25 | return TopicManagementResponse.Error.NotFound;
26 | case "INTERNAL":
27 | return TopicManagementResponse.Error.Internal;
28 | case "TOO_MANY_TOPICS":
29 | return TopicManagementResponse.Error.TooManyTopics;
30 | case "PERMISSION_DENIED":
31 | return TopicManagementResponse.Error.PermissionDenied;
32 | default:
33 | return TopicManagementResponse.Error.Unknown;
34 | }
35 | }
36 |
37 | public override bool CanConvert(Type objectType)
38 | {
39 | return objectType == typeof(string);
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Responses/FcmBatchResponse.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System.Collections.Generic;
5 | using Newtonsoft.Json;
6 |
7 | namespace FcmSharp.Responses
8 | {
9 | public class FcmBatchResponse
10 | {
11 | public FcmSendResponse[] Responses { get; set; }
12 | }
13 |
14 | public class FcmSendResponse
15 | {
16 | [JsonProperty("name")]
17 | public string Name { get; set; }
18 |
19 | [JsonProperty("error")]
20 | public IDictionary Error { get; set; }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Responses/FcmMessageErrorResponse.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System.Collections.Generic;
5 | using Newtonsoft.Json;
6 |
7 | namespace FcmSharp.Responses
8 | {
9 | public class FcmMessageErrorResponse
10 | {
11 | [JsonProperty("error")]
12 | public IDictionary Error { get; set; }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Responses/FcmMessageResponse.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using Newtonsoft.Json;
5 |
6 | namespace FcmSharp.Responses
7 | {
8 | public class FcmMessageResponse
9 | {
10 | [JsonProperty("name")]
11 | public string Name { get; set; }
12 | }
13 | }
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Responses/InstanceIdServiceErrorResponse.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using Newtonsoft.Json;
5 |
6 | namespace FcmSharp.Responses
7 | {
8 | public class InstanceIdServiceErrorResponse
9 | {
10 | [JsonProperty("error")]
11 | public string Error { get; set; }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Responses/InstanceIdServiceResponse.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System.Collections.Generic;
5 | using Newtonsoft.Json;
6 |
7 | namespace FcmSharp.Responses
8 | {
9 | public class InstanceIdServiceResponse
10 | {
11 | [JsonProperty("results")]
12 | public IDictionary[] Results { get; set; }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Responses/TopicMessageResponse.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using FcmSharp.Responses.Converters;
5 | using Newtonsoft.Json;
6 |
7 | namespace FcmSharp.Responses
8 | {
9 | public class TopicManagementResponse
10 | {
11 | public enum Error
12 | {
13 | Unknown,
14 | InvalidArgument,
15 | NotFound,
16 | Internal,
17 | TooManyTopics,
18 | PermissionDenied
19 | }
20 |
21 | public class ResultItem
22 | {
23 | [JsonProperty("error")]
24 | [JsonConverter(typeof(TopicErrorEnumConverter))]
25 | public Error? ErrorCode { get; set; }
26 | }
27 |
28 | [JsonProperty("results")]
29 | public ResultItem[] Results { get; set; }
30 | }
31 | }
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Responses/TopicMessageResponseError.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using Newtonsoft.Json;
5 |
6 | namespace FcmSharp.Responses
7 | {
8 | public class TopicMessageResponseError
9 | {
10 | [JsonProperty("error")]
11 | public string Error { get; set; }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Serializer/IJsonSerializer.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | namespace FcmSharp.Serializer
5 | {
6 | public interface IJsonSerializer
7 | {
8 | string SerializeObject(object value);
9 |
10 | TTargetType DeserializeObject(string value);
11 | }
12 | }
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Serializer/JsonSerializer.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using Newtonsoft.Json;
5 |
6 | namespace FcmSharp.Serializer
7 | {
8 | public class JsonSerializer : IJsonSerializer
9 | {
10 | private readonly JsonSerializerSettings settings;
11 |
12 | private JsonSerializer(JsonSerializerSettings settings)
13 | {
14 | this.settings = settings;
15 | }
16 |
17 | public string SerializeObject(object value)
18 | {
19 | return JsonConvert.SerializeObject(value, settings);
20 | }
21 |
22 | public TTargetType DeserializeObject(string value)
23 | {
24 | return JsonConvert.DeserializeObject(value, settings);
25 | }
26 |
27 | public static JsonSerializer Default
28 | {
29 | get
30 | {
31 | return new JsonSerializer(new JsonSerializerSettings()
32 | {
33 | NullValueHandling = NullValueHandling.Ignore
34 | });
35 | }
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Settings/FcmClientSettings.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using FcmSharp.BackOff;
5 |
6 | namespace FcmSharp.Settings
7 | {
8 | public class FcmClientSettings : IFcmClientSettings
9 | {
10 | public string Project { get; }
11 |
12 | public string Credentials { get; }
13 |
14 | public ExponentialBackOffSettings ExponentialBackOffSettings { get; }
15 |
16 | public FcmClientSettings(string project, string credentials)
17 | : this(project, credentials, ExponentialBackOffSettings.Default)
18 | {
19 | }
20 |
21 | public FcmClientSettings(string project, string credentials, ExponentialBackOffSettings exportExponentialBackOffSettings)
22 | {
23 | Project = project;
24 | Credentials = credentials;
25 | ExponentialBackOffSettings = exportExponentialBackOffSettings;
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Settings/FileBasedFcmClientSettings.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System;
5 | using System.Collections;
6 | using System.Collections.Generic;
7 | using System.IO;
8 | using FcmSharp.BackOff;
9 | using Google.Apis.Auth.OAuth2;
10 | using Newtonsoft.Json;
11 |
12 | namespace FcmSharp.Settings
13 | {
14 | public static class FileBasedFcmClientSettings
15 | {
16 | public static FcmClientSettings CreateFromFile(string credentialsFileName)
17 | {
18 | var credentials = ReadCredentialsFromFile(credentialsFileName);
19 | var project = GetProjectId(credentialsFileName, credentials);
20 |
21 | return new FcmClientSettings(project, credentials);
22 | }
23 |
24 | public static FcmClientSettings CreateFromFile(string credentialsFileName, ExponentialBackOffSettings exponentialBackOffSettings)
25 | {
26 | var credentials = ReadCredentialsFromFile(credentialsFileName);
27 | var project = GetProjectId(credentialsFileName, credentials);
28 |
29 | return new FcmClientSettings(project, credentials, exponentialBackOffSettings);
30 | }
31 |
32 | public static FcmClientSettings CreateFromFile(string project, string credentialsFileName)
33 | {
34 | var credentials = ReadCredentialsFromFile(credentialsFileName);
35 |
36 | return new FcmClientSettings(project, credentials);
37 | }
38 |
39 | public static FcmClientSettings CreateFromFile(string project, string credentialsFileName, ExponentialBackOffSettings exponentialBackOffSettings)
40 | {
41 | var credentials = ReadCredentialsFromFile(credentialsFileName);
42 |
43 | return new FcmClientSettings(project, credentials, exponentialBackOffSettings);
44 | }
45 |
46 | private static string ReadCredentialsFromFile(string fileName)
47 | {
48 | if (fileName == null)
49 | {
50 | throw new ArgumentNullException("fileName");
51 | }
52 |
53 | if (!File.Exists(fileName))
54 | {
55 | throw new Exception($"Could not Read Credentials. (Reason = File Does Not Exist, FileName = '{fileName}')");
56 | }
57 |
58 | string credentials = File.ReadAllText(fileName);
59 |
60 | if (string.IsNullOrWhiteSpace(credentials))
61 | {
62 | throw new Exception($"Could not Read Credentials. (Reason = File Is Empty, FileName = '{fileName}')");
63 | }
64 |
65 | return credentials;
66 | }
67 |
68 | private static string GetProjectId(string serviceAccountKeyFile, string serviceAccountKeyJson)
69 | {
70 | var serviceAccountKeyDictionary = JsonConvert.DeserializeObject>(serviceAccountKeyJson);
71 |
72 | if (!serviceAccountKeyDictionary.ContainsKey("project_id"))
73 | {
74 | throw new Exception($"Could not read Project ID from ServiceAccountKey File '{serviceAccountKeyFile}'");
75 | }
76 |
77 | return serviceAccountKeyDictionary["project_id"];
78 | }
79 | }
80 | }
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Settings/IFcmClientSettings.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner. All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using FcmSharp.BackOff;
5 |
6 | namespace FcmSharp.Settings
7 | {
8 | public interface IFcmClientSettings
9 | {
10 | string Project { get; }
11 |
12 | string Credentials { get; }
13 |
14 | ExponentialBackOffSettings ExponentialBackOffSettings { get; }
15 | }
16 | }
--------------------------------------------------------------------------------
/FcmSharp/FcmSharp/Settings/StreamBasedFcmClientSettings.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Philipp Wagner and janniksam (https://github.com/janniksam). All rights reserved.
2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 |
4 | using System;
5 | using System.IO;
6 | using FcmSharp.BackOff;
7 |
8 | namespace FcmSharp.Settings
9 | {
10 | public static class StreamBasedFcmClientSettings
11 | {
12 | public static FcmClientSettings CreateFromStream(string project, Stream credentialsStream)
13 | {
14 | var credentials = ReadCredentialsFromStream(credentialsStream);
15 |
16 | return new FcmClientSettings(project, credentials);
17 | }
18 |
19 | public static FcmClientSettings CreateFromStream(string project, Stream credentialsStream, ExponentialBackOffSettings exponentialBackOffSettings)
20 | {
21 | var credentials = ReadCredentialsFromStream(credentialsStream);
22 |
23 | return new FcmClientSettings(project, credentials, exponentialBackOffSettings);
24 | }
25 |
26 | private static string ReadCredentialsFromStream(Stream credentialStream)
27 | {
28 | if (credentialStream == null)
29 | {
30 | throw new ArgumentNullException("credentialStream");
31 | }
32 |
33 | if (!credentialStream.CanRead)
34 | {
35 | throw new ArgumentException("Cannot read from the given stream", "credentialStream");
36 | }
37 |
38 | using (StreamReader reader = new StreamReader(credentialStream))
39 | {
40 | return reader.ReadToEnd();
41 | }
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/FcmSharp/Images/OV_Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/Images/OV_Logo.png
--------------------------------------------------------------------------------
/FcmSharp/Images/jetbrains.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/Images/jetbrains.png
--------------------------------------------------------------------------------
/FcmSharp/NuGet/FcmSharp.1.0.0.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/NuGet/FcmSharp.1.0.0.nupkg
--------------------------------------------------------------------------------
/FcmSharp/NuGet/FcmSharp.1.1.0.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/NuGet/FcmSharp.1.1.0.nupkg
--------------------------------------------------------------------------------
/FcmSharp/NuGet/FcmSharp.2.0.0.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/NuGet/FcmSharp.2.0.0.nupkg
--------------------------------------------------------------------------------
/FcmSharp/NuGet/FcmSharp.2.1.0.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/NuGet/FcmSharp.2.1.0.nupkg
--------------------------------------------------------------------------------
/FcmSharp/NuGet/FcmSharp.2.1.1.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/NuGet/FcmSharp.2.1.1.nupkg
--------------------------------------------------------------------------------
/FcmSharp/NuGet/FcmSharp.2.2.0.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/NuGet/FcmSharp.2.2.0.nupkg
--------------------------------------------------------------------------------
/FcmSharp/NuGet/FcmSharp.2.3.0.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/NuGet/FcmSharp.2.3.0.nupkg
--------------------------------------------------------------------------------
/FcmSharp/NuGet/FcmSharp.2.3.1.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/NuGet/FcmSharp.2.3.1.nupkg
--------------------------------------------------------------------------------
/FcmSharp/NuGet/FcmSharp.2.3.2.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/NuGet/FcmSharp.2.3.2.nupkg
--------------------------------------------------------------------------------
/FcmSharp/NuGet/FcmSharp.2.4.0.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/NuGet/FcmSharp.2.4.0.nupkg
--------------------------------------------------------------------------------
/FcmSharp/NuGet/FcmSharp.2.5.0.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/NuGet/FcmSharp.2.5.0.nupkg
--------------------------------------------------------------------------------
/FcmSharp/NuGet/FcmSharp.2.6.0-alpha.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/NuGet/FcmSharp.2.6.0-alpha.nupkg
--------------------------------------------------------------------------------
/FcmSharp/NuGet/FcmSharp.2.6.0.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/NuGet/FcmSharp.2.6.0.nupkg
--------------------------------------------------------------------------------
/FcmSharp/NuGet/FcmSharp.2.7.0.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/NuGet/FcmSharp.2.7.0.nupkg
--------------------------------------------------------------------------------
/FcmSharp/NuGet/FcmSharp.2.7.1.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/NuGet/FcmSharp.2.7.1.nupkg
--------------------------------------------------------------------------------
/FcmSharp/NuGet/FcmSharp.2.8.0.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/NuGet/FcmSharp.2.8.0.nupkg
--------------------------------------------------------------------------------
/FcmSharp/NuGet/FcmSharp.2.8.1.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/NuGet/FcmSharp.2.8.1.nupkg
--------------------------------------------------------------------------------
/FcmSharp/NuGet/FcmSharp.2.8.2.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/NuGet/FcmSharp.2.8.2.nupkg
--------------------------------------------------------------------------------
/FcmSharp/NuGet/FcmSharp.2.8.3.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/NuGet/FcmSharp.2.8.3.nupkg
--------------------------------------------------------------------------------
/FcmSharp/NuGet/FcmSharp.2.8.4.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/NuGet/FcmSharp.2.8.4.nupkg
--------------------------------------------------------------------------------
/FcmSharp/NuGet/FcmSharp.3.0.0.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/NuGet/FcmSharp.3.0.0.nupkg
--------------------------------------------------------------------------------
/FcmSharp/NuGet/FcmSharp.3.0.1.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytefish/FcmSharp/20bbdcb4f69ee521511efea7ec48b10f130d701a/FcmSharp/NuGet/FcmSharp.3.0.1.nupkg
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) Philipp Wagner and Contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------