├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── pull_request_template.md └── workflows │ ├── build.yml │ ├── create_jira.yml │ ├── release.yml │ └── snyk.yml ├── .gitignore ├── Analytics-CSharp.sln ├── Analytics-CSharp ├── Analytics-CSharp.csproj ├── Resources │ └── link.xml └── Segment │ └── Analytics │ ├── Analytics.cs │ ├── Compat │ └── Migration.cs │ ├── Configuration.cs │ ├── Errors.cs │ ├── Events.cs │ ├── Plugins.cs │ ├── Plugins │ ├── ContextPlugin.cs │ ├── DestinationMetadataPlugin.cs │ ├── SegmentDestination.cs │ └── StartupQueue.cs │ ├── Policies │ ├── CountFlushPolicy.cs │ ├── FrequencyFlushPolicy.cs │ ├── IFlushPolicy.cs │ └── StartupFlushPolicy.cs │ ├── Settings.cs │ ├── State.cs │ ├── Timeline.cs │ ├── Types.cs │ ├── Utilities │ ├── EventOutputStream.cs │ ├── EventPipeline.cs │ ├── EventPipelineProvider.cs │ ├── ExtensionMethods.cs │ ├── HTTPClient.cs │ ├── IEventPipeline.cs │ ├── IEventPipelineProvider.cs │ ├── Logging.cs │ ├── Storage.cs │ ├── SyncEventPipeline.cs │ ├── SyncEventPipelineProvider.cs │ ├── SystemInfo.cs │ └── UserPrefs.cs │ └── Version.cs ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── RELEASING.md ├── Samples ├── AspNetMvcSample │ ├── AspNetMvcSample.csproj │ ├── Controllers │ │ ├── AnalyticsController.cs │ │ ├── HomeController.cs │ │ └── PizzaController.cs │ ├── Models │ │ ├── ErrorViewModel.cs │ │ └── Pizza.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Services │ │ └── PizzaService.cs │ ├── Startup.cs │ ├── Views │ │ ├── Home │ │ │ ├── Index.cshtml │ │ │ └── Privacy.cshtml │ │ ├── Pizza │ │ │ └── Index.cshtml │ │ ├── Shared │ │ │ ├── Error.cshtml │ │ │ ├── _Layout.cshtml │ │ │ └── _ValidationScriptsPartial.cshtml │ │ ├── _ViewImports.cshtml │ │ └── _ViewStart.cshtml │ ├── appsettings.Development.json │ ├── appsettings.json │ └── wwwroot │ │ ├── css │ │ └── site.css │ │ ├── favicon.ico │ │ ├── js │ │ └── site.js │ │ └── lib │ │ ├── bootstrap │ │ ├── LICENSE │ │ └── dist │ │ │ ├── css │ │ │ ├── bootstrap-grid.css │ │ │ ├── bootstrap-grid.css.map │ │ │ ├── bootstrap-grid.min.css │ │ │ ├── bootstrap-grid.min.css.map │ │ │ ├── bootstrap-reboot.css │ │ │ ├── bootstrap-reboot.css.map │ │ │ ├── bootstrap-reboot.min.css │ │ │ ├── bootstrap-reboot.min.css.map │ │ │ ├── bootstrap.css │ │ │ ├── bootstrap.css.map │ │ │ ├── bootstrap.min.css │ │ │ └── bootstrap.min.css.map │ │ │ └── js │ │ │ ├── bootstrap.bundle.js │ │ │ ├── bootstrap.bundle.js.map │ │ │ ├── bootstrap.bundle.min.js │ │ │ ├── bootstrap.bundle.min.js.map │ │ │ ├── bootstrap.js │ │ │ ├── bootstrap.js.map │ │ │ ├── bootstrap.min.js │ │ │ └── bootstrap.min.js.map │ │ ├── jquery-validation-unobtrusive │ │ ├── LICENSE.txt │ │ ├── jquery.validate.unobtrusive.js │ │ └── jquery.validate.unobtrusive.min.js │ │ ├── jquery-validation │ │ ├── LICENSE.md │ │ └── dist │ │ │ ├── additional-methods.js │ │ │ ├── additional-methods.min.js │ │ │ ├── jquery.validate.js │ │ │ └── jquery.validate.min.js │ │ └── jquery │ │ ├── LICENSE.txt │ │ └── dist │ │ ├── jquery.js │ │ ├── jquery.min.js │ │ └── jquery.min.map ├── AspNetSample │ ├── AnalyticsPageModel.cs │ ├── AspNetSample.csproj │ ├── Pages │ │ ├── Error.cshtml │ │ ├── Error.cshtml.cs │ │ ├── Index.cshtml │ │ ├── Index.cshtml.cs │ │ ├── Models │ │ │ └── Pizza.cs │ │ ├── Pizza.cshtml │ │ ├── Pizza.cshtml.cs │ │ ├── Privacy.cshtml │ │ ├── Privacy.cshtml.cs │ │ ├── Services │ │ │ └── PizzaService.cs │ │ ├── Shared │ │ │ ├── _Layout.cshtml │ │ │ └── _ValidationScriptsPartial.cshtml │ │ ├── _ViewImports.cshtml │ │ └── _ViewStart.cshtml │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Startup.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ └── wwwroot │ │ ├── css │ │ └── site.css │ │ ├── favicon.ico │ │ ├── js │ │ └── site.js │ │ └── lib │ │ ├── bootstrap │ │ ├── LICENSE │ │ └── dist │ │ │ ├── css │ │ │ ├── bootstrap-grid.css │ │ │ ├── bootstrap-grid.css.map │ │ │ ├── bootstrap-grid.min.css │ │ │ ├── bootstrap-grid.min.css.map │ │ │ ├── bootstrap-reboot.css │ │ │ ├── bootstrap-reboot.css.map │ │ │ ├── bootstrap-reboot.min.css │ │ │ ├── bootstrap-reboot.min.css.map │ │ │ ├── bootstrap.css │ │ │ ├── bootstrap.css.map │ │ │ ├── bootstrap.min.css │ │ │ └── bootstrap.min.css.map │ │ │ └── js │ │ │ ├── bootstrap.bundle.js │ │ │ ├── bootstrap.bundle.js.map │ │ │ ├── bootstrap.bundle.min.js │ │ │ ├── bootstrap.bundle.min.js.map │ │ │ ├── bootstrap.js │ │ │ ├── bootstrap.js.map │ │ │ ├── bootstrap.min.js │ │ │ └── bootstrap.min.js.map │ │ ├── jquery-validation-unobtrusive │ │ ├── LICENSE.txt │ │ ├── jquery.validate.unobtrusive.js │ │ └── jquery.validate.unobtrusive.min.js │ │ ├── jquery-validation │ │ ├── LICENSE.md │ │ └── dist │ │ │ ├── additional-methods.js │ │ │ ├── additional-methods.min.js │ │ │ ├── jquery.validate.js │ │ │ └── jquery.validate.min.js │ │ └── jquery │ │ ├── LICENSE.txt │ │ └── dist │ │ ├── jquery.js │ │ ├── jquery.min.js │ │ └── jquery.min.map ├── ConsoleSample │ ├── ConsoleSample.csproj │ ├── FlushOnScreenEventsPolicy.cs │ ├── NetworkErrorHandler.cs │ ├── Program.cs │ ├── ProxyHttpClient.cs │ └── SegmentLogger.cs ├── UnitySample │ ├── LifecyclePlugin.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Singleton.cs │ ├── SingletonAnalytics.cs │ ├── UnityHTTPClient.cs │ └── UnitySample.csproj └── XamarinSample │ ├── XamarinSample.Droid │ ├── Assets │ │ └── AboutAssets.txt │ ├── MainActivity.cs │ ├── Properties │ │ ├── AndroidManifest.xml │ │ └── AssemblyInfo.cs │ ├── Resources │ │ ├── AboutResources.txt │ │ ├── Resource.designer.cs │ │ ├── layout │ │ │ ├── Tabbar.axml │ │ │ └── Toolbar.axml │ │ └── values │ │ │ ├── colors.xml │ │ │ └── styles.xml │ └── XamarinSample.Droid.csproj │ ├── XamarinSample.iOS │ ├── AppDelegate.cs │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Entitlements.plist │ ├── Info.plist │ ├── Main.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Resources │ │ └── LaunchScreen.storyboard │ └── XamarinSample.iOS.csproj │ └── XamarinSample │ ├── App.xaml │ ├── App.xaml.cs │ ├── MainPage.xaml │ ├── MainPage.xaml.cs │ └── XamarinSample.csproj ├── Tests ├── AnalyticsTest.cs ├── Compat │ └── MigrationTest.cs ├── EventsTest.cs ├── Plugins │ └── DestinationMetadataPluginTest.cs ├── PluginsTest.cs ├── Policies │ ├── CountFlushPolicyTest.cs │ ├── FrequencyFlushPolicyTest.cs │ └── StartupFlushPolicyTest.cs ├── StateTest.cs ├── Tests.csproj ├── Utilities │ ├── EventOutputStreamTest.cs │ ├── EventPipelineTest.cs │ ├── LoggingTest.cs │ ├── SettingsTest.cs │ ├── StorageTest.cs │ ├── SystemInfoTest.cs │ └── UserPrefsTest.cs └── Utils │ └── Stubs.cs └── upm_release.sh /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: triage 6 | assignees: wenxi-zeng 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Do '...' 16 | 2. '....' 17 | 3. See error 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Screenshots** 23 | If applicable, add screenshots to help explain your problem. 24 | 25 | **Platform (please complete the following information):** 26 | - Library Version in use: [e.g. 0.0.5] 27 | - Platform being tested: [e.g. Android] 28 | - Integrations in use: [e.g. Firebase, Amplitude] 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: triage 6 | assignees: wenxi-zeng 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Purpose 2 | _Describe the problem or feature in addition to a link to the issues._ 3 | 4 | ## Approach 5 | _How does this change address the problem?_ 6 | 7 | #### Open Questions and Pre-Merge TODOs 8 | - [ ] Use github checklists. When solved, check the box and explain the answer. 9 | 10 | ## Learning 11 | _Describe the research stage_ 12 | 13 | _Links to blog posts, patterns, libraries or addons used to solve this problem_ -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | cancel_previous: 12 | permissions: write-all 13 | runs-on: ubuntu-22.04 14 | steps: 15 | - uses: styfle/cancel-workflow-action@0.9.1 16 | with: 17 | workflow_id: ${{ github.event.workflow.id }} 18 | 19 | build: 20 | needs: cancel_previous 21 | runs-on: ubuntu-22.04 22 | 23 | steps: 24 | - uses: actions/checkout@v3 25 | - name: Setup .NET Core SDK 26 | uses: actions/setup-dotnet@v2 27 | with: 28 | dotnet-version: | 29 | 3.1.x 30 | 5.0.x 31 | 6.0.x 32 | 33 | - name: Restore cache 34 | uses: actions/cache@v3 35 | with: 36 | path: ~/.nuget/packages 37 | # Look to see if there is a cache hit for the corresponding requirements file 38 | key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }} 39 | restore-keys: | 40 | ${{ runner.os }}-nuget 41 | 42 | - name: Build 43 | run: dotnet build 44 | 45 | - name: Test 46 | run: dotnet test 47 | -------------------------------------------------------------------------------- /.github/workflows/create_jira.yml: -------------------------------------------------------------------------------- 1 | name: Create Jira Ticket 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | 8 | jobs: 9 | create_jira: 10 | name: Create Jira Ticket 11 | runs-on: ubuntu-latest 12 | environment: IssueTracker 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@master 16 | - name: Login 17 | uses: atlassian/gajira-login@master 18 | env: 19 | JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} 20 | JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} 21 | JIRA_API_TOKEN: ${{ secrets.JIRA_TOKEN }} 22 | JIRA_EPIC_KEY: ${{ secrets.JIRA_EPIC_KEY }} 23 | JIRA_PROJECT: ${{ secrets.JIRA_PROJECT }} 24 | 25 | - name: Create 26 | id: create 27 | uses: atlassian/gajira-create@master 28 | with: 29 | project: ${{ secrets.JIRA_PROJECT }} 30 | issuetype: Bug 31 | summary: | 32 | [${{ github.event.repository.name }}] (${{ github.event.issue.number }}): ${{ github.event.issue.title }} 33 | description: | 34 | Github Link: ${{ github.event.issue.html_url }} 35 | ${{ github.event.issue.body }} 36 | fields: '{"parent": {"key": "${{ secrets.JIRA_EPIC_KEY }}"}}' 37 | 38 | - name: Log created issue 39 | run: echo "Issue ${{ steps.create.outputs.issue }} was created" 40 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '[0-9]+.[0-9]+.[0-9]+' 7 | - '[0-9]+.[0-9]+.[0-9]+-alpha.[0-9]+' 8 | 9 | jobs: 10 | release: 11 | permissions: write-all 12 | runs-on: ubuntu-22.04 13 | environment: deployment 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Get tag 18 | id: vars 19 | run: echo ::set-output name=tag::${GITHUB_REF#refs/*/} 20 | - name: Verify tag 21 | run: | 22 | VERSION=$(grep '' Analytics-CSharp/Analytics-CSharp.csproj | sed "s@.*\(.*\).*@\1@") 23 | if [ "${{ steps.vars.outputs.tag }}" != "$VERSION" ]; then { 24 | echo "Tag ${{ steps.vars.outputs.tag }} does not match the package version ($VERSION)" 25 | exit 1 26 | } fi 27 | - name: Setup .NET Core SDK 28 | uses: actions/setup-dotnet@v2 29 | with: 30 | dotnet-version: | 31 | 3.1.x 32 | 5.0.x 33 | 6.0.x 34 | - name: Restore cache 35 | uses: actions/cache@v3 36 | with: 37 | path: ~/.nuget/packages 38 | # Look to see if there is a cache hit for the corresponding requirements file 39 | key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }} 40 | restore-keys: | 41 | ${{ runner.os }}-nuget 42 | - name: Pack 43 | run: dotnet pack 44 | 45 | - name: Publish 46 | run: dotnet nuget push **/*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }} --skip-duplicate 47 | 48 | - name: Create release 49 | run: | 50 | curl \ 51 | -X POST \ 52 | -H "Authorization: token $GITHUB_TOKEN" \ 53 | https://api.github.com/repos/${{github.repository}}/releases \ 54 | -d '{"tag_name": "${{ env.RELEASE_VERSION }}", "name": "${{ env.RELEASE_VERSION }}", "body": "Release of version ${{ env.RELEASE_VERSION }}", "draft": false, "prerelease": false, "generate_release_notes": true}' 55 | env: 56 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 57 | RELEASE_VERSION: ${{ steps.vars.outputs.tag }} 58 | -------------------------------------------------------------------------------- /.github/workflows/snyk.yml: -------------------------------------------------------------------------------- 1 | name: Snyk 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | security: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@master 15 | - name: Install dependencies 16 | run: dotnet restore 17 | - name: Run Snyk to check for vulnerabilities 18 | uses: snyk/actions/dotnet@master 19 | env: 20 | SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} 21 | with: 22 | args: | 23 | --file=Analytics-CSharp.sln 24 | --fail-on=patchable -------------------------------------------------------------------------------- /Analytics-CSharp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.810.25 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Analytics-CSharp", "Analytics-CSharp\Analytics-CSharp.csproj", "{139FE430-293C-470E-A67A-5C35A316FF00}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{90B4FDEC-99F3-4DBE-969A-956363CDA17F}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XamarinSample", "Samples\XamarinSample\XamarinSample\XamarinSample.csproj", "{A50D6328-41E0-45F0-9287-A8E7D4C7F71E}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XamarinSample.Droid", "Samples\XamarinSample\XamarinSample.Droid\XamarinSample.Droid.csproj", "{86FFFF37-9FB0-4632-8D44-E09202E158AF}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XamarinSample.iOS", "Samples\XamarinSample\XamarinSample.iOS\XamarinSample.iOS.csproj", "{39FCFC71-411C-419E-97BB-7C4281764B10}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleSample", "Samples\ConsoleSample\ConsoleSample.csproj", "{51261FC5-F6E0-478C-A466-A026A6CFC972}" 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitySample", "Samples\UnitySample\UnitySample.csproj", "{1C2C676E-E82F-484C-BCC5-9ECAD5C5A118}" 19 | EndProject 20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetSample", "Samples\AspNetSample\AspNetSample.csproj", "{3A5D38A4-2E80-404D-B16B-747F7B6EBB86}" 21 | EndProject 22 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetMvcSample", "Samples\AspNetMvcSample\AspNetMvcSample.csproj", "{78870E6B-FEEC-4F20-87AB-50B124D99203}" 23 | EndProject 24 | Global 25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 26 | Debug|Any CPU = Debug|Any CPU 27 | Release|Any CPU = Release|Any CPU 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {139FE430-293C-470E-A67A-5C35A316FF00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {139FE430-293C-470E-A67A-5C35A316FF00}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {139FE430-293C-470E-A67A-5C35A316FF00}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {139FE430-293C-470E-A67A-5C35A316FF00}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {90B4FDEC-99F3-4DBE-969A-956363CDA17F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {90B4FDEC-99F3-4DBE-969A-956363CDA17F}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {90B4FDEC-99F3-4DBE-969A-956363CDA17F}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {90B4FDEC-99F3-4DBE-969A-956363CDA17F}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {A50D6328-41E0-45F0-9287-A8E7D4C7F71E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {A50D6328-41E0-45F0-9287-A8E7D4C7F71E}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {A50D6328-41E0-45F0-9287-A8E7D4C7F71E}.Release|Any CPU.Build.0 = Release|Any CPU 41 | {86FFFF37-9FB0-4632-8D44-E09202E158AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 42 | {86FFFF37-9FB0-4632-8D44-E09202E158AF}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {86FFFF37-9FB0-4632-8D44-E09202E158AF}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {39FCFC71-411C-419E-97BB-7C4281764B10}.Release|Any CPU.ActiveCfg = Release|iPhoneSimulator 45 | {39FCFC71-411C-419E-97BB-7C4281764B10}.Release|Any CPU.Build.0 = Release|iPhoneSimulator 46 | {39FCFC71-411C-419E-97BB-7C4281764B10}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator 47 | {51261FC5-F6E0-478C-A466-A026A6CFC972}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 48 | {51261FC5-F6E0-478C-A466-A026A6CFC972}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {51261FC5-F6E0-478C-A466-A026A6CFC972}.Release|Any CPU.Build.0 = Release|Any CPU 50 | {1C2C676E-E82F-484C-BCC5-9ECAD5C5A118}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {1C2C676E-E82F-484C-BCC5-9ECAD5C5A118}.Release|Any CPU.ActiveCfg = Release|Any CPU 52 | {1C2C676E-E82F-484C-BCC5-9ECAD5C5A118}.Release|Any CPU.Build.0 = Release|Any CPU 53 | {3A5D38A4-2E80-404D-B16B-747F7B6EBB86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 54 | {3A5D38A4-2E80-404D-B16B-747F7B6EBB86}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {3A5D38A4-2E80-404D-B16B-747F7B6EBB86}.Release|Any CPU.Build.0 = Release|Any CPU 56 | {78870E6B-FEEC-4F20-87AB-50B124D99203}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 57 | {78870E6B-FEEC-4F20-87AB-50B124D99203}.Release|Any CPU.ActiveCfg = Release|Any CPU 58 | {78870E6B-FEEC-4F20-87AB-50B124D99203}.Release|Any CPU.Build.0 = Release|Any CPU 59 | EndGlobalSection 60 | GlobalSection(SolutionProperties) = preSolution 61 | HideSolutionNode = FALSE 62 | EndGlobalSection 63 | GlobalSection(ExtensibilityGlobals) = postSolution 64 | SolutionGuid = {BE115971-81AE-42FA-B2D7-873E1A002F01} 65 | EndGlobalSection 66 | GlobalSection(MonoDevelopProperties) = preSolution 67 | Policies = $0 68 | $0.DotNetNamingPolicy = $1 69 | $1.DirectoryNamespaceAssociation = Hierarchical 70 | $0.TextStylePolicy = $2 71 | $2.inheritsSet = null 72 | $2.scope = text/x-csharp 73 | $0.CSharpFormattingPolicy = $3 74 | $3.scope = text/x-csharp 75 | $0.TextStylePolicy = $4 76 | $4.FileWidth = 80 77 | $4.TabsToSpaces = True 78 | $4.scope = text/plain 79 | $0.StandardHeader = $5 80 | $0.VersionControlPolicy = $6 81 | EndGlobalSection 82 | EndGlobal 83 | -------------------------------------------------------------------------------- /Analytics-CSharp/Analytics-CSharp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard1.3;netstandard2.0 5 | Segment.Analytics.CSharp 6 | 7 | 8 | 9 | ./nupkg 10 | 11 | Segment, Inc 12 | The hassle-free way to add analytics to your C# app. 13 | 2.5.3 14 | MIT 15 | https://github.com/segmentio/Analytics-CSharp 16 | git 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | Segment.Analytics.CSharp.xml 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | <_Parameter1>Tests 48 | 49 | 50 | <_Parameter1>DynamicProxyGenAssembly2 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /Analytics-CSharp/Resources/link.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Analytics-CSharp/Segment/Analytics/Compat/Migration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Segment.Serialization; 4 | 5 | namespace Segment.Analytics.Compat 6 | { 7 | 8 | [Obsolete("This should only be used if migrating from Analytics.NET or Analytics.Xamarin")] 9 | public class Traits : Dictionary 10 | { 11 | } 12 | 13 | [Obsolete("This should only be used if migrating from Analytics.NET or Analytics.Xamarin")] 14 | public class Properties : Dictionary 15 | { 16 | } 17 | 18 | public static class AnalyticsExtensions 19 | { 20 | [Obsolete("This should only be used if migrating from Analytics.NET or Analytics.Xamarin")] 21 | public static void Track(this Analytics analytics, string userId, string eventName) 22 | { 23 | analytics.Track(eventName, new JsonObject() {{"userId", userId}}); 24 | } 25 | 26 | [Obsolete("This should only be used if migrating from Analytics.NET or Analytics.Xamarin")] 27 | public static void Track(this Analytics analytics, string userId, string eventName, 28 | Dictionary properties) 29 | { 30 | if (properties == null) 31 | { 32 | properties = new Dictionary(); 33 | } 34 | properties.Add("userId", userId); 35 | analytics.Track(eventName, 36 | JsonUtility.FromJson(JsonUtility.ToJson(properties))); 37 | } 38 | 39 | [Obsolete("This should only be used if migrating from Analytics.NET or Analytics.Xamarin")] 40 | public static void Screen(this Analytics analytics, string userId, string eventName) 41 | { 42 | analytics.Screen(eventName, new JsonObject() {{"userId", userId}}); 43 | } 44 | 45 | [Obsolete("This should only be used if migrating from Analytics.NET or Analytics.Xamarin")] 46 | public static void Screen(this Analytics analytics, string userId, string title, 47 | Dictionary properties) 48 | { 49 | if (properties == null) 50 | { 51 | properties = new Dictionary(); 52 | } 53 | properties.Add("userId", userId); 54 | analytics.Screen(title, 55 | JsonUtility.FromJson(JsonUtility.ToJson(properties))); 56 | } 57 | 58 | [Obsolete("This should only be used if migrating from Analytics.NET or Analytics.Xamarin")] 59 | public static void Page(this Analytics analytics, string userId, string title) 60 | { 61 | analytics.Page(title, new JsonObject() {{"userId", userId}}); 62 | } 63 | 64 | [Obsolete("This should only be used if migrating from Analytics.NET or Analytics.Xamarin")] 65 | public static void Page(this Analytics analytics, string userId, string title, 66 | Dictionary properties) 67 | { 68 | if (properties == null) 69 | { 70 | properties = new Dictionary(); 71 | } 72 | properties.Add("userId", userId); 73 | analytics.Page(title, 74 | JsonUtility.FromJson(JsonUtility.ToJson(properties))); 75 | } 76 | 77 | [Obsolete("This should only be used if migrating from Analytics.NET or Analytics.Xamarin")] 78 | public static void Group(this Analytics analytics, string userId, string groupId, 79 | Dictionary traits) 80 | { 81 | if (traits == null) 82 | { 83 | traits = new Dictionary(); 84 | } 85 | traits.Add("userId", userId); 86 | analytics.Group(groupId, 87 | JsonUtility.FromJson(JsonUtility.ToJson(traits))); 88 | } 89 | 90 | [Obsolete("This should only be used if migrating from Analytics.NET or Analytics.Xamarin")] 91 | public static void Alias(this Analytics analytics, string previousId, string userId) 92 | { 93 | analytics._userInfo._userId = previousId; 94 | analytics.Alias(userId); 95 | } 96 | } 97 | 98 | /// 99 | /// Plugin that patches user id on a per event basis. 100 | /// This plugin helps migration from the old Analytics.NET and Analytics.Xamarin libraries, 101 | /// since Analytics-CSharp does not support passing user id on every track method. 102 | /// 103 | [Obsolete("This should only be used if migrating from Analytics.NET or Analytics.Xamarin")] 104 | class UserIdPlugin : EventPlugin 105 | { 106 | public override PluginType Type => PluginType.Enrichment; 107 | 108 | public override RawEvent Execute(RawEvent incomingEvent) 109 | { 110 | switch (incomingEvent) 111 | { 112 | case TrackEvent e: 113 | PatchUserId(e, e.Properties); 114 | break; 115 | case PageEvent e: 116 | PatchUserId(e, e.Properties); 117 | break; 118 | case ScreenEvent e: 119 | PatchUserId(e, e.Properties); 120 | break; 121 | case GroupEvent e: 122 | PatchUserId(e, e.Traits); 123 | break; 124 | } 125 | 126 | return incomingEvent; 127 | } 128 | 129 | private void PatchUserId(RawEvent @event, JsonObject jsonObject) 130 | { 131 | if (jsonObject.ContainsKey("userId")) 132 | { 133 | @event.UserId = jsonObject.GetString("userId"); 134 | jsonObject.Remove("userId"); 135 | } 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /Analytics-CSharp/Segment/Analytics/Errors.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Segment.Analytics.Utilities; 3 | using Segment.Concurrent; 4 | 5 | namespace Segment.Analytics 6 | { 7 | public partial class Analytics 8 | { 9 | /// 10 | /// Reports an internal error to the user-defined error handler. 11 | /// 12 | /// Exception to report 13 | /// Error message 14 | public static void ReportInternalError(AnalyticsError error = null, string message = null) 15 | { 16 | Logger.Log(LogLevel.Error, error, message); 17 | } 18 | 19 | /// 20 | /// Extension method to reports an internal error to the user-defined error handler if 21 | /// analytics instance is available to access. 22 | /// 23 | /// Type of the analytics error 24 | /// Exception to throw 25 | /// Error message 26 | public static void ReportInternalError(AnalyticsErrorType type, Exception exception = null, string message = null) 27 | { 28 | ReportInternalError(new AnalyticsError(type, message, exception), message); 29 | } 30 | } 31 | 32 | public static class ExtensionMethods 33 | { 34 | /// 35 | /// Extension method to reports an internal error to the user-defined error handler if 36 | /// analytics instance is available to access. 37 | /// 38 | /// Segment Analytics 39 | /// Exception to report 40 | public static void ReportInternalError(this Analytics analytics, AnalyticsError error) 41 | { 42 | analytics.Configuration.AnalyticsErrorHandler?.OnExceptionThrown(error); 43 | Analytics.ReportInternalError(error, error.Message); 44 | } 45 | 46 | /// 47 | /// Extension method to reports an internal error to the user-defined error handler if 48 | /// analytics instance is available to access. 49 | /// 50 | /// Segment Analytics 51 | /// Type of the analytics error 52 | /// Exception to throw 53 | /// Error message 54 | public static void ReportInternalError(this Analytics analytics, AnalyticsErrorType type, Exception exception = null, string message = null) 55 | { 56 | var error = new AnalyticsError(type, message, exception); 57 | analytics.Configuration.AnalyticsErrorHandler?.OnExceptionThrown(error); 58 | Analytics.ReportInternalError(error, error.Message); 59 | } 60 | } 61 | 62 | public class AnalyticsError : Exception 63 | { 64 | public AnalyticsErrorType ErrorType { get; } 65 | 66 | public AnalyticsError(AnalyticsErrorType type, string message = null, Exception exception = null) : base(message, exception) 67 | { 68 | ErrorType = type; 69 | } 70 | } 71 | 72 | public enum AnalyticsErrorType 73 | { 74 | StorageUnableToCreate, 75 | StorageUnableToWrite, 76 | StorageUnableToRename, 77 | StorageUnableToOpen, 78 | StorageUnableToRemove, 79 | StorageInvalid, 80 | StorageUnknown, 81 | 82 | NetworkUnexpectedHttpCode, 83 | NetworkServerLimited, 84 | NetworkServerRejected, 85 | NetworkUnknown, 86 | NetworkInvalidData, 87 | 88 | JsonUnableToSerialize, 89 | JsonUnableToDeserialize, 90 | JsonUnknown, 91 | 92 | PluginError, 93 | 94 | PayloadInvalid 95 | } 96 | 97 | public interface IAnalyticsErrorHandler : ICoroutineExceptionHandler {} 98 | 99 | internal class AnalyticsErrorHandlerAdapter : IAnalyticsErrorHandler 100 | { 101 | private readonly ICoroutineExceptionHandler _handler; 102 | 103 | public AnalyticsErrorHandlerAdapter(ICoroutineExceptionHandler handler) 104 | { 105 | _handler = handler; 106 | } 107 | 108 | public void OnExceptionThrown(Exception e) => _handler.OnExceptionThrown(e); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Analytics-CSharp/Segment/Analytics/Plugins/ContextPlugin.cs: -------------------------------------------------------------------------------- 1 | 2 | using Segment.Analytics.Utilities; 3 | using Segment.Serialization; 4 | 5 | namespace Segment.Analytics.Plugins 6 | { 7 | /// 8 | /// Analytics plugin used to populate events with basic context data. 9 | /// Auto-added to analytics client on construction 10 | /// 11 | public class ContextPlugin : Plugin 12 | { 13 | public override PluginType Type => PluginType.Before; 14 | 15 | private JsonObject _library; 16 | 17 | private const string LibraryKey = "library"; 18 | 19 | private const string LibraryNameKey = "name"; 20 | 21 | private const string LibraryVersionKey = "version"; 22 | 23 | private const string OSKey = "os"; 24 | 25 | private const string PlatformKey = "platform"; 26 | 27 | public override void Configure(Analytics analytics) 28 | { 29 | base.Configure(analytics); 30 | _library = new JsonObject 31 | { 32 | [LibraryNameKey] = "Analytics-CSharp", 33 | [LibraryVersionKey] = Version.SegmentVersion 34 | }; 35 | } 36 | 37 | private void ApplyContextData(RawEvent @event) 38 | { 39 | var context = new JsonObject(@event.Context?.Content) 40 | { 41 | [LibraryKey] = _library, 42 | [OSKey] = SystemInfo.GetOs(), 43 | [PlatformKey] = SystemInfo.GetPlatform() 44 | }; 45 | 46 | @event.Context = context; 47 | } 48 | 49 | public override RawEvent Execute(RawEvent incomingEvent) 50 | { 51 | ApplyContextData(incomingEvent); 52 | return base.Execute(incomingEvent); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Analytics-CSharp/Segment/Analytics/Plugins/DestinationMetadataPlugin.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Segment.Serialization; 4 | 5 | namespace Segment.Analytics.Plugins 6 | { 7 | public class DestinationMetadataPlugin: Plugin 8 | { 9 | public override PluginType Type => PluginType.Enrichment; 10 | 11 | private Settings _settings; 12 | 13 | public override void Update(Settings settings, UpdateType type) 14 | { 15 | base.Update(settings, type); 16 | _settings = settings; 17 | } 18 | 19 | public override RawEvent Execute(RawEvent incomingEvent) 20 | { 21 | // TODO: Precompute the metadata instead of compute it for every events once we have Sovran changed to synchronize mode 22 | HashSet bundled = new HashSet(); 23 | HashSet unbundled = new HashSet(); 24 | 25 | foreach (Plugin plugin in Analytics.Timeline._plugins[PluginType.Destination]._plugins) 26 | { 27 | if (plugin is DestinationPlugin destinationPlugin && !(plugin is SegmentDestination) && destinationPlugin._enabled) 28 | { 29 | bundled.Add(destinationPlugin.Key); 30 | } 31 | } 32 | 33 | // All active integrations, not in `bundled` are put in `unbundled` 34 | foreach (string integration in _settings.Integrations.Keys) 35 | { 36 | if (integration != "Segment.io" && !bundled.Contains(integration)) 37 | { 38 | unbundled.Add(integration); 39 | } 40 | } 41 | 42 | // All unbundledIntegrations not in `bundled` are put in `unbundled` 43 | JsonArray unbundledIntegrations = 44 | _settings.Integrations?.GetJsonObject("Segment.io")?.GetJsonArray("unbundledIntegrations") ?? 45 | new JsonArray(); 46 | foreach (JsonElement integration in unbundledIntegrations) 47 | { 48 | string content = ((JsonPrimitive)integration).Content; 49 | if (!bundled.Contains(content)) 50 | { 51 | unbundled.Add(content); 52 | } 53 | } 54 | 55 | incomingEvent._metadata = new DestinationMetadata 56 | { 57 | Bundled = CreateJsonArray(bundled), 58 | Unbundled = CreateJsonArray(unbundled), 59 | BundledIds = new JsonArray() 60 | }; 61 | 62 | return incomingEvent; 63 | } 64 | 65 | private JsonArray CreateJsonArray(IEnumerable list) 66 | { 67 | var jsonArray = new JsonArray(); 68 | foreach (string value in list) 69 | { 70 | jsonArray.Add(value); 71 | } 72 | 73 | return jsonArray; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Analytics-CSharp/Segment/Analytics/Plugins/SegmentDestination.cs: -------------------------------------------------------------------------------- 1 | using Segment.Analytics.Utilities; 2 | using Segment.Serialization; 3 | using Segment.Sovran; 4 | 5 | namespace Segment.Analytics.Plugins 6 | { 7 | /// 8 | /// Segment Analytics plugin that is used to send events to Segment's tracking api, in the choice of region. 9 | /// How it works: 10 | /// 11 | /// Plugin receives apiHost settings 12 | /// We store events into a file with the batch api format 13 | /// We upload events on a dedicated thread using the batch api 14 | /// 15 | /// 16 | public class SegmentDestination : DestinationPlugin, ISubscriber 17 | { 18 | private IEventPipeline _pipeline = null; 19 | 20 | public override string Key => "Segment.io"; 21 | 22 | internal const string ApiHost = "apiHost"; 23 | 24 | public override IdentifyEvent Identify(IdentifyEvent identifyEvent) 25 | { 26 | Enqueue(identifyEvent); 27 | return identifyEvent; 28 | } 29 | 30 | public override TrackEvent Track(TrackEvent trackEvent) 31 | { 32 | Enqueue(trackEvent); 33 | return trackEvent; 34 | } 35 | 36 | public override GroupEvent Group(GroupEvent groupEvent) 37 | { 38 | Enqueue(groupEvent); 39 | return groupEvent; 40 | } 41 | 42 | public override AliasEvent Alias(AliasEvent aliasEvent) 43 | { 44 | Enqueue(aliasEvent); 45 | return aliasEvent; 46 | } 47 | 48 | public override ScreenEvent Screen(ScreenEvent screenEvent) 49 | { 50 | Enqueue(screenEvent); 51 | return screenEvent; 52 | } 53 | 54 | public override PageEvent Page(PageEvent pageEvent) 55 | { 56 | Enqueue(pageEvent); 57 | return pageEvent; 58 | } 59 | 60 | public override void Configure(Analytics analytics) 61 | { 62 | base.Configure(analytics); 63 | 64 | // Add DestinationMetadata enrichment plugin 65 | Add(new DestinationMetadataPlugin()); 66 | 67 | _pipeline = analytics.Configuration.EventPipelineProvider.Create(analytics, Key); 68 | 69 | analytics.AnalyticsScope.Launch(analytics.AnalyticsDispatcher, async () => 70 | { 71 | await analytics.Store.Subscribe(this, state => OnEnableToggled((System)state), true); 72 | }); 73 | } 74 | 75 | public override void Update(Settings settings, UpdateType type) 76 | { 77 | base.Update(settings, type); 78 | 79 | JsonObject segmentInfo = settings.Integrations?.GetJsonObject(Key); 80 | string apiHost = segmentInfo?.GetString(ApiHost); 81 | if (apiHost != null && _pipeline != null) 82 | { 83 | _pipeline.ApiHost = apiHost; 84 | } 85 | } 86 | 87 | public override void Reset() 88 | { 89 | 90 | } 91 | 92 | public override void Flush() => _pipeline?.Flush(); 93 | 94 | private void Enqueue(T payload) where T : RawEvent 95 | { 96 | // TODO: filter out empty userid and traits values 97 | _pipeline?.Put(payload); 98 | } 99 | 100 | private void OnEnableToggled(System state) 101 | { 102 | if (state._enable) 103 | { 104 | _pipeline?.Start(); 105 | } 106 | else 107 | { 108 | _pipeline?.Stop(); 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Analytics-CSharp/Segment/Analytics/Plugins/StartupQueue.cs: -------------------------------------------------------------------------------- 1 | using global::System.Collections.Concurrent; 2 | using Segment.Analytics.Utilities; 3 | using Segment.Concurrent; 4 | using Segment.Sovran; 5 | 6 | namespace Segment.Analytics.Plugins 7 | { 8 | /// 9 | /// Analytics plugin to manage started state of analytics client 10 | /// All events will be held in an in-memory queue until started state is enabled, and once enabled 11 | /// events will be replayed into the analytics timeline 12 | /// 13 | public class StartupQueue : Plugin, ISubscriber 14 | { 15 | private static readonly int s_maxSize = 1000; 16 | private readonly AtomicBool _running = new AtomicBool(false); 17 | private readonly ConcurrentQueue _queuedEvents = new ConcurrentQueue(); 18 | 19 | public override PluginType Type => PluginType.Before; 20 | 21 | public override void Configure(Analytics analytics) 22 | { 23 | base.Configure(analytics); 24 | analytics.AnalyticsScope.Launch(analytics.AnalyticsDispatcher, async () => await analytics.Store.Subscribe(this, state => RunningUpdate((System)state))); 25 | } 26 | 27 | public override RawEvent Execute(RawEvent incomingEvent) 28 | { 29 | if (!_running.Get() && incomingEvent != null) 30 | { 31 | Analytics.Logger.Log(LogLevel.Debug, message: "SegmentStartupQueue queueing event"); 32 | // The timeline hasn't started, we need to start queueing so we don't lose events 33 | if (_queuedEvents.Count >= s_maxSize) 34 | { 35 | // We've exceeded the max size and need to start dropping events 36 | _queuedEvents.TryDequeue(out _); 37 | } 38 | _queuedEvents.Enqueue(incomingEvent); 39 | return null; 40 | } 41 | // The timeline has started, just let the event pass on to the next plugin 42 | return incomingEvent; 43 | } 44 | 45 | private void RunningUpdate(System state) 46 | { 47 | Analytics.Logger.Log(LogLevel.Debug, message: "Analytics starting = " + state._running); 48 | _running.Set(state._running); 49 | if (_running.Get()) 50 | { 51 | ReplayEvents(); 52 | } 53 | } 54 | 55 | private void ReplayEvents() 56 | { 57 | while (!_queuedEvents.IsEmpty) 58 | { 59 | if (_queuedEvents.TryDequeue(out RawEvent e)) 60 | { 61 | Analytics.Process(e, e.Enrichment); 62 | } 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Analytics-CSharp/Segment/Analytics/Policies/CountFlushPolicy.cs: -------------------------------------------------------------------------------- 1 | namespace Segment.Analytics.Policies 2 | { 3 | /// 4 | /// A Count based Flush Policy that instructs the EventPipeline to flush at the 5 | /// given @param[flushAt]. The default value is 20. @param[flushAt] values should 6 | /// be >= 1 or they'll get the default value. 7 | /// 8 | public class CountFlushPolicy : IFlushPolicy 9 | { 10 | private int _flushAt; 11 | 12 | private int _count = 0; 13 | 14 | public int FlushAt 15 | { 16 | get => _flushAt; 17 | set { 18 | _flushAt = value >= 1 ? value : 20; 19 | } 20 | } 21 | 22 | public CountFlushPolicy(int flushAt = 20) 23 | { 24 | FlushAt = flushAt; 25 | } 26 | 27 | public bool ShouldFlush() 28 | { 29 | return _count >= _flushAt; 30 | } 31 | 32 | public void UpdateState(RawEvent @event) 33 | { 34 | _count++; 35 | } 36 | 37 | public void Reset() 38 | { 39 | _count = 0; 40 | } 41 | 42 | public void Schedule(Analytics analytics) {} 43 | 44 | public void Unschedule() {} 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Analytics-CSharp/Segment/Analytics/Policies/FrequencyFlushPolicy.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace Segment.Analytics.Policies 5 | { 6 | public class FrequencyFlushPolicy : IFlushPolicy 7 | { 8 | public long FlushIntervalInMills { get; set; } 9 | 10 | private bool _jobStarted = false; 11 | 12 | private CancellationTokenSource _cts = null; 13 | 14 | 15 | public FrequencyFlushPolicy(long flushIntervalInMills = 30 * 1000) 16 | { 17 | FlushIntervalInMills = flushIntervalInMills; 18 | } 19 | 20 | public void Schedule(Analytics analytics) 21 | { 22 | if (_jobStarted) return; 23 | _jobStarted = true; 24 | _cts = new CancellationTokenSource(); 25 | 26 | analytics.AnalyticsScope.Launch(analytics.FileIODispatcher, async () => 27 | { 28 | if (FlushIntervalInMills > 0) 29 | { 30 | while (!_cts.IsCancellationRequested) 31 | { 32 | analytics.Flush(); 33 | 34 | // use delay to do periodical task 35 | // this is doable in coroutine, since delay only suspends, allowing thread to 36 | // do other work and then come back. 37 | await Task.Delay((int)FlushIntervalInMills); 38 | } 39 | } 40 | }); 41 | } 42 | 43 | public void Unschedule() 44 | { 45 | if (!_jobStarted) 46 | { 47 | return; 48 | } 49 | 50 | _jobStarted = false; 51 | _cts?.Cancel(); 52 | } 53 | 54 | public bool ShouldFlush() => false; // Always return false; Scheduler will call flush. 55 | 56 | public void UpdateState(RawEvent @event) {} 57 | 58 | public void Reset() {} 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Analytics-CSharp/Segment/Analytics/Policies/IFlushPolicy.cs: -------------------------------------------------------------------------------- 1 | namespace Segment.Analytics.Policies 2 | { 3 | public interface IFlushPolicy 4 | { 5 | /// 6 | /// Called when the policy becomes active. We should start any periodic flushing 7 | /// we want here. 8 | /// 9 | /// 10 | void Schedule(Analytics analytics); 11 | 12 | /// 13 | /// Called when policy should stop running any scheduled flushes 14 | /// 15 | void Unschedule(); 16 | 17 | /// 18 | /// Called to check whether or not the events should be flushed. 19 | /// 20 | /// 21 | bool ShouldFlush(); 22 | 23 | /// 24 | /// Called as events are added to the timeline and JSON Stringified. 25 | /// 26 | /// 27 | void UpdateState(RawEvent @event); 28 | 29 | /// 30 | /// Called after the events are flushed. 31 | /// 32 | void Reset(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Analytics-CSharp/Segment/Analytics/Policies/StartupFlushPolicy.cs: -------------------------------------------------------------------------------- 1 | namespace Segment.Analytics.Policies 2 | { 3 | /// 4 | /// Flush policy that dictates flushing events at app startup. 5 | /// 6 | public class StartupFlushPolicy : IFlushPolicy 7 | { 8 | private bool _flushed = false; 9 | 10 | public bool ShouldFlush() 11 | { 12 | if (!_flushed) 13 | { 14 | _flushed = true; 15 | return true; 16 | } 17 | else return false; 18 | } 19 | 20 | public void Schedule(Analytics analytics) {} 21 | 22 | public void Unschedule() {} 23 | 24 | public void UpdateState(RawEvent @event) {} 25 | 26 | public void Reset() {} 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Analytics-CSharp/Segment/Analytics/Settings.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using global::System.Threading.Tasks; 3 | using Segment.Analytics.Utilities; 4 | using Segment.Concurrent; 5 | using Segment.Serialization; 6 | 7 | namespace Segment.Analytics 8 | { 9 | public struct Settings 10 | { 11 | // public Json integrations; 12 | public JsonObject Integrations { get; set; } 13 | public JsonObject Plan { get; set; } 14 | public JsonObject EdgeFunctions { get; set; } 15 | } 16 | 17 | public partial class Analytics 18 | { 19 | internal async Task Update(Settings settings) { 20 | System systemState = await Store.CurrentState(); 21 | HashSet initializedPlugins = new HashSet(); 22 | Timeline.Apply(plugin => { 23 | UpdateType type = systemState._initializedPlugins.Contains(plugin.GetHashCode()) ? UpdateType.Refresh : UpdateType.Initial; 24 | plugin.Update(settings, type); 25 | initializedPlugins.Add(plugin.GetHashCode()); 26 | }); 27 | await Store.Dispatch(new System.AddInitializedPluginAction(initializedPlugins)); 28 | } 29 | 30 | internal async Task CheckSettings() 31 | { 32 | HTTPClient httpClient = Configuration.HttpClientProvider.CreateHTTPClient(Configuration.WriteKey, cdnHost: Configuration.CdnHost); 33 | httpClient.AnalyticsRef = this; 34 | System systemState = await Store.CurrentState(); 35 | 36 | await Store.Dispatch(new System.ToggleRunningAction(false)); 37 | Settings? settings = null; 38 | await Scope.WithContext(NetworkIODispatcher, async () => 39 | { 40 | settings = await httpClient.Settings(); 41 | }); 42 | 43 | if (settings != null) 44 | { 45 | await Store.Dispatch(new System.UpdateSettingsAction(settings.Value)); 46 | } 47 | else 48 | { 49 | settings = systemState._settings; 50 | } 51 | 52 | await Update(settings.Value); 53 | await Store.Dispatch(new System.ToggleRunningAction(true)); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Analytics-CSharp/Segment/Analytics/Types.cs: -------------------------------------------------------------------------------- 1 | using global::System; 2 | using Segment.Serialization; 3 | 4 | #if NETSTANDARD2_0 5 | using System.Text.Json.Serialization; 6 | #else 7 | using Newtonsoft.Json; 8 | #endif 9 | 10 | namespace Segment.Analytics 11 | { 12 | public class DestinationMetadata 13 | { 14 | public JsonArray Bundled { get; set; } 15 | public JsonArray Unbundled { get; set; } 16 | public JsonArray BundledIds { get; set; } 17 | } 18 | 19 | public abstract class RawEvent 20 | { 21 | public virtual string Type { get; set; } 22 | public virtual string AnonymousId { get; set; } 23 | public virtual string MessageId { get; set; } 24 | public virtual string UserId { get; set; } 25 | public virtual string Timestamp { get; set; } 26 | 27 | [JsonIgnore] 28 | public Func Enrichment { get; set; } 29 | 30 | // JSON types 31 | public JsonObject Context { get; set; } 32 | public JsonObject Integrations { get; set; } 33 | 34 | public JsonArray Metrics { get; set; } 35 | 36 | public DestinationMetadata _metadata { get; set; } 37 | 38 | internal void ApplyRawEventData(RawEvent rawEvent) 39 | { 40 | AnonymousId = rawEvent.AnonymousId; 41 | MessageId = rawEvent.MessageId; 42 | UserId = rawEvent.UserId; 43 | Timestamp = rawEvent.Timestamp; 44 | Context = rawEvent.Context; 45 | Integrations = rawEvent.Integrations; 46 | } 47 | 48 | internal void ApplyRawEventData(UserInfo userInfo, Func enrichment) 49 | { 50 | Enrichment = enrichment; 51 | MessageId = Guid.NewGuid().ToString(); 52 | Context = new JsonObject(); 53 | Timestamp = DateTime.UtcNow.ToString("o"); // iso8601 54 | Integrations = new JsonObject(); 55 | 56 | // attach the latest in-memory copy of anonId and userId if not present 57 | if (string.IsNullOrEmpty(AnonymousId)) 58 | { 59 | AnonymousId = userInfo._anonymousId; 60 | } 61 | if (string.IsNullOrEmpty(UserId)) 62 | { 63 | UserId = userInfo._userId; 64 | } 65 | } 66 | } 67 | 68 | public sealed class TrackEvent : RawEvent 69 | { 70 | public override string Type => "track"; 71 | 72 | public string Event { get; set; } 73 | 74 | public JsonObject Properties { get; set; } 75 | 76 | internal TrackEvent(string trackEvent, JsonObject properties) 77 | { 78 | Event = trackEvent; 79 | Properties = properties; 80 | } 81 | 82 | internal TrackEvent(TrackEvent existing) : this(existing.Event, existing.Properties) => ApplyRawEventData(existing); 83 | } 84 | 85 | public sealed class IdentifyEvent : RawEvent 86 | { 87 | public override string Type => "identify"; 88 | 89 | public JsonObject Traits { get; set; } 90 | 91 | internal IdentifyEvent(string userId = null, JsonObject traits = null) 92 | { 93 | UserId = userId; 94 | Traits = traits; 95 | } 96 | 97 | internal IdentifyEvent(IdentifyEvent existing) => ApplyRawEventData(existing); 98 | } 99 | 100 | public sealed class ScreenEvent : RawEvent 101 | { 102 | public override string Type => "screen"; 103 | 104 | public string Name { get; set; } 105 | 106 | public string Category { get; set; } 107 | 108 | public JsonObject Properties { get; set; } 109 | 110 | internal ScreenEvent(string category, string title = null, JsonObject properties = null) 111 | { 112 | Name = title; 113 | Properties = properties; 114 | Category = category; 115 | } 116 | 117 | internal ScreenEvent(ScreenEvent existing) : this(existing.Category, existing.Name, existing.Properties) => ApplyRawEventData(existing); 118 | } 119 | 120 | public sealed class PageEvent : RawEvent 121 | { 122 | public override string Type => "page"; 123 | 124 | public string Name { get; set; } 125 | 126 | public string Category { get; set; } 127 | 128 | public JsonObject Properties { get; set; } 129 | 130 | internal PageEvent(string category, string title = null, JsonObject properties = null) 131 | { 132 | Name = title; 133 | Properties = properties; 134 | Category = category; 135 | } 136 | 137 | internal PageEvent(PageEvent existing) : this(existing.Category, existing.Name, existing.Properties) => ApplyRawEventData(existing); 138 | } 139 | 140 | public sealed class GroupEvent : RawEvent 141 | { 142 | public override string Type => "group"; 143 | 144 | public string GroupId { get; set; } 145 | 146 | public JsonObject Traits { get; set; } 147 | 148 | internal GroupEvent(string groupId = null, JsonObject traits = null) 149 | { 150 | GroupId = groupId; 151 | Traits = traits; 152 | } 153 | 154 | internal GroupEvent(GroupEvent existing) : this(existing.GroupId, existing.Traits) => ApplyRawEventData(existing); 155 | } 156 | 157 | public sealed class AliasEvent : RawEvent 158 | { 159 | public override string Type => "alias"; 160 | 161 | public string PreviousId { get; set; } 162 | 163 | internal AliasEvent(string newId, string previousId) 164 | { 165 | UserId = newId; 166 | PreviousId = previousId; 167 | } 168 | 169 | internal AliasEvent(AliasEvent existing) => ApplyRawEventData(existing); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /Analytics-CSharp/Segment/Analytics/Utilities/EventPipelineProvider.cs: -------------------------------------------------------------------------------- 1 | namespace Segment.Analytics.Utilities 2 | { 3 | public class EventPipelineProvider:IEventPipelineProvider 4 | { 5 | public EventPipelineProvider() 6 | { 7 | } 8 | 9 | public IEventPipeline Create(Analytics analytics, string key) 10 | { 11 | return new EventPipeline(analytics, key, 12 | analytics.Configuration.WriteKey, 13 | analytics.Configuration.FlushPolicies, 14 | analytics.Configuration.ApiHost); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Analytics-CSharp/Segment/Analytics/Utilities/ExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using global::System.Text; 2 | 3 | namespace Segment.Analytics.Utilities 4 | { 5 | public static class ExtensionMethods 6 | { 7 | public static byte[] GetBytes(this string str) => Encoding.UTF8.GetBytes(str); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Analytics-CSharp/Segment/Analytics/Utilities/IEventPipeline.cs: -------------------------------------------------------------------------------- 1 | namespace Segment.Analytics.Utilities 2 | { 3 | public interface IEventPipeline 4 | { 5 | bool Running { get; } 6 | string ApiHost { get; set; } 7 | 8 | void Put(RawEvent @event); 9 | void Flush(); 10 | void Start(); 11 | void Stop(); 12 | } 13 | } -------------------------------------------------------------------------------- /Analytics-CSharp/Segment/Analytics/Utilities/IEventPipelineProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Segment.Analytics.Utilities 4 | { 5 | public interface IEventPipelineProvider 6 | { 7 | IEventPipeline Create(Analytics analytics, string key); 8 | } 9 | } -------------------------------------------------------------------------------- /Analytics-CSharp/Segment/Analytics/Utilities/Logging.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Segment.Analytics.Utilities 4 | { 5 | public interface ISegmentLogger 6 | { 7 | void Log(LogLevel logLevel, Exception exception = null, string message = null); 8 | } 9 | 10 | public enum LogLevel 11 | { 12 | Trace, Debug, Information, Warning, Error, Critical, None 13 | } 14 | 15 | internal class StubLogger : ISegmentLogger 16 | { 17 | public void Log(LogLevel logLevel, Exception exception = null, string message = null) {} 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Analytics-CSharp/Segment/Analytics/Utilities/SyncEventPipelineProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | 3 | namespace Segment.Analytics.Utilities 4 | { 5 | public class SyncEventPipelineProvider: IEventPipelineProvider 6 | { 7 | internal int _flushTimeout = -1; 8 | internal CancellationToken? _flushCancellationToken = null; 9 | 10 | public SyncEventPipelineProvider( 11 | int flushTimeout = -1, 12 | CancellationToken? flushCancellationToken = null) 13 | { 14 | _flushTimeout = flushTimeout; 15 | _flushCancellationToken = flushCancellationToken; 16 | } 17 | 18 | public IEventPipeline Create(Analytics analytics, string key) 19 | { 20 | return new SyncEventPipeline(analytics, key, 21 | analytics.Configuration.WriteKey, 22 | analytics.Configuration.FlushPolicies, 23 | analytics.Configuration.ApiHost, 24 | _flushTimeout, 25 | _flushCancellationToken); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /Analytics-CSharp/Segment/Analytics/Utilities/SystemInfo.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | using System.Reflection; 4 | using global::System; 5 | using global::System.Runtime.InteropServices; 6 | 7 | namespace Segment.Analytics.Utilities 8 | { 9 | public static class SystemInfo 10 | { 11 | public static string GetAppFolder() 12 | { 13 | var type = Type.GetType("UnityEngine.Application, UnityEngine.CoreModule"); 14 | string unityPath = type?.GetRuntimeProperty("persistentDataPath")?.GetValue(null, null).ToString(); 15 | 16 | if (unityPath != null) return unityPath; 17 | 18 | #if NETSTANDARD2_0 19 | return Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); 20 | #else 21 | return Directory.GetCurrentDirectory(); 22 | #endif 23 | } 24 | 25 | public static string GetPlatform() 26 | { 27 | string type = ""; 28 | 29 | if (Type.GetType("UnityEngine.Application, UnityEngine.CoreModule") != null) 30 | { 31 | type = "Unity"; 32 | } 33 | else if (AssemblyExists("Xamarin.Forms.Core")) 34 | { 35 | type = "Xamarin"; 36 | } 37 | else 38 | { 39 | string descr = RuntimeInformation.FrameworkDescription; 40 | string platf = descr.Substring(0, descr.LastIndexOf(' ')); 41 | 42 | type = platf; 43 | } 44 | return type; 45 | } 46 | 47 | public static string GetOs() 48 | { 49 | return RuntimeInformation.OSDescription; 50 | } 51 | 52 | public static bool AssemblyExists(string assembly) 53 | { 54 | #if NETSTANDARD2_0 55 | return AppDomain.CurrentDomain.GetAssemblies().Where(a => a.ToString().StartsWith(assembly)).Count() > 0; 56 | #else 57 | try 58 | { 59 | Assembly.Load(new AssemblyName(assembly)); 60 | return true; 61 | } 62 | catch (Exception) 63 | { 64 | return false; 65 | } 66 | #endif 67 | } 68 | } 69 | } 70 | 71 | -------------------------------------------------------------------------------- /Analytics-CSharp/Segment/Analytics/Version.cs: -------------------------------------------------------------------------------- 1 | namespace Segment.Analytics 2 | { 3 | internal static class Version 4 | { 5 | internal const string SegmentVersion = "2.5.3"; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We want this community to be friendly and respectful to each other. Please follow it in all your interactions with the project. 4 | 5 | 6 | ### Commit message convention 7 | 8 | We follow the [conventional commits specification](https://www.conventionalcommits.org/en) for our commit messages: 9 | 10 | - `fix`: bug fixes, e.g. fix crash due to deprecated method. 11 | - `feat`: new features, e.g. add new method to the module. 12 | - `refactor`: code refactor, e.g. migrate from class components to hooks. 13 | - `docs`: changes into documentation, e.g. add usage example for the module.. 14 | - `test`: adding or updating tests, eg add integration tests using detox. 15 | - `chore`: tooling changes, e.g. change CI config. 16 | 17 | Our pre-commit hooks verify that your commit message matches this format when committing. 18 | 19 | 20 | ### Sending a pull request 21 | 22 | > **Working on your first pull request?** You can learn how from this _free_ series: [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github). 23 | 24 | When you're sending a pull request: 25 | 26 | - Prefer small pull requests focused on one change. 27 | - Verify that linters and tests are passing. 28 | - Review the documentation to make sure it looks good. 29 | - Follow the pull request template when opening a pull request. 30 | - For pull requests that change the API or implementation, discuss with maintainers first by opening an issue. 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Segment 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 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | Update Version 2 | ========== 3 | * update `` value in `Analytics-CSharp.csproj` 4 | * update `SegmentVersion` value in `Segment/Analytics/Version.cs` 5 | 6 | Release to Nuget 7 | ========== 8 | 1. Create a new branch called `release/X.Y.Z` 9 | 2. `git checkout -b release/X.Y.Z` 10 | 3. Change the version in `Analytics-CSharp.csproj` to your desired release version (see `Update Version`) 11 | 4. `git commit -am "Create release X.Y.Z."` (where X.Y.Z is the new version) 12 | 5. `git tag -a X.Y.Z -m "Version X.Y.Z"` (where X.Y.Z is the new version) 13 | 6. The CI pipeline will recognize the tag and upload the artifacts to nuget and generate changelog automatically 14 | 7. Push to github with `git push && git push --tags` 15 | 8. Create a PR to merge to main 16 | 17 | Release to OpenUPM 18 | ========== 19 | follow the instruction above to `Release to Nuget`. once the new version is available in Nuget and PR merged to main, run the following command in the root of the project: 20 | ```bash 21 | sh upm_release.sh 22 | ``` 23 | NOTE: `` is a required folder to setup sandbox for release. it should be **outside** the project folder. 24 | 25 | the script will setup a sandbox to pack the artifacts and create a `unity/` tag on github. OpenUPM checks the `unity/` tag periodically and create a release automatically. 26 | 27 | Pre-release 28 | ========== 29 | Pre-release is useful when testing code compatibility on Unity. To make a pre-release, update the version tag with a suffix of `-alpha.` where `` is the version number of this alpha release. The following is a list of valid pre-release versions: 30 | * `2.0.0-alpha.1` 31 | * `2.0.0-alpha.2` 32 | * `2.0.0-alpha.12` 33 | 34 | The rest of the pre-release progress is the same as a regular release. 35 | -------------------------------------------------------------------------------- /Samples/AspNetMvcSample/AspNetMvcSample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Samples/AspNetMvcSample/Controllers/AnalyticsController.cs: -------------------------------------------------------------------------------- 1 | using System.Dynamic; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Segment.Analytics; 4 | 5 | namespace AspNetMvcSample.Controllers 6 | { 7 | public class AnalyticsController : Controller 8 | { 9 | public readonly Analytics analytics; 10 | 11 | public dynamic viewModel = new ExpandoObject(); 12 | 13 | public AnalyticsController(Analytics analytics) 14 | { 15 | this.analytics = analytics; 16 | viewModel.analytics = this.analytics; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Samples/AspNetMvcSample/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.Extensions.Logging; 8 | using AspNetMvcSample.Models; 9 | using AspNetMvcSample.Services; 10 | using Segment.Analytics; 11 | 12 | namespace AspNetMvcSample.Controllers 13 | { 14 | public class HomeController : AnalyticsController 15 | { 16 | public HomeController(Analytics analytics) : base(analytics) 17 | { 18 | } 19 | 20 | public IActionResult Index() 21 | { 22 | return View(viewModel); 23 | } 24 | 25 | public IActionResult Pizza() 26 | { 27 | return RedirectToAction("Index", "Pizza"); 28 | } 29 | 30 | public IActionResult Privacy() 31 | { 32 | return View(viewModel); 33 | } 34 | 35 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 36 | public IActionResult Error() 37 | { 38 | viewModel.ErrorViewModel = 39 | new ErrorViewModel {RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier}; 40 | return View(viewModel); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Samples/AspNetMvcSample/Controllers/PizzaController.cs: -------------------------------------------------------------------------------- 1 | using AspNetMvcSample.Models; 2 | using AspNetMvcSample.Services; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Segment.Analytics; 6 | using Segment.Serialization; 7 | 8 | namespace AspNetMvcSample.Controllers 9 | { 10 | public class PizzaController : AnalyticsController 11 | { 12 | public PizzaController(Analytics analytics) : base(analytics) 13 | { 14 | } 15 | 16 | public IActionResult Index() 17 | { 18 | viewModel.pizzas = PizzaService.GetAll(); 19 | return View(viewModel); 20 | } 21 | 22 | [HttpPost] 23 | public IActionResult OnPost(Pizza pizza) 24 | { 25 | if (!ModelState.IsValid) 26 | { 27 | return RedirectToAction("Index"); 28 | } 29 | PizzaService.Add(pizza); 30 | analytics.Track("New Pizza Added", new JsonObject 31 | { 32 | ["id"] = pizza.Id, 33 | ["isGlutenFree"] = pizza.IsGlutenFree, 34 | ["name"] = pizza.Name, 35 | ["price"] = (double)pizza.Price, 36 | ["size"] = (int)pizza.Size 37 | }); 38 | 39 | return RedirectToAction("Index"); 40 | } 41 | 42 | public IActionResult OnPostDelete(int id) 43 | { 44 | PizzaService.Delete(id); 45 | analytics.Track("Pizza Deleted", new JsonObject 46 | { 47 | ["id"] = id 48 | }); 49 | return RedirectToAction("Index"); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Samples/AspNetMvcSample/Models/ErrorViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace AspNetMvcSample.Models 4 | { 5 | public class ErrorViewModel 6 | { 7 | public string RequestId { get; set; } 8 | 9 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Samples/AspNetMvcSample/Models/Pizza.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace AspNetMvcSample.Models 4 | { 5 | public class Pizza 6 | { 7 | public int Id { get; set; } 8 | 9 | [Required] 10 | public string Name { get; set; } 11 | public PizzaSize Size { get; set; } 12 | public bool IsGlutenFree { get; set; } 13 | 14 | [Range(0.01, 9999.99)] 15 | public decimal Price { get; set; } 16 | 17 | public string GlutenFreeText => IsGlutenFree ? "Gluten Free": "Not Gluten Free"; 18 | } 19 | 20 | public enum PizzaSize { Small, Medium, Large } 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /Samples/AspNetMvcSample/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.Hosting; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace AspNetMvcSample 11 | { 12 | public class Program 13 | { 14 | public static void Main(string[] args) 15 | { 16 | CreateHostBuilder(args).Build().Run(); 17 | } 18 | 19 | public static IHostBuilder CreateHostBuilder(string[] args) => 20 | Host.CreateDefaultBuilder(args) 21 | .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Samples/AspNetMvcSample/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:42525", 7 | "sslPort": 44341 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "AspNetMvcSample": { 19 | "commandName": "Project", 20 | "dotnetRunMessages": "true", 21 | "launchBrowser": true, 22 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Samples/AspNetMvcSample/Services/PizzaService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using AspNetMvcSample.Models; 4 | 5 | namespace AspNetMvcSample.Services 6 | { 7 | public static class PizzaService 8 | { 9 | static List Pizzas { get; } 10 | static int nextId = 3; 11 | static PizzaService() 12 | { 13 | Pizzas = new List 14 | { 15 | new Pizza { Id = 1, Name = "Classic Italian", Price=20.00M, Size=PizzaSize.Large, IsGlutenFree = false }, 16 | new Pizza { Id = 2, Name = "Veggie", Price=15.00M, Size=PizzaSize.Small, IsGlutenFree = true } 17 | }; 18 | } 19 | 20 | public static List GetAll() => Pizzas; 21 | 22 | public static Pizza? Get(int id) => Pizzas.FirstOrDefault(p => p.Id == id); 23 | 24 | public static void Add(Pizza pizza) 25 | { 26 | pizza.Id = nextId++; 27 | Pizzas.Add(pizza); 28 | } 29 | 30 | public static void Delete(int id) 31 | { 32 | var pizza = Get(id); 33 | if (pizza is null) 34 | return; 35 | 36 | Pizzas.Remove(pizza); 37 | } 38 | 39 | public static void Update(Pizza pizza) 40 | { 41 | var index = Pizzas.FindIndex(p => p.Id == pizza.Id); 42 | if (index == -1) 43 | return; 44 | 45 | Pizzas[index] = pizza; 46 | } 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Samples/AspNetMvcSample/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.HttpsPolicy; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.DependencyInjection; 10 | using Microsoft.Extensions.Hosting; 11 | using Segment.Analytics; 12 | using Segment.Analytics.Utilities; 13 | 14 | namespace AspNetMvcSample 15 | { 16 | public class Startup 17 | { 18 | public Startup(IConfiguration configuration) 19 | { 20 | Configuration = configuration; 21 | } 22 | 23 | public IConfiguration Configuration { get; } 24 | 25 | // This method gets called by the runtime. Use this method to add services to the container. 26 | public void ConfigureServices(IServiceCollection services) 27 | { 28 | // use `InMemoryStorageProvider` to make Analytics stateless 29 | var configuration = new Configuration("YOUR WRITE KEY", 30 | flushAt: 1, 31 | flushInterval: 10, 32 | storageProvider: new InMemoryStorageProvider()); 33 | 34 | services.AddControllersWithViews(); 35 | services.AddScoped(_ => new Analytics(configuration)); 36 | } 37 | 38 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 39 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 40 | { 41 | if (env.IsDevelopment()) 42 | { 43 | app.UseDeveloperExceptionPage(); 44 | } 45 | else 46 | { 47 | app.UseExceptionHandler("/Home/Error"); 48 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 49 | app.UseHsts(); 50 | } 51 | 52 | app.UseHttpsRedirection(); 53 | app.UseStaticFiles(); 54 | 55 | app.UseRouting(); 56 | 57 | app.UseAuthorization(); 58 | 59 | app.UseEndpoints(endpoints => 60 | { 61 | endpoints.MapControllerRoute( 62 | name: "default", 63 | pattern: "{controller=Home}/{action=Index}/{id?}"); 64 | }); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Samples/AspNetMvcSample/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Home Page"; 3 | } 4 | 5 |
6 |

Welcome

7 |

Learn about building Web apps with ASP.NET Core.

8 |
9 | -------------------------------------------------------------------------------- /Samples/AspNetMvcSample/Views/Home/Privacy.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Privacy Policy"; 3 | } 4 |

@ViewData["Title"]

5 | 6 |

Use this page to detail your site's privacy policy.

7 | -------------------------------------------------------------------------------- /Samples/AspNetMvcSample/Views/Pizza/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model dynamic 2 | @{ 3 | ViewData["Title"] = "Pizza"; 4 | } 5 | 6 |

Pizza List 🍕

7 |
8 |
9 |
10 |
11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | @Html.CheckBox("IsGlutenFree", false, new { @class="mr-5" }) 20 | 21 |
22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | @foreach (var pizza in Model.pizzas) 34 | { 35 | 36 | 37 | 38 | 39 | 40 | 45 | 46 | } 47 |
NamePriceSizeGluten FreeDelete
@pizza.Name@($"{pizza.Price:C}")@pizza.Size@pizza.GlutenFreeText 41 |
42 | 43 |
44 |
48 | 49 | 50 | @section Scripts { 51 | 52 | } 53 | -------------------------------------------------------------------------------- /Samples/AspNetMvcSample/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @model ErrorViewModel 2 | @{ 3 | ViewData["Title"] = "Error"; 4 | } 5 | 6 |

Error.

7 |

An error occurred while processing your request.

8 | 9 | @if (Model.ShowRequestId) 10 | { 11 |

12 | Request ID: @Model.RequestId 13 |

14 | } 15 | 16 |

Development Mode

17 |

18 | Swapping to Development environment will display more detailed information about the error that occurred. 19 |

20 |

21 | The Development environment shouldn't be enabled for deployed applications. 22 | It can result in displaying sensitive information from exceptions to end users. 23 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development 24 | and restarting the app. 25 |

26 | -------------------------------------------------------------------------------- /Samples/AspNetMvcSample/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | @model dynamic 2 | @{ 3 | Model.analytics?.Screen(ViewData["Title"]?.ToString()); 4 | } 5 | 6 | 7 | 8 | 9 | 10 | 11 | @ViewData["Title"] - AspNetMvcSample 12 | 13 | 14 | 15 | 16 |
17 | 39 |
40 |
41 |
42 | @RenderBody() 43 |
44 |
45 | 46 |
47 |
48 | © 2023 - AspNetMvcSample - Privacy 49 |
50 |
51 | 52 | 53 | 54 | @await RenderSectionAsync("Scripts", required: false) 55 | 56 | 57 | -------------------------------------------------------------------------------- /Samples/AspNetMvcSample/Views/Shared/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | -------------------------------------------------------------------------------- /Samples/AspNetMvcSample/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using AspNetMvcSample 2 | @using AspNetMvcSample.Models 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | -------------------------------------------------------------------------------- /Samples/AspNetMvcSample/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /Samples/AspNetMvcSample/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Samples/AspNetMvcSample/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /Samples/AspNetMvcSample/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | /* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification 2 | for details on configuring this project to bundle and minify static web assets. */ 3 | 4 | a.navbar-brand { 5 | white-space: normal; 6 | text-align: center; 7 | word-break: break-all; 8 | } 9 | 10 | /* Provide sufficient contrast against white background */ 11 | a { 12 | color: #0366d6; 13 | } 14 | 15 | .btn-primary { 16 | color: #fff; 17 | background-color: #1b6ec2; 18 | border-color: #1861ac; 19 | } 20 | 21 | .nav-pills .nav-link.active, .nav-pills .show > .nav-link { 22 | color: #fff; 23 | background-color: #1b6ec2; 24 | border-color: #1861ac; 25 | } 26 | 27 | /* Sticky footer styles 28 | -------------------------------------------------- */ 29 | html { 30 | font-size: 14px; 31 | } 32 | @media (min-width: 768px) { 33 | html { 34 | font-size: 16px; 35 | } 36 | } 37 | 38 | .border-top { 39 | border-top: 1px solid #e5e5e5; 40 | } 41 | .border-bottom { 42 | border-bottom: 1px solid #e5e5e5; 43 | } 44 | 45 | .box-shadow { 46 | box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); 47 | } 48 | 49 | button.accept-policy { 50 | font-size: 1rem; 51 | line-height: inherit; 52 | } 53 | 54 | /* Sticky footer styles 55 | -------------------------------------------------- */ 56 | html { 57 | position: relative; 58 | min-height: 100%; 59 | } 60 | 61 | body { 62 | /* Margin bottom by footer height */ 63 | margin-bottom: 60px; 64 | } 65 | .footer { 66 | position: absolute; 67 | bottom: 0; 68 | width: 100%; 69 | white-space: nowrap; 70 | line-height: 60px; /* Vertically center the text there */ 71 | } 72 | -------------------------------------------------------------------------------- /Samples/AspNetMvcSample/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/segmentio/Analytics-CSharp/27e5464d538d1e57ae01622f4041912ed47a9dbc/Samples/AspNetMvcSample/wwwroot/favicon.ico -------------------------------------------------------------------------------- /Samples/AspNetMvcSample/wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | // Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification 2 | // for details on configuring this project to bundle and minify static web assets. 3 | 4 | // Write your JavaScript code. 5 | -------------------------------------------------------------------------------- /Samples/AspNetMvcSample/wwwroot/lib/bootstrap/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2018 Twitter, Inc. 4 | Copyright (c) 2011-2018 The Bootstrap Authors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Samples/AspNetMvcSample/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v4.3.1 (https://getbootstrap.com/) 3 | * Copyright 2011-2019 The Bootstrap Authors 4 | * Copyright 2011-2019 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important} 8 | /*# sourceMappingURL=bootstrap-reboot.min.css.map */ -------------------------------------------------------------------------------- /Samples/AspNetMvcSample/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) .NET Foundation. All rights reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 4 | these files except in compliance with the License. You may obtain a copy of the 5 | License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software distributed 10 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 12 | specific language governing permissions and limitations under the License. 13 | -------------------------------------------------------------------------------- /Samples/AspNetMvcSample/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js: -------------------------------------------------------------------------------- 1 | // Unobtrusive validation support library for jQuery and jQuery Validate 2 | // Copyright (c) .NET Foundation. All rights reserved. 3 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 4 | // @version v3.2.11 5 | !function(a){"function"==typeof define&&define.amd?define("jquery.validate.unobtrusive",["jquery-validation"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery-validation")):jQuery.validator.unobtrusive=a(jQuery)}(function(a){function e(a,e,n){a.rules[e]=n,a.message&&(a.messages[e]=a.message)}function n(a){return a.replace(/^\s+|\s+$/g,"").split(/\s*,\s*/g)}function t(a){return a.replace(/([!"#$%&'()*+,.\/:;<=>?@\[\\\]^`{|}~])/g,"\\$1")}function r(a){return a.substr(0,a.lastIndexOf(".")+1)}function i(a,e){return 0===a.indexOf("*.")&&(a=a.replace("*.",e)),a}function o(e,n){var r=a(this).find("[data-valmsg-for='"+t(n[0].name)+"']"),i=r.attr("data-valmsg-replace"),o=i?a.parseJSON(i)!==!1:null;r.removeClass("field-validation-valid").addClass("field-validation-error"),e.data("unobtrusiveContainer",r),o?(r.empty(),e.removeClass("input-validation-error").appendTo(r)):e.hide()}function d(e,n){var t=a(this).find("[data-valmsg-summary=true]"),r=t.find("ul");r&&r.length&&n.errorList.length&&(r.empty(),t.addClass("validation-summary-errors").removeClass("validation-summary-valid"),a.each(n.errorList,function(){a("
  • ").html(this.message).appendTo(r)}))}function s(e){var n=e.data("unobtrusiveContainer");if(n){var t=n.attr("data-valmsg-replace"),r=t?a.parseJSON(t):null;n.addClass("field-validation-valid").removeClass("field-validation-error"),e.removeData("unobtrusiveContainer"),r&&n.empty()}}function l(e){var n=a(this),t="__jquery_unobtrusive_validation_form_reset";if(!n.data(t)){n.data(t,!0);try{n.data("validator").resetForm()}finally{n.removeData(t)}n.find(".validation-summary-errors").addClass("validation-summary-valid").removeClass("validation-summary-errors"),n.find(".field-validation-error").addClass("field-validation-valid").removeClass("field-validation-error").removeData("unobtrusiveContainer").find(">*").removeData("unobtrusiveContainer")}}function u(e){var n=a(e),t=n.data(v),r=a.proxy(l,e),i=f.unobtrusive.options||{},u=function(n,t){var r=i[n];r&&a.isFunction(r)&&r.apply(e,t)};return t||(t={options:{errorClass:i.errorClass||"input-validation-error",errorElement:i.errorElement||"span",errorPlacement:function(){o.apply(e,arguments),u("errorPlacement",arguments)},invalidHandler:function(){d.apply(e,arguments),u("invalidHandler",arguments)},messages:{},rules:{},success:function(){s.apply(e,arguments),u("success",arguments)}},attachValidation:function(){n.off("reset."+v,r).on("reset."+v,r).validate(this.options)},validate:function(){return n.validate(),n.valid()}},n.data(v,t)),t}var m,f=a.validator,v="unobtrusiveValidation";return f.unobtrusive={adapters:[],parseElement:function(e,n){var t,r,i,o=a(e),d=o.parents("form")[0];d&&(t=u(d),t.options.rules[e.name]=r={},t.options.messages[e.name]=i={},a.each(this.adapters,function(){var n="data-val-"+this.name,t=o.attr(n),s={};void 0!==t&&(n+="-",a.each(this.params,function(){s[this]=o.attr(n+this)}),this.adapt({element:e,form:d,message:t,params:s,rules:r,messages:i}))}),a.extend(r,{__dummy__:!0}),n||t.attachValidation())},parse:function(e){var n=a(e),t=n.parents().addBack().filter("form").add(n.find("form")).has("[data-val=true]");n.find("[data-val=true]").each(function(){f.unobtrusive.parseElement(this,!0)}),t.each(function(){var a=u(this);a&&a.attachValidation()})}},m=f.unobtrusive.adapters,m.add=function(a,e,n){return n||(n=e,e=[]),this.push({name:a,params:e,adapt:n}),this},m.addBool=function(a,n){return this.add(a,function(t){e(t,n||a,!0)})},m.addMinMax=function(a,n,t,r,i,o){return this.add(a,[i||"min",o||"max"],function(a){var i=a.params.min,o=a.params.max;i&&o?e(a,r,[i,o]):i?e(a,n,i):o&&e(a,t,o)})},m.addSingleVal=function(a,n,t){return this.add(a,[n||"val"],function(r){e(r,t||a,r.params[n])})},f.addMethod("__dummy__",function(a,e,n){return!0}),f.addMethod("regex",function(a,e,n){var t;return!!this.optional(e)||(t=new RegExp(n).exec(a),t&&0===t.index&&t[0].length===a.length)}),f.addMethod("nonalphamin",function(a,e,n){var t;return n&&(t=a.match(/\W/g),t=t&&t.length>=n),t}),f.methods.extension?(m.addSingleVal("accept","mimtype"),m.addSingleVal("extension","extension")):m.addSingleVal("extension","extension","accept"),m.addSingleVal("regex","pattern"),m.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url"),m.addMinMax("length","minlength","maxlength","rangelength").addMinMax("range","min","max","range"),m.addMinMax("minlength","minlength").addMinMax("maxlength","minlength","maxlength"),m.add("equalto",["other"],function(n){var o=r(n.element.name),d=n.params.other,s=i(d,o),l=a(n.form).find(":input").filter("[name='"+t(s)+"']")[0];e(n,"equalTo",l)}),m.add("required",function(a){"INPUT"===a.element.tagName.toUpperCase()&&"CHECKBOX"===a.element.type.toUpperCase()||e(a,"required",!0)}),m.add("remote",["url","type","additionalfields"],function(o){var d={url:o.params.url,type:o.params.type||"GET",data:{}},s=r(o.element.name);a.each(n(o.params.additionalfields||o.element.name),function(e,n){var r=i(n,s);d.data[r]=function(){var e=a(o.form).find(":input").filter("[name='"+t(r)+"']");return e.is(":checkbox")?e.filter(":checked").val()||e.filter(":hidden").val()||"":e.is(":radio")?e.filter(":checked").val()||"":e.val()}}),e(o,"remote",d)}),m.add("password",["min","nonalphamin","regex"],function(a){a.params.min&&e(a,"minlength",a.params.min),a.params.nonalphamin&&e(a,"nonalphamin",a.params.nonalphamin),a.params.regex&&e(a,"regex",a.params.regex)}),m.add("fileextensions",["extensions"],function(a){e(a,"extension",a.params.extensions)}),a(function(){f.unobtrusive.parse(document)}),f.unobtrusive}); -------------------------------------------------------------------------------- /Samples/AspNetMvcSample/wwwroot/lib/jquery-validation/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright Jörn Zaefferer 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Samples/AspNetMvcSample/wwwroot/lib/jquery/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright JS Foundation and other contributors, https://js.foundation/ 2 | 3 | This software consists of voluntary contributions made by many 4 | individuals. For exact contribution history, see the revision history 5 | available at https://github.com/jquery/jquery 6 | 7 | The following license applies to all parts of this software except as 8 | documented below: 9 | 10 | ==== 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining 13 | a copy of this software and associated documentation files (the 14 | "Software"), to deal in the Software without restriction, including 15 | without limitation the rights to use, copy, modify, merge, publish, 16 | distribute, sublicense, and/or sell copies of the Software, and to 17 | permit persons to whom the Software is furnished to do so, subject to 18 | the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be 21 | included in all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30 | 31 | ==== 32 | 33 | All files located in the node_modules and external directories are 34 | externally maintained libraries used by this software which have their 35 | own licenses; we recommend you read them, as their terms may differ from 36 | the terms above. 37 | -------------------------------------------------------------------------------- /Samples/AspNetSample/AnalyticsPageModel.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.RazorPages; 2 | using Segment.Analytics; 3 | 4 | namespace AspNetSample 5 | { 6 | public class AnalyticsPageModel : PageModel 7 | { 8 | public readonly Analytics analytics; 9 | 10 | public AnalyticsPageModel(Analytics analytics) => this.analytics = analytics; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Samples/AspNetSample/AspNetSample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Samples/AspNetSample/Pages/Error.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ErrorModel 3 | @{ 4 | ViewData["Title"] = "Error"; 5 | } 6 | 7 |

    Error.

    8 |

    An error occurred while processing your request.

    9 | 10 | @if (Model.ShowRequestId) 11 | { 12 |

    13 | Request ID: @Model.RequestId 14 |

    15 | } 16 | 17 |

    Development Mode

    18 |

    19 | Swapping to the Development environment displays detailed information about the error that occurred. 20 |

    21 |

    22 | The Development environment shouldn't be enabled for deployed applications. 23 | It can result in displaying sensitive information from exceptions to end users. 24 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development 25 | and restarting the app. 26 |

    27 | -------------------------------------------------------------------------------- /Samples/AspNetSample/Pages/Error.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Mvc.RazorPages; 8 | using Microsoft.Extensions.Logging; 9 | using Segment.Analytics; 10 | 11 | namespace AspNetSample.Pages 12 | { 13 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 14 | [IgnoreAntiforgeryToken] 15 | public class ErrorModel : AnalyticsPageModel 16 | { 17 | public string RequestId { get; set; } 18 | 19 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 20 | 21 | public ErrorModel(Analytics analytics) : base(analytics) 22 | { 23 | } 24 | 25 | public void OnGet() 26 | { 27 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Samples/AspNetSample/Pages/Index.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model IndexModel 3 | @{ 4 | ViewData["Title"] = "Home page"; 5 | } 6 | 7 |
    8 |

    Welcome

    9 |

    Learn about building Web apps with ASP.NET Core.

    10 |
    11 | -------------------------------------------------------------------------------- /Samples/AspNetSample/Pages/Index.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.AspNetCore.Mvc.RazorPages; 7 | using Microsoft.Extensions.Logging; 8 | using Segment.Analytics; 9 | 10 | namespace AspNetSample.Pages 11 | { 12 | public class IndexModel : AnalyticsPageModel 13 | { 14 | public IndexModel(Analytics analytics) : base(analytics) 15 | { 16 | } 17 | 18 | public void OnGet() 19 | { 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Samples/AspNetSample/Pages/Models/Pizza.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace AspNetSample.Pages.Models 4 | { 5 | public class Pizza 6 | { 7 | public int Id { get; set; } 8 | 9 | [Required] 10 | public string Name { get; set; } 11 | public PizzaSize Size { get; set; } 12 | public bool IsGlutenFree { get; set; } 13 | 14 | [Range(0.01, 9999.99)] 15 | public decimal Price { get; set; } 16 | } 17 | 18 | public enum PizzaSize { Small, Medium, Large } 19 | 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Samples/AspNetSample/Pages/Pizza.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @using AspNetSample.Pages.Models 3 | @using Microsoft.AspNetCore.Mvc.TagHelpers 4 | @model AspNetSample.Pages.PizzaModel 5 | @{ 6 | ViewData["Title"] = "Pizza"; 7 | } 8 | 9 |

    Pizza List 🍕

    10 |
    11 |
    12 |
    13 |
    14 |
    15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
    25 |
    26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | @foreach (var pizza in Model.pizzas) 37 | { 38 | 39 | 40 | 41 | 42 | 43 | 48 | 49 | } 50 |
    NamePriceSizeGluten FreeDelete
    @pizza.Name@($"{pizza.Price:C}")@pizza.Size@Model.GlutenFreeText(pizza) 44 |
    45 | 46 |
    47 |
    51 | 52 | @section Scripts { 53 | 54 | } 55 | 56 | -------------------------------------------------------------------------------- /Samples/AspNetSample/Pages/Pizza.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using AspNetSample.Pages.Models; 6 | using AspNetSample.Pages.Services; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.AspNetCore.Mvc.RazorPages; 9 | using Segment.Analytics; 10 | using Segment.Serialization; 11 | 12 | namespace AspNetSample.Pages 13 | { 14 | public class PizzaModel : AnalyticsPageModel 15 | { 16 | [BindProperty] 17 | public Pizza NewPizza { get; set; } = new(); 18 | 19 | public List pizzas = new(); 20 | 21 | public PizzaModel(Analytics analytics) : base(analytics) 22 | { 23 | } 24 | 25 | public void OnGet() 26 | { 27 | pizzas = PizzaService.GetAll(); 28 | } 29 | 30 | public string GlutenFreeText(Pizza pizza) 31 | { 32 | return pizza.IsGlutenFree ? "Gluten Free": "Not Gluten Free"; 33 | } 34 | 35 | public IActionResult OnPost() 36 | { 37 | if (!ModelState.IsValid) 38 | { 39 | return Page(); 40 | } 41 | PizzaService.Add(NewPizza); 42 | analytics.Track("New Pizza Added", new JsonObject 43 | { 44 | ["id"] = NewPizza.Id, 45 | ["isGlutenFree"] = NewPizza.IsGlutenFree, 46 | ["name"] = NewPizza.Name, 47 | ["price"] = (double)NewPizza.Price, 48 | ["size"] = (int)NewPizza.Size 49 | }); 50 | return RedirectToAction("Get"); 51 | } 52 | 53 | public IActionResult OnPostDelete(int id) 54 | { 55 | PizzaService.Delete(id); 56 | analytics.Track("Pizza Deleted", new JsonObject 57 | { 58 | ["id"] = id 59 | }); 60 | return RedirectToAction("Get"); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Samples/AspNetSample/Pages/Privacy.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model PrivacyModel 3 | @{ 4 | ViewData["Title"] = "Privacy Policy"; 5 | } 6 |

    @ViewData["Title"]

    7 | 8 |

    Use this page to detail your site's privacy policy.

    9 | -------------------------------------------------------------------------------- /Samples/AspNetSample/Pages/Privacy.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.AspNetCore.Mvc.RazorPages; 7 | using Microsoft.Extensions.Logging; 8 | using Segment.Analytics; 9 | 10 | namespace AspNetSample.Pages 11 | { 12 | public class PrivacyModel : AnalyticsPageModel 13 | { 14 | public PrivacyModel(Analytics analytics) : base(analytics) 15 | { 16 | } 17 | 18 | 19 | public void OnGet() 20 | { 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Samples/AspNetSample/Pages/Services/PizzaService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using AspNetSample.Pages.Models; 4 | 5 | namespace AspNetSample.Pages.Services 6 | { 7 | public static class PizzaService 8 | { 9 | static List Pizzas { get; } 10 | static int nextId = 3; 11 | static PizzaService() 12 | { 13 | Pizzas = new List 14 | { 15 | new Pizza { Id = 1, Name = "Classic Italian", Price=20.00M, Size=PizzaSize.Large, IsGlutenFree = false }, 16 | new Pizza { Id = 2, Name = "Veggie", Price=15.00M, Size=PizzaSize.Small, IsGlutenFree = true } 17 | }; 18 | } 19 | 20 | public static List GetAll() => Pizzas; 21 | 22 | public static Pizza? Get(int id) => Pizzas.FirstOrDefault(p => p.Id == id); 23 | 24 | public static void Add(Pizza pizza) 25 | { 26 | pizza.Id = nextId++; 27 | Pizzas.Add(pizza); 28 | } 29 | 30 | public static void Delete(int id) 31 | { 32 | var pizza = Get(id); 33 | if (pizza is null) 34 | return; 35 | 36 | Pizzas.Remove(pizza); 37 | } 38 | 39 | public static void Update(Pizza pizza) 40 | { 41 | var index = Pizzas.FindIndex(p => p.Id == pizza.Id); 42 | if (index == -1) 43 | return; 44 | 45 | Pizzas[index] = pizza; 46 | } 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Samples/AspNetSample/Pages/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | @model AspNetSample.AnalyticsPageModel 2 | @{ 3 | Model.analytics.Screen(ViewData["Title"]?.ToString()); 4 | } 5 | 6 | 7 | 8 | 9 | 10 | @ViewData["Title"] - AspNetSample 11 | 12 | 13 | 14 | 15 |
    16 | 38 |
    39 |
    40 |
    41 | @RenderBody() 42 |
    43 |
    44 | 45 |
    46 |
    47 | © 2023 - AspNetSample - Privacy 48 |
    49 |
    50 | 51 | 52 | 53 | 54 | 55 | @await RenderSectionAsync("Scripts", required: false) 56 | 57 | 58 | -------------------------------------------------------------------------------- /Samples/AspNetSample/Pages/Shared/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | -------------------------------------------------------------------------------- /Samples/AspNetSample/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using AspNetSample 2 | @namespace AspNetSample.Pages 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | -------------------------------------------------------------------------------- /Samples/AspNetSample/Pages/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /Samples/AspNetSample/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace AspNetSample 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IHostBuilder CreateHostBuilder(string[] args) => 14 | Host.CreateDefaultBuilder(args) 15 | .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Samples/AspNetSample/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:37926", 7 | "sslPort": 44343 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "AspNetSample": { 19 | "commandName": "Project", 20 | "dotnetRunMessages": "true", 21 | "launchBrowser": true, 22 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Samples/AspNetSample/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.HttpsPolicy; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.DependencyInjection; 10 | using Microsoft.Extensions.Hosting; 11 | using Segment.Analytics; 12 | using Segment.Analytics.Utilities; 13 | 14 | namespace AspNetSample 15 | { 16 | public class Startup 17 | { 18 | public Startup(IConfiguration configuration) 19 | { 20 | Configuration = configuration; 21 | } 22 | 23 | public IConfiguration Configuration { get; } 24 | 25 | // This method gets called by the runtime. Use this method to add services to the container. 26 | public void ConfigureServices(IServiceCollection services) 27 | { 28 | // use `InMemoryStorageProvider` to make Analytics stateless 29 | var configuration = new Configuration("YOUR WRITE KEY", 30 | flushAt: 1, 31 | flushInterval: 10, 32 | storageProvider: new InMemoryStorageProvider()); 33 | 34 | services.AddRazorPages(); 35 | services.AddScoped(_ => new Analytics(configuration)); 36 | } 37 | 38 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 39 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 40 | { 41 | if (env.IsDevelopment()) 42 | { 43 | app.UseDeveloperExceptionPage(); 44 | } 45 | else 46 | { 47 | app.UseExceptionHandler("/Error"); 48 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 49 | app.UseHsts(); 50 | } 51 | 52 | app.UseHttpsRedirection(); 53 | app.UseStaticFiles(); 54 | 55 | app.UseRouting(); 56 | 57 | app.UseAuthorization(); 58 | 59 | app.UseEndpoints(endpoints => { endpoints.MapRazorPages(); }); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Samples/AspNetSample/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "DetailedErrors": true, 3 | "Logging": { 4 | "LogLevel": { 5 | "Default": "Information", 6 | "Microsoft": "Warning", 7 | "Microsoft.Hosting.Lifetime": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Samples/AspNetSample/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /Samples/AspNetSample/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | /* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification 2 | for details on configuring this project to bundle and minify static web assets. */ 3 | 4 | a.navbar-brand { 5 | white-space: normal; 6 | text-align: center; 7 | word-break: break-all; 8 | } 9 | 10 | /* Provide sufficient contrast against white background */ 11 | a { 12 | color: #0366d6; 13 | } 14 | 15 | .btn-primary { 16 | color: #fff; 17 | background-color: #1b6ec2; 18 | border-color: #1861ac; 19 | } 20 | 21 | .nav-pills .nav-link.active, .nav-pills .show > .nav-link { 22 | color: #fff; 23 | background-color: #1b6ec2; 24 | border-color: #1861ac; 25 | } 26 | 27 | /* Sticky footer styles 28 | -------------------------------------------------- */ 29 | html { 30 | font-size: 14px; 31 | } 32 | @media (min-width: 768px) { 33 | html { 34 | font-size: 16px; 35 | } 36 | } 37 | 38 | .border-top { 39 | border-top: 1px solid #e5e5e5; 40 | } 41 | .border-bottom { 42 | border-bottom: 1px solid #e5e5e5; 43 | } 44 | 45 | .box-shadow { 46 | box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); 47 | } 48 | 49 | button.accept-policy { 50 | font-size: 1rem; 51 | line-height: inherit; 52 | } 53 | 54 | /* Sticky footer styles 55 | -------------------------------------------------- */ 56 | html { 57 | position: relative; 58 | min-height: 100%; 59 | } 60 | 61 | body { 62 | /* Margin bottom by footer height */ 63 | margin-bottom: 60px; 64 | } 65 | .footer { 66 | position: absolute; 67 | bottom: 0; 68 | width: 100%; 69 | white-space: nowrap; 70 | line-height: 60px; /* Vertically center the text there */ 71 | } 72 | -------------------------------------------------------------------------------- /Samples/AspNetSample/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/segmentio/Analytics-CSharp/27e5464d538d1e57ae01622f4041912ed47a9dbc/Samples/AspNetSample/wwwroot/favicon.ico -------------------------------------------------------------------------------- /Samples/AspNetSample/wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | // Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification 2 | // for details on configuring this project to bundle and minify static web assets. 3 | 4 | // Write your JavaScript code. 5 | -------------------------------------------------------------------------------- /Samples/AspNetSample/wwwroot/lib/bootstrap/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2018 Twitter, Inc. 4 | Copyright (c) 2011-2018 The Bootstrap Authors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Samples/AspNetSample/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v4.3.1 (https://getbootstrap.com/) 3 | * Copyright 2011-2019 The Bootstrap Authors 4 | * Copyright 2011-2019 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important} 8 | /*# sourceMappingURL=bootstrap-reboot.min.css.map */ -------------------------------------------------------------------------------- /Samples/AspNetSample/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) .NET Foundation. All rights reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 4 | these files except in compliance with the License. You may obtain a copy of the 5 | License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software distributed 10 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 12 | specific language governing permissions and limitations under the License. 13 | -------------------------------------------------------------------------------- /Samples/AspNetSample/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js: -------------------------------------------------------------------------------- 1 | // Unobtrusive validation support library for jQuery and jQuery Validate 2 | // Copyright (c) .NET Foundation. All rights reserved. 3 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 4 | // @version v3.2.11 5 | !function(a){"function"==typeof define&&define.amd?define("jquery.validate.unobtrusive",["jquery-validation"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery-validation")):jQuery.validator.unobtrusive=a(jQuery)}(function(a){function e(a,e,n){a.rules[e]=n,a.message&&(a.messages[e]=a.message)}function n(a){return a.replace(/^\s+|\s+$/g,"").split(/\s*,\s*/g)}function t(a){return a.replace(/([!"#$%&'()*+,.\/:;<=>?@\[\\\]^`{|}~])/g,"\\$1")}function r(a){return a.substr(0,a.lastIndexOf(".")+1)}function i(a,e){return 0===a.indexOf("*.")&&(a=a.replace("*.",e)),a}function o(e,n){var r=a(this).find("[data-valmsg-for='"+t(n[0].name)+"']"),i=r.attr("data-valmsg-replace"),o=i?a.parseJSON(i)!==!1:null;r.removeClass("field-validation-valid").addClass("field-validation-error"),e.data("unobtrusiveContainer",r),o?(r.empty(),e.removeClass("input-validation-error").appendTo(r)):e.hide()}function d(e,n){var t=a(this).find("[data-valmsg-summary=true]"),r=t.find("ul");r&&r.length&&n.errorList.length&&(r.empty(),t.addClass("validation-summary-errors").removeClass("validation-summary-valid"),a.each(n.errorList,function(){a("
  • ").html(this.message).appendTo(r)}))}function s(e){var n=e.data("unobtrusiveContainer");if(n){var t=n.attr("data-valmsg-replace"),r=t?a.parseJSON(t):null;n.addClass("field-validation-valid").removeClass("field-validation-error"),e.removeData("unobtrusiveContainer"),r&&n.empty()}}function l(e){var n=a(this),t="__jquery_unobtrusive_validation_form_reset";if(!n.data(t)){n.data(t,!0);try{n.data("validator").resetForm()}finally{n.removeData(t)}n.find(".validation-summary-errors").addClass("validation-summary-valid").removeClass("validation-summary-errors"),n.find(".field-validation-error").addClass("field-validation-valid").removeClass("field-validation-error").removeData("unobtrusiveContainer").find(">*").removeData("unobtrusiveContainer")}}function u(e){var n=a(e),t=n.data(v),r=a.proxy(l,e),i=f.unobtrusive.options||{},u=function(n,t){var r=i[n];r&&a.isFunction(r)&&r.apply(e,t)};return t||(t={options:{errorClass:i.errorClass||"input-validation-error",errorElement:i.errorElement||"span",errorPlacement:function(){o.apply(e,arguments),u("errorPlacement",arguments)},invalidHandler:function(){d.apply(e,arguments),u("invalidHandler",arguments)},messages:{},rules:{},success:function(){s.apply(e,arguments),u("success",arguments)}},attachValidation:function(){n.off("reset."+v,r).on("reset."+v,r).validate(this.options)},validate:function(){return n.validate(),n.valid()}},n.data(v,t)),t}var m,f=a.validator,v="unobtrusiveValidation";return f.unobtrusive={adapters:[],parseElement:function(e,n){var t,r,i,o=a(e),d=o.parents("form")[0];d&&(t=u(d),t.options.rules[e.name]=r={},t.options.messages[e.name]=i={},a.each(this.adapters,function(){var n="data-val-"+this.name,t=o.attr(n),s={};void 0!==t&&(n+="-",a.each(this.params,function(){s[this]=o.attr(n+this)}),this.adapt({element:e,form:d,message:t,params:s,rules:r,messages:i}))}),a.extend(r,{__dummy__:!0}),n||t.attachValidation())},parse:function(e){var n=a(e),t=n.parents().addBack().filter("form").add(n.find("form")).has("[data-val=true]");n.find("[data-val=true]").each(function(){f.unobtrusive.parseElement(this,!0)}),t.each(function(){var a=u(this);a&&a.attachValidation()})}},m=f.unobtrusive.adapters,m.add=function(a,e,n){return n||(n=e,e=[]),this.push({name:a,params:e,adapt:n}),this},m.addBool=function(a,n){return this.add(a,function(t){e(t,n||a,!0)})},m.addMinMax=function(a,n,t,r,i,o){return this.add(a,[i||"min",o||"max"],function(a){var i=a.params.min,o=a.params.max;i&&o?e(a,r,[i,o]):i?e(a,n,i):o&&e(a,t,o)})},m.addSingleVal=function(a,n,t){return this.add(a,[n||"val"],function(r){e(r,t||a,r.params[n])})},f.addMethod("__dummy__",function(a,e,n){return!0}),f.addMethod("regex",function(a,e,n){var t;return!!this.optional(e)||(t=new RegExp(n).exec(a),t&&0===t.index&&t[0].length===a.length)}),f.addMethod("nonalphamin",function(a,e,n){var t;return n&&(t=a.match(/\W/g),t=t&&t.length>=n),t}),f.methods.extension?(m.addSingleVal("accept","mimtype"),m.addSingleVal("extension","extension")):m.addSingleVal("extension","extension","accept"),m.addSingleVal("regex","pattern"),m.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url"),m.addMinMax("length","minlength","maxlength","rangelength").addMinMax("range","min","max","range"),m.addMinMax("minlength","minlength").addMinMax("maxlength","minlength","maxlength"),m.add("equalto",["other"],function(n){var o=r(n.element.name),d=n.params.other,s=i(d,o),l=a(n.form).find(":input").filter("[name='"+t(s)+"']")[0];e(n,"equalTo",l)}),m.add("required",function(a){"INPUT"===a.element.tagName.toUpperCase()&&"CHECKBOX"===a.element.type.toUpperCase()||e(a,"required",!0)}),m.add("remote",["url","type","additionalfields"],function(o){var d={url:o.params.url,type:o.params.type||"GET",data:{}},s=r(o.element.name);a.each(n(o.params.additionalfields||o.element.name),function(e,n){var r=i(n,s);d.data[r]=function(){var e=a(o.form).find(":input").filter("[name='"+t(r)+"']");return e.is(":checkbox")?e.filter(":checked").val()||e.filter(":hidden").val()||"":e.is(":radio")?e.filter(":checked").val()||"":e.val()}}),e(o,"remote",d)}),m.add("password",["min","nonalphamin","regex"],function(a){a.params.min&&e(a,"minlength",a.params.min),a.params.nonalphamin&&e(a,"nonalphamin",a.params.nonalphamin),a.params.regex&&e(a,"regex",a.params.regex)}),m.add("fileextensions",["extensions"],function(a){e(a,"extension",a.params.extensions)}),a(function(){f.unobtrusive.parse(document)}),f.unobtrusive}); -------------------------------------------------------------------------------- /Samples/AspNetSample/wwwroot/lib/jquery-validation/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright Jörn Zaefferer 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Samples/AspNetSample/wwwroot/lib/jquery/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright JS Foundation and other contributors, https://js.foundation/ 2 | 3 | This software consists of voluntary contributions made by many 4 | individuals. For exact contribution history, see the revision history 5 | available at https://github.com/jquery/jquery 6 | 7 | The following license applies to all parts of this software except as 8 | documented below: 9 | 10 | ==== 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining 13 | a copy of this software and associated documentation files (the 14 | "Software"), to deal in the Software without restriction, including 15 | without limitation the rights to use, copy, modify, merge, publish, 16 | distribute, sublicense, and/or sell copies of the Software, and to 17 | permit persons to whom the Software is furnished to do so, subject to 18 | the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be 21 | included in all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30 | 31 | ==== 32 | 33 | All files located in the node_modules and external directories are 34 | externally maintained libraries used by this software which have their 35 | own licenses; we recommend you read them, as their terms may differ from 36 | the terms above. 37 | -------------------------------------------------------------------------------- /Samples/ConsoleSample/ConsoleSample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net5.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Samples/ConsoleSample/FlushOnScreenEventsPolicy.cs: -------------------------------------------------------------------------------- 1 | using Segment.Analytics; 2 | using Segment.Analytics.Policies; 3 | 4 | namespace ConsoleSample 5 | { 6 | class FlushOnScreenEventsPolicy : IFlushPolicy 7 | { 8 | private bool _screenEventsSeen = false; 9 | 10 | public bool ShouldFlush() => _screenEventsSeen; 11 | 12 | public void UpdateState(RawEvent @event) 13 | { 14 | if (@event is ScreenEvent) 15 | { 16 | _screenEventsSeen = true; 17 | } 18 | } 19 | 20 | public void Reset() 21 | { 22 | _screenEventsSeen = false; 23 | } 24 | 25 | public void Schedule(Analytics analytics) {} 26 | 27 | public void Unschedule() {} 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Samples/ConsoleSample/NetworkErrorHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Segment.Analytics; 3 | using Segment.Analytics.Policies; 4 | 5 | namespace ConsoleSample 6 | { 7 | class NetworkErrorHandler : IAnalyticsErrorHandler 8 | { 9 | private Analytics _analytics; 10 | 11 | public NetworkErrorHandler(Analytics analytics) 12 | { 13 | _analytics = analytics; 14 | } 15 | 16 | public void OnExceptionThrown(Exception e) 17 | { 18 | if (e is AnalyticsError error && error.ErrorType == AnalyticsErrorType.NetworkServerLimited) 19 | { 20 | _analytics.ClearFlushPolicies(); 21 | // Add less persistent flush policies 22 | _analytics.AddFlushPolicy(new CountFlushPolicy(1000), new FrequencyFlushPolicy(60 * 60 * 1000)); 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Samples/ConsoleSample/Program.cs: -------------------------------------------------------------------------------- 1 | // See https://aka.ms/new-console-template for more information 2 | 3 | using System; 4 | using ConsoleSample; 5 | using Segment.Analytics; 6 | 7 | 8 | var configuration = new Configuration("YOUR WRITE KEY", 9 | flushAt: 1, 10 | flushInterval: 10, 11 | exceptionHandler: new ErrorHandler()); 12 | var analytics = new Analytics(configuration); 13 | Analytics.Logger = new SegmentLogger(); 14 | 15 | analytics.Identify("foo"); 16 | analytics.Track("track right after identify"); 17 | 18 | Console.ReadLine(); 19 | 20 | 21 | class ErrorHandler : IAnalyticsErrorHandler 22 | { 23 | public void OnExceptionThrown(Exception e) 24 | { 25 | Console.WriteLine(e.StackTrace); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Samples/ConsoleSample/ProxyHttpClient.cs: -------------------------------------------------------------------------------- 1 | using Segment.Analytics.Utilities; 2 | 3 | namespace ConsoleSample 4 | { 5 | class ProxyHttpClient : DefaultHTTPClient 6 | { 7 | public ProxyHttpClient(string apiKey, string apiHost = null, string cdnHost = null) : base(apiKey, apiHost, cdnHost) 8 | { 9 | } 10 | 11 | public override string SegmentURL(string host, string path) 12 | { 13 | if (host.Equals(_apiHost)) 14 | { 15 | return "Your proxy api url"; 16 | } 17 | else 18 | { 19 | return "Your proxy cdn url"; 20 | } 21 | } 22 | } 23 | 24 | class ProxyHttpClientProvider : IHTTPClientProvider 25 | { 26 | public HTTPClient CreateHTTPClient(string apiKey, string apiHost = null, string cdnHost = null) 27 | { 28 | return new ProxyHttpClient(apiKey, apiHost, cdnHost); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Samples/ConsoleSample/SegmentLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Segment.Analytics.Utilities; 3 | 4 | namespace ConsoleSample 5 | { 6 | class SegmentLogger : ISegmentLogger 7 | { 8 | public void Log(LogLevel logLevel, Exception exception = null, string message = null) 9 | { 10 | switch (logLevel) 11 | { 12 | case LogLevel.Warning: 13 | case LogLevel.Information: 14 | case LogLevel.Debug: 15 | Console.Out.WriteLine("Message: " + message); 16 | break; 17 | case LogLevel.Critical: 18 | case LogLevel.Trace: 19 | case LogLevel.Error: 20 | Console.Error.WriteLine("Exception: " + exception?.StackTrace); 21 | Console.Error.WriteLine("Message: " + message); 22 | break; 23 | case LogLevel.None: 24 | default: 25 | break; 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Samples/UnitySample/LifecyclePlugin.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Segment.Analytics; 4 | using Segment.Serialization; 5 | using UnityEngine; 6 | 7 | namespace UnitySample 8 | { 9 | 10 | /// 11 | /// Track your lifecycle events such as: installed, updated, opened, and backgrounded. 12 | /// Copy and paste the classes in this file and the class to your unity project, 13 | /// and add the following one-liner: 14 | /// 15 | /// analytics.Add(new LifecyclePlugin()); 16 | /// 17 | /// Now your lifecycle events are automatically tracked. 18 | /// 19 | public class LifecyclePlugin : Plugin, IObserver 20 | { 21 | public override PluginType Type => PluginType.Utility; 22 | 23 | private IDisposable _unsubscriber; 24 | 25 | public override void Configure(Analytics analytics) 26 | { 27 | base.Configure(analytics); 28 | _unsubscriber = Lifecycle.Instance.Subscribe(this); 29 | } 30 | 31 | public void OnNext(Lifecycle.State newState) 32 | { 33 | Analytics.Track(newState.Message, newState.Properties); 34 | } 35 | 36 | public override void Shutdown() 37 | { 38 | base.Shutdown(); 39 | _unsubscriber?.Dispose(); 40 | } 41 | 42 | public void OnCompleted() 43 | { 44 | } 45 | 46 | public void OnError(Exception error) 47 | { 48 | } 49 | } 50 | 51 | 52 | #region Observer Classes 53 | 54 | /// 55 | /// A singleton component that listens unity events and reports to its observers 56 | /// 57 | public class Lifecycle : Singleton, IObservable 58 | { 59 | // use Segment's ConcurrentList to avoid modification during enumeration 60 | // or you have to make a copy for iterating the observers. 61 | private readonly IList> _observers = new ConcurrentList>(); 62 | 63 | private const string AppVersionKey = "app_version"; 64 | 65 | private void CheckVersion() 66 | { 67 | string currentVersion = Application.version; 68 | string previousVersion = PlayerPrefs.GetString(AppVersionKey); 69 | 70 | if (!PlayerPrefs.HasKey(AppVersionKey)) 71 | { 72 | NotifyObservers(new State 73 | { 74 | Message = "Application Installed", Properties = new JsonObject {["version"] = currentVersion} 75 | }); 76 | } 77 | else if (previousVersion != currentVersion) 78 | { 79 | NotifyObservers(new State 80 | { 81 | Message = "Application Updated", 82 | Properties = new JsonObject 83 | { 84 | ["previous_version"] = previousVersion, ["version"] = currentVersion 85 | } 86 | }); 87 | } 88 | 89 | PlayerPrefs.SetString(AppVersionKey, currentVersion); 90 | PlayerPrefs.Save(); 91 | } 92 | 93 | private void Start() 94 | { 95 | CheckVersion(); 96 | NotifyObservers(new State {Message = "Application Opened"}); 97 | } 98 | 99 | private void OnApplicationPause(bool pauseStatus) 100 | { 101 | if (pauseStatus) 102 | { 103 | NotifyObservers(new State {Message = "Application Backgrounded"}); 104 | } 105 | } 106 | 107 | public void NotifyObservers(State newState) 108 | { 109 | foreach (var observer in _observers) 110 | { 111 | observer.OnNext(newState); 112 | } 113 | } 114 | 115 | public IDisposable Subscribe(IObserver observer) 116 | { 117 | if (!_observers.Contains(observer)) 118 | { 119 | _observers.Add(observer); 120 | } 121 | 122 | return new Unsubscriber(_observers, observer); 123 | } 124 | 125 | private class Unsubscriber : IDisposable 126 | { 127 | private IList> _observers; 128 | private IObserver _observer; 129 | 130 | public Unsubscriber(IList> observers, IObserver observer) 131 | { 132 | _observers = observers; 133 | _observer = observer; 134 | } 135 | 136 | public void Dispose() 137 | { 138 | if (_observer != null && _observers.Contains(_observer)) 139 | _observers.Remove(_observer); 140 | } 141 | } 142 | 143 | /// 144 | /// Lifecycle state that contains the data send over to analytics 145 | /// 146 | public class State 147 | { 148 | public string Message { get; set; } 149 | public JsonObject Properties { get; set; } 150 | } 151 | } 152 | 153 | #endregion 154 | } 155 | -------------------------------------------------------------------------------- /Samples/UnitySample/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("UnitySample")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("UnitySample")] 12 | [assembly: AssemblyCopyright("Copyright © 2023")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("1C2C676E-E82F-484C-BCC5-9ECAD5C5A118")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /Samples/UnitySample/Singleton.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace UnitySample 4 | { 5 | /// 6 | /// A singleton template that adds component to the scene automatically and persists across scenes 7 | /// 8 | /// Type of the Component 9 | public class Singleton : MonoBehaviour where T : Component 10 | { 11 | private static T s_instance; 12 | 13 | public static T Instance 14 | { 15 | get 16 | { 17 | if (s_instance == null) 18 | { 19 | s_instance = FindObjectOfType(); 20 | if (s_instance == null) 21 | { 22 | s_instance = new GameObject("Segment Singleton").AddComponent(); 23 | DontDestroyOnLoad(s_instance.gameObject); 24 | } 25 | } 26 | 27 | return s_instance; 28 | } 29 | } 30 | 31 | protected virtual void Awake() 32 | { 33 | if (s_instance == null) 34 | { 35 | s_instance = this as T; 36 | DontDestroyOnLoad(gameObject); 37 | } 38 | else 39 | { 40 | Destroy(gameObject); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Samples/UnitySample/SingletonAnalytics.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Segment.Analytics; 3 | using Segment.Concurrent; 4 | using UnityEngine; 5 | 6 | namespace UnitySample 7 | { 8 | public class SingletonAnalytics : Singleton 9 | { 10 | public Analytics Analytics { get; set; } 11 | 12 | protected override void Awake() 13 | { 14 | // you don't have to use `UnityHTTPClientProvider` 15 | // the default httpClientProvider works on Unity, too. 16 | Configuration configuration = 17 | new Configuration("YOUR WRITE KEY", 18 | exceptionHandler: new ErrorHandler(), 19 | httpClientProvider: new UnityHTTPClientProvider(MainThreadDispatcher.Instance)); 20 | Analytics = new Analytics(configuration); 21 | Analytics.Add(new LifecyclePlugin()); 22 | } 23 | 24 | class ErrorHandler : ICoroutineExceptionHandler 25 | { 26 | public void OnExceptionThrown(Exception e) 27 | { 28 | Debug.LogException(e); 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Samples/UnitySample/UnityHTTPClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Concurrent; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Segment.Analytics.Utilities; 7 | using UnityEngine.Networking; 8 | 9 | namespace UnitySample 10 | { 11 | /// 12 | /// Http client based on UnityWebRequest. 13 | /// NOTE: this implementation shows how you can customize the network logic with your favorite network library. 14 | /// Though it is based on UnityWebRequest, it does not work in WebGL due to the fact that the Analytics library 15 | /// is architected to be a multi-thread library, whereas WebGL does not support multi-threading as of right now. 16 | /// 17 | public class UnityHTTPClient : HTTPClient 18 | { 19 | public UnityHTTPClient(string apiKey, string apiHost = null, string cdnHost = null) : base(apiKey, apiHost, cdnHost) 20 | { 21 | } 22 | 23 | public override async Task DoGet(string url) 24 | { 25 | using (var request = new NetworkRequest {URL = url, Action = GetRequest}) 26 | { 27 | 28 | MainThreadDispatcher.Instance.Post(request); 29 | await request.Semaphore.WaitAsync(); 30 | 31 | return request.Response; 32 | } 33 | } 34 | 35 | IEnumerator GetRequest(NetworkRequest networkRequest) 36 | { 37 | using (var request = UnityWebRequest.Get(networkRequest.URL)) 38 | { 39 | request.SetRequestHeader("Content-Type", "application/json; charset=utf-8"); 40 | yield return request.SendWebRequest(); 41 | 42 | networkRequest.Response.StatusCode = (int)request.responseCode; 43 | networkRequest.Response.Content = request.downloadHandler.text; 44 | networkRequest.Semaphore.Release(); 45 | } 46 | } 47 | 48 | public override async Task DoPost(string url, byte[] data) 49 | { 50 | using (var request = new NetworkRequest {URL = url, Data = data, Action = PostRequest}) 51 | { 52 | MainThreadDispatcher.Instance.Post(request); 53 | await request.Semaphore.WaitAsync(); 54 | 55 | return request.Response; 56 | } 57 | } 58 | 59 | IEnumerator PostRequest(NetworkRequest networkRequest) 60 | { 61 | using (var request = UnityWebRequest.Put(networkRequest.URL, networkRequest.Data)) 62 | { 63 | request.SetRequestHeader("Content-Type", "text/plain"); 64 | yield return request.SendWebRequest(); 65 | 66 | networkRequest.Response.StatusCode = (int)request.responseCode; 67 | networkRequest.Semaphore.Release(); 68 | } 69 | } 70 | } 71 | 72 | public class UnityHTTPClientProvider : IHTTPClientProvider 73 | { 74 | /// 75 | /// Provider that creates a Http client based on UnityWebRequest 76 | /// 77 | /// the dispatcher is required to force instantiation of MainThreadDispatcher 78 | public UnityHTTPClientProvider(MainThreadDispatcher dispatcher) 79 | { 80 | } 81 | 82 | public HTTPClient CreateHTTPClient(string apiKey, string apiHost = null, string cdnHost = null) 83 | { 84 | return new UnityHTTPClient(apiKey, apiHost, cdnHost); 85 | } 86 | } 87 | 88 | public class NetworkRequest : IDisposable 89 | { 90 | public HTTPClient.Response Response { get; set; } 91 | public SemaphoreSlim Semaphore { get; set; } 92 | 93 | public string URL { get; set; } 94 | 95 | public byte[] Data { get; set; } 96 | 97 | public Func Action { get; set; } 98 | 99 | public NetworkRequest() 100 | { 101 | Response = new HTTPClient.Response(); 102 | Semaphore = new SemaphoreSlim(0); 103 | } 104 | 105 | public IEnumerator Run() => Action(this); 106 | 107 | public void Dispose() 108 | { 109 | Semaphore?.Dispose(); 110 | } 111 | } 112 | 113 | public class MainThreadDispatcher : Singleton 114 | { 115 | private ConcurrentQueue _tasks; 116 | 117 | protected override void Awake() 118 | { 119 | base.Awake(); 120 | _tasks= new ConcurrentQueue(); 121 | } 122 | 123 | public void Post(NetworkRequest task) 124 | { 125 | _tasks.Enqueue(task); 126 | } 127 | 128 | 129 | private void Update() 130 | { 131 | while (!_tasks.IsEmpty) 132 | { 133 | if (_tasks.TryDequeue(out NetworkRequest task)) 134 | { 135 | StartCoroutine(task.Run()); 136 | } 137 | } 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /Samples/UnitySample/UnitySample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {1C2C676E-E82F-484C-BCC5-9ECAD5C5A118} 8 | Library 9 | Properties 10 | UnitySample 11 | UnitySample 12 | v4.8 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | ..\..\packages\Coroutine.NET.1.3.0\lib\netstandard2.0\Coroutine.NET.dll 37 | 38 | 39 | ..\..\..\..\.nuget\packages\serialization.net\1.4.0\lib\netstandard2.0\Serialization.NET.dll 40 | 41 | 42 | 43 | 44 | 45 | /Applications/Unity/Hub/Editor/2021.3.20f1/Unity.app/Contents/Managed/UnityEngine.dll 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | {139fe430-293c-470e-a67a-5c35a316ff00} 58 | Analytics-CSharp 59 | 60 | 61 | 62 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /Samples/XamarinSample/XamarinSample.Droid/Assets/AboutAssets.txt: -------------------------------------------------------------------------------- 1 | Any raw assets you want to be deployed with your application can be placed in 2 | this directory (and child directories) and given a Build Action of "AndroidAsset". 3 | 4 | These files will be deployed with you package and will be accessible using Android's 5 | AssetManager, like this: 6 | 7 | public class ReadAsset : Activity 8 | { 9 | protected override void OnCreate (Bundle bundle) 10 | { 11 | base.OnCreate (bundle); 12 | 13 | InputStream input = Assets.Open ("my_asset.txt"); 14 | } 15 | } 16 | 17 | Additionally, some Android functions will automatically load asset files: 18 | 19 | Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf"); 20 | -------------------------------------------------------------------------------- /Samples/XamarinSample/XamarinSample.Droid/MainActivity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using Android.App; 4 | using Android.Content.PM; 5 | using Android.Runtime; 6 | using Android.Views; 7 | using Android.Widget; 8 | using Android.OS; 9 | 10 | namespace XamarinSample.Droid 11 | { 12 | [Activity(Label = "XamarinSample", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)] 13 | public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity 14 | { 15 | protected override void OnCreate(Bundle savedInstanceState) 16 | { 17 | TabLayoutResource = Resource.Layout.Tabbar; 18 | ToolbarResource = Resource.Layout.Toolbar; 19 | 20 | base.OnCreate(savedInstanceState); 21 | global::Xamarin.Forms.Forms.Init(this, savedInstanceState); 22 | LoadApplication(new App()); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /Samples/XamarinSample/XamarinSample.Droid/Properties/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Samples/XamarinSample/XamarinSample.Droid/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | using Android.App; 5 | 6 | // General Information about an assembly is controlled through the following 7 | // set of attributes. Change these attribute values to modify the information 8 | // associated with an assembly. 9 | [assembly: AssemblyTitle("XamarinSample.Droid")] 10 | [assembly: AssemblyDescription("")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCompany("CompanyName")] 13 | [assembly: AssemblyProduct("XamarinSample.Droid")] 14 | [assembly: AssemblyCopyright("Copyright © CompanyName Year")] 15 | [assembly: AssemblyTrademark("CompanyTrademark")] 16 | [assembly: AssemblyCulture("")] 17 | [assembly: ComVisible(false)] 18 | 19 | // Version information for an assembly consists of the following four values: 20 | // 21 | // Major Version 22 | // Minor Version 23 | // Build Number 24 | // Revision 25 | // 26 | // You can specify all the values or you can default the Build and Revision Numbers 27 | // by using the '*' as shown below: 28 | // [assembly: AssemblyVersion("1.0.*")] 29 | [assembly: AssemblyVersion("1.0.0.0")] 30 | [assembly: AssemblyFileVersion("1.0.0.0")] -------------------------------------------------------------------------------- /Samples/XamarinSample/XamarinSample.Droid/Resources/AboutResources.txt: -------------------------------------------------------------------------------- 1 | Images, layout descriptions, binary blobs and string dictionaries can be included 2 | in your application as resource files. Various Android APIs are designed to 3 | operate on the resource IDs instead of dealing with images, strings or binary blobs 4 | directly. 5 | 6 | For example, a sample Android app that contains a user interface layout (main.xml), 7 | an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png) 8 | would keep its resources in the "Resources" directory of the application: 9 | 10 | Resources/ 11 | drawable-hdpi/ 12 | icon.png 13 | 14 | drawable-ldpi/ 15 | icon.png 16 | 17 | drawable-mdpi/ 18 | icon.png 19 | 20 | layout/ 21 | main.xml 22 | 23 | values/ 24 | strings.xml 25 | 26 | In order to get the build system to recognize Android resources, set the build action to 27 | "AndroidResource". The native Android APIs do not operate directly with filenames, but 28 | instead operate on resource IDs. When you compile an Android application that uses resources, 29 | the build system will package the resources for distribution and generate a class called 30 | "Resource" that contains the tokens for each one of the resources included. For example, 31 | for the above Resources layout, this is what the Resource class would expose: 32 | 33 | public class Resource { 34 | public class drawable { 35 | public const int icon = 0x123; 36 | } 37 | 38 | public class layout { 39 | public const int main = 0x456; 40 | } 41 | 42 | public class strings { 43 | public const int first_string = 0xabc; 44 | public const int second_string = 0xbcd; 45 | } 46 | } 47 | 48 | You would then use R.drawable.icon to reference the drawable/icon.png file, or Resource.layout.main 49 | to reference the layout/main.xml file, or Resource.strings.first_string to reference the first 50 | string in the dictionary file values/strings.xml. 51 | -------------------------------------------------------------------------------- /Samples/XamarinSample/XamarinSample.Droid/Resources/layout/Tabbar.axml: -------------------------------------------------------------------------------- 1 | 2 | 12 | -------------------------------------------------------------------------------- /Samples/XamarinSample/XamarinSample.Droid/Resources/layout/Toolbar.axml: -------------------------------------------------------------------------------- 1 | 9 | 10 | -------------------------------------------------------------------------------- /Samples/XamarinSample/XamarinSample.Droid/Resources/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | #3F51B5 5 | #303F9F 6 | #FF4081 7 | 8 | -------------------------------------------------------------------------------- /Samples/XamarinSample/XamarinSample.Droid/Resources/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 26 | 27 | 30 | 31 | -------------------------------------------------------------------------------- /Samples/XamarinSample/XamarinSample.Droid/XamarinSample.Droid.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {86FFFF37-9FB0-4632-8D44-E09202E158AF} 7 | {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 8 | Library 9 | XamarinSample.Droid 10 | XamarinSample.Droid 11 | True 12 | Resources\Resource.designer.cs 13 | Resource 14 | Properties\AndroidManifest.xml 15 | Resources 16 | Assets 17 | false 18 | v12.0 19 | Xamarin.Android.Net.AndroidClientHandler 20 | 21 | 22 | true 23 | portable 24 | false 25 | bin\Debug 26 | DEBUG; 27 | prompt 28 | 4 29 | None 30 | true 31 | 32 | 33 | true 34 | pdbonly 35 | true 36 | bin\Release 37 | prompt 38 | 4 39 | true 40 | false 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | {DC46FE98-A67E-4667-ADE2-42B25003BACC} 71 | XamarinSample 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /Samples/XamarinSample/XamarinSample.iOS/AppDelegate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | using Foundation; 6 | using UIKit; 7 | 8 | namespace XamarinSample.iOS 9 | { 10 | // The UIApplicationDelegate for the application. This class is responsible for launching the 11 | // User Interface of the application, as well as listening (and optionally responding) to 12 | // application events from iOS. 13 | [Register("AppDelegate")] 14 | public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate 15 | { 16 | // 17 | // This method is invoked when the application has loaded and is ready to run. In this 18 | // method you should instantiate the window, load the UI into it and then make the window 19 | // visible. 20 | // 21 | // You have 17 seconds to return from this method, or iOS will terminate your application. 22 | // 23 | public override bool FinishedLaunching(UIApplication app, NSDictionary options) 24 | { 25 | global::Xamarin.Forms.Forms.Init(); 26 | LoadApplication(new App()); 27 | 28 | return base.FinishedLaunching(app, options); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Samples/XamarinSample/XamarinSample.iOS/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "scale": "2x", 5 | "size": "20x20", 6 | "idiom": "iphone", 7 | "filename": "Icon40.png" 8 | }, 9 | { 10 | "scale": "3x", 11 | "size": "20x20", 12 | "idiom": "iphone", 13 | "filename": "Icon60.png" 14 | }, 15 | { 16 | "scale": "2x", 17 | "size": "29x29", 18 | "idiom": "iphone", 19 | "filename": "Icon58.png" 20 | }, 21 | { 22 | "scale": "3x", 23 | "size": "29x29", 24 | "idiom": "iphone", 25 | "filename": "Icon87.png" 26 | }, 27 | { 28 | "scale": "2x", 29 | "size": "40x40", 30 | "idiom": "iphone", 31 | "filename": "Icon80.png" 32 | }, 33 | { 34 | "scale": "3x", 35 | "size": "40x40", 36 | "idiom": "iphone", 37 | "filename": "Icon120.png" 38 | }, 39 | { 40 | "scale": "2x", 41 | "size": "60x60", 42 | "idiom": "iphone", 43 | "filename": "Icon120.png" 44 | }, 45 | { 46 | "scale": "3x", 47 | "size": "60x60", 48 | "idiom": "iphone", 49 | "filename": "Icon180.png" 50 | }, 51 | { 52 | "scale": "1x", 53 | "size": "20x20", 54 | "idiom": "ipad", 55 | "filename": "Icon20.png" 56 | }, 57 | { 58 | "scale": "2x", 59 | "size": "20x20", 60 | "idiom": "ipad", 61 | "filename": "Icon40.png" 62 | }, 63 | { 64 | "scale": "1x", 65 | "size": "29x29", 66 | "idiom": "ipad", 67 | "filename": "Icon29.png" 68 | }, 69 | { 70 | "scale": "2x", 71 | "size": "29x29", 72 | "idiom": "ipad", 73 | "filename": "Icon58.png" 74 | }, 75 | { 76 | "scale": "1x", 77 | "size": "40x40", 78 | "idiom": "ipad", 79 | "filename": "Icon40.png" 80 | }, 81 | { 82 | "scale": "2x", 83 | "size": "40x40", 84 | "idiom": "ipad", 85 | "filename": "Icon80.png" 86 | }, 87 | { 88 | "scale": "1x", 89 | "size": "76x76", 90 | "idiom": "ipad", 91 | "filename": "Icon76.png" 92 | }, 93 | { 94 | "scale": "2x", 95 | "size": "76x76", 96 | "idiom": "ipad", 97 | "filename": "Icon152.png" 98 | }, 99 | { 100 | "scale": "2x", 101 | "size": "83.5x83.5", 102 | "idiom": "ipad", 103 | "filename": "Icon167.png" 104 | }, 105 | { 106 | "scale": "1x", 107 | "size": "1024x1024", 108 | "idiom": "ios-marketing", 109 | "filename": "Icon1024.png" 110 | } 111 | ], 112 | "properties": {}, 113 | "info": { 114 | "version": 1, 115 | "author": "xcode" 116 | } 117 | } -------------------------------------------------------------------------------- /Samples/XamarinSample/XamarinSample.iOS/Entitlements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Samples/XamarinSample/XamarinSample.iOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIDeviceFamily 6 | 7 | 1 8 | 2 9 | 10 | UISupportedInterfaceOrientations 11 | 12 | UIInterfaceOrientationPortrait 13 | UIInterfaceOrientationLandscapeLeft 14 | UIInterfaceOrientationLandscapeRight 15 | 16 | UISupportedInterfaceOrientations~ipad 17 | 18 | UIInterfaceOrientationPortrait 19 | UIInterfaceOrientationPortraitUpsideDown 20 | UIInterfaceOrientationLandscapeLeft 21 | UIInterfaceOrientationLandscapeRight 22 | 23 | MinimumOSVersion 24 | 15.0 25 | CFBundleDisplayName 26 | XamarinSample 27 | CFBundleIdentifier 28 | com.companyname.XamarinSample 29 | CFBundleVersion 30 | 1.0 31 | UILaunchStoryboardName 32 | LaunchScreen 33 | CFBundleName 34 | XamarinSample 35 | XSAppIconAssets 36 | Assets.xcassets/AppIcon.appiconset 37 | 38 | 39 | -------------------------------------------------------------------------------- /Samples/XamarinSample/XamarinSample.iOS/Main.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | using Foundation; 6 | using UIKit; 7 | 8 | namespace XamarinSample.iOS 9 | { 10 | public class Application 11 | { 12 | // This is the main entry point of the application. 13 | static void Main(string[] args) 14 | { 15 | // if you want to use a different Application Delegate class from "AppDelegate" 16 | // you can specify it here. 17 | UIApplication.Main(args, null, "AppDelegate"); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Samples/XamarinSample/XamarinSample.iOS/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("XamarinSample.iOS")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("CompanyName")] 12 | [assembly: AssemblyProduct("XamarinSample.iOS")] 13 | [assembly: AssemblyCopyright("Copyright © CompanyName Year")] 14 | [assembly: AssemblyTrademark("CompanyTrademark")] 15 | [assembly: AssemblyCulture("")] 16 | [assembly: ComVisible(false)] 17 | 18 | // Version information for an assembly consists of the following four values: 19 | // 20 | // Major Version 21 | // Minor Version 22 | // Build Number 23 | // Revision 24 | // 25 | // You can specify all the values or you can default the Build and Revision Numbers 26 | // by using the '*' as shown below: 27 | // [assembly: AssemblyVersion("1.0.*")] 28 | [assembly: AssemblyVersion("1.0.0.0")] 29 | [assembly: AssemblyFileVersion("1.0.0.0")] -------------------------------------------------------------------------------- /Samples/XamarinSample/XamarinSample.iOS/Resources/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Samples/XamarinSample/XamarinSample.iOS/XamarinSample.iOS.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | iPhoneSimulator 6 | 8.0.30703 7 | 2.0 8 | {39FCFC71-411C-419E-97BB-7C4281764B10} 9 | {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 10 | Exe 11 | XamarinSample.iOS 12 | Resources 13 | XamarinSample.iOS 14 | NSUrlSessionHandler 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\iPhoneSimulator\Debug 21 | DEBUG 22 | prompt 23 | 4 24 | false 25 | x86_64 26 | None 27 | true 28 | 29 | 30 | none 31 | true 32 | bin\iPhoneSimulator\Release 33 | prompt 34 | 4 35 | None 36 | x86_64 37 | false 38 | 39 | 40 | true 41 | full 42 | false 43 | bin\iPhone\Debug 44 | DEBUG 45 | prompt 46 | 4 47 | false 48 | ARM64 49 | iPhone Developer 50 | true 51 | Entitlements.plist 52 | 53 | 54 | none 55 | true 56 | bin\iPhone\Release 57 | prompt 58 | 4 59 | ARM64 60 | false 61 | iPhone Developer 62 | Entitlements.plist 63 | 64 | 65 | none 66 | True 67 | bin\iPhone\Ad-Hoc 68 | prompt 69 | 4 70 | False 71 | ARM64 72 | True 73 | Automatic:AdHoc 74 | iPhone Distribution 75 | Entitlements.plist 76 | 77 | 78 | none 79 | True 80 | bin\iPhone\AppStore 81 | prompt 82 | 4 83 | False 84 | ARM64 85 | Automatic:AppStore 86 | iPhone Distribution 87 | Entitlements.plist 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | false 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | {DC46FE98-A67E-4667-ADE2-42B25003BACC} 114 | XamarinSample 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /Samples/XamarinSample/XamarinSample/App.xaml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Samples/XamarinSample/XamarinSample/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Segment.Analytics; 3 | using Xamarin.Forms; 4 | using Xamarin.Forms.Xaml; 5 | 6 | [assembly: XamlCompilation(XamlCompilationOptions.Compile)] 7 | 8 | namespace XamarinSample 9 | { 10 | public partial class App : Application 11 | { 12 | public static Analytics analytics { get; private set; } 13 | 14 | public App() 15 | { 16 | InitializeComponent(); 17 | 18 | var configuration = new Configuration("HhvdP2KyRHb1XGiSTHjBcJiHckP6efii", 19 | flushAt: 1); 20 | analytics = new Analytics(configuration); 21 | 22 | MainPage = new MainPage(); 23 | } 24 | 25 | protected override void OnStart() 26 | { 27 | analytics.Track("Application Opened"); 28 | } 29 | 30 | protected override void OnSleep() 31 | { 32 | analytics.Track("Application Backgrounded"); 33 | } 34 | 35 | protected override void OnResume() 36 | { 37 | // Handle when your app resumes 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Samples/XamarinSample/XamarinSample/MainPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Segment.Analytics; 3 | using Segment.Serialization; 4 | using Xamarin.Forms; 5 | 6 | namespace XamarinSample 7 | { 8 | public partial class MainPage : ContentPage 9 | { 10 | enum EventType 11 | { 12 | Track, Identify, Screen, Group 13 | } 14 | 15 | private EventType _type = EventType.Track; 16 | 17 | #region UI setup 18 | 19 | public MainPage() 20 | { 21 | InitializeComponent(); 22 | 23 | TrackButton.Clicked += Track_Clicked; 24 | IdentifyButton.Clicked += Identify_Clicked; 25 | ScreenButton.Clicked += Screen_Clicked; 26 | GroupButton.Clicked += Group_Clicked; 27 | FlushButton.Clicked += Flush_Clicked; 28 | SendEventButton.Clicked += SendEvent_Clicked; 29 | 30 | AddPlugin(); 31 | } 32 | 33 | void Track_Clicked(object sender, EventArgs e) 34 | { 35 | _type = EventType.Track; 36 | EventLabel.Text = "Track Event"; 37 | EventNameEditor.Placeholder = "Event Name"; 38 | Reset(); 39 | } 40 | 41 | void Identify_Clicked(object sender, EventArgs e) 42 | { 43 | _type = EventType.Identify; 44 | EventLabel.Text = "Identify User"; 45 | EventNameEditor.Placeholder = "User Id"; 46 | Reset(); 47 | } 48 | 49 | void Screen_Clicked(object sender, EventArgs e) 50 | { 51 | _type = EventType.Screen; 52 | EventLabel.Text = "Track Screen"; 53 | EventNameEditor.Placeholder = "Screen Name"; 54 | Reset(); 55 | } 56 | 57 | void Group_Clicked(object sender, EventArgs e) 58 | { 59 | _type = EventType.Group; 60 | EventLabel.Text = "Identify Group"; 61 | EventNameEditor.Placeholder = "Group Id"; 62 | Reset(); 63 | } 64 | 65 | void Flush_Clicked(object sender, EventArgs e) 66 | { 67 | Flush(); 68 | Reset(); 69 | } 70 | 71 | void SendEvent_Clicked(object sender, EventArgs e) 72 | { 73 | string field = string.IsNullOrEmpty(EventNameEditor.Text) ? EventNameEditor.Placeholder : EventNameEditor.Text; 74 | string key = string.IsNullOrEmpty(PropertyEditor.Text) ? PropertyEditor.Placeholder : PropertyEditor.Text; 75 | string value = string.IsNullOrEmpty(ValueEditor.Text) ? ValueEditor.Placeholder : ValueEditor.Text; 76 | var payload = new JsonObject 77 | { 78 | [key] = value 79 | }; 80 | 81 | Send(field, payload); 82 | } 83 | 84 | private void Reset() 85 | { 86 | EventNameEditor.Text = ""; 87 | PropertyEditor.Text = ""; 88 | ValueEditor.Text = ""; 89 | } 90 | 91 | #endregion 92 | 93 | #region Analytics usage samples 94 | 95 | void Flush() 96 | { 97 | App.analytics.Flush(); 98 | } 99 | 100 | private void Send(string field, JsonObject payload) 101 | { 102 | switch (_type) 103 | { 104 | case EventType.Track: 105 | App.analytics.Track(field, payload); 106 | break; 107 | case EventType.Identify: 108 | App.analytics.Identify(field, payload); 109 | break; 110 | case EventType.Screen: 111 | App.analytics.Screen(field, payload); 112 | break; 113 | case EventType.Group: 114 | App.analytics.Group(field, payload); 115 | break; 116 | default: 117 | throw new ArgumentOutOfRangeException(); 118 | } 119 | } 120 | 121 | private void AddPlugin() 122 | { 123 | App.analytics.Add(new DisplayResultPlugin(result => 124 | { 125 | Device.BeginInvokeOnMainThread(() => 126 | { 127 | ConsoleLabel.Text = result; 128 | }); 129 | })); 130 | } 131 | 132 | /// 133 | /// Sample of a custom plugin that sends the final event payload 134 | /// to a callback in json format 135 | /// 136 | private class DisplayResultPlugin : Plugin 137 | { 138 | public override PluginType Type => PluginType.After; 139 | 140 | private readonly Action _onResult; 141 | 142 | public DisplayResultPlugin(Action onResult) 143 | { 144 | _onResult = onResult; 145 | } 146 | 147 | public override RawEvent Execute(RawEvent incomingEvent) 148 | { 149 | string result = JsonUtility.ToJson(incomingEvent); 150 | _onResult(result); 151 | 152 | return base.Execute(incomingEvent); 153 | } 154 | } 155 | 156 | #endregion 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /Samples/XamarinSample/XamarinSample/XamarinSample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | pdbonly 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Tests/Plugins/DestinationMetadataPluginTest.cs: -------------------------------------------------------------------------------- 1 | using Moq; 2 | using Segment.Analytics; 3 | using Segment.Analytics.Plugins; 4 | using Segment.Analytics.Utilities; 5 | using Segment.Serialization; 6 | using Tests.Utils; 7 | using Xunit; 8 | 9 | namespace Tests.Plugins 10 | { 11 | public class DestinationMetadataPluginTest 12 | { 13 | private readonly Analytics _analytics; 14 | 15 | private Settings _settings; 16 | 17 | private DestinationMetadataPlugin _metadataPlugin; 18 | 19 | public DestinationMetadataPluginTest() 20 | { 21 | _settings = JsonUtility.FromJson( 22 | "{\"integrations\":{\"Segment.io\":{\"apiKey\":\"1vNgUqwJeCHmqgI9S1sOm9UHCyfYqbaQ\"}},\"plan\":{},\"edgeFunction\":{}}"); 23 | 24 | var mockHttpClient = new Mock(null, null, null); 25 | mockHttpClient 26 | .Setup(httpClient => httpClient.Settings()) 27 | .ReturnsAsync(_settings); 28 | 29 | var config = new Configuration( 30 | writeKey: "123", 31 | storageProvider: new DefaultStorageProvider("tests"), 32 | autoAddSegmentDestination: false, 33 | useSynchronizeDispatcher: true, 34 | httpClientProvider: new MockHttpClientProvider(mockHttpClient) 35 | ); 36 | _analytics = new Analytics(config); 37 | _metadataPlugin = new DestinationMetadataPlugin(); 38 | _metadataPlugin.Configure(_analytics); 39 | _metadataPlugin.Update(_settings, UpdateType.Initial); 40 | } 41 | 42 | [Fact] 43 | public void TestBundled() 44 | { 45 | var a = new StubDestinationPlugin("A"); 46 | var b = new StubDestinationPlugin("B"); 47 | var c = new StubDestinationPlugin("C"); 48 | _analytics.Add(a); 49 | _analytics.Add(b); 50 | _analytics.Add(c); 51 | _analytics.ManuallyEnableDestination(a); 52 | _analytics.ManuallyEnableDestination(b); 53 | _analytics.ManuallyEnableDestination(c); 54 | 55 | var trackEvent = new TrackEvent("test", new JsonObject()); 56 | RawEvent actual = _metadataPlugin.Execute(trackEvent); 57 | 58 | Assert.Equal(3, actual._metadata.Bundled.Count); 59 | Assert.Equal(0, actual._metadata.Unbundled.Count); 60 | Assert.Equal(0, actual._metadata.BundledIds.Count); 61 | } 62 | 63 | [Fact] 64 | public void TestIntegrationNotInBundled() 65 | { 66 | _settings.Integrations.Add("a", "test"); 67 | _settings.Integrations.Add("b", "test"); 68 | _settings.Integrations.Add("c", "test"); 69 | _metadataPlugin.Update(_settings, UpdateType.Refresh); 70 | 71 | var trackEvent = new TrackEvent("test", new JsonObject()); 72 | RawEvent actual = _metadataPlugin.Execute(trackEvent); 73 | 74 | Assert.Equal(0, actual._metadata.Bundled.Count); 75 | Assert.Equal(3, actual._metadata.Unbundled.Count); 76 | Assert.Equal(0, actual._metadata.BundledIds.Count); 77 | } 78 | 79 | [Fact] 80 | public void TestUnbundledIntegrations() 81 | { 82 | _metadataPlugin.Update(new Settings 83 | { 84 | Integrations = new JsonObject 85 | { 86 | ["Segment.io"] = new JsonObject 87 | { 88 | ["unbundledIntegrations"] = new JsonArray 89 | { 90 | "a", "b", "c" 91 | } 92 | } 93 | } 94 | }, UpdateType.Refresh); 95 | 96 | var trackEvent = new TrackEvent("test", new JsonObject()); 97 | RawEvent actual = _metadataPlugin.Execute(trackEvent); 98 | 99 | Assert.Equal(0, actual._metadata.Bundled.Count); 100 | Assert.Equal(3, actual._metadata.Unbundled.Count); 101 | Assert.Equal(0, actual._metadata.BundledIds.Count); 102 | } 103 | 104 | [Fact] 105 | public void TestCombination() 106 | { 107 | // bundled 108 | var a = new StubDestinationPlugin("A"); 109 | _analytics.Add(a); 110 | _analytics.ManuallyEnableDestination(a); 111 | 112 | 113 | _metadataPlugin.Update(new Settings 114 | { 115 | Integrations = new JsonObject 116 | { 117 | // IntegrationNotInBundled 118 | ["b"] = "test", 119 | ["c"] = "test", 120 | ["Segment.io"] = new JsonObject 121 | { 122 | ["unbundledIntegrations"] = new JsonArray 123 | { 124 | // UnbundledIntegrations 125 | "d", "e", "f" 126 | } 127 | } 128 | } 129 | }, UpdateType.Refresh); 130 | 131 | var trackEvent = new TrackEvent("test", new JsonObject()); 132 | RawEvent actual = _metadataPlugin.Execute(trackEvent); 133 | 134 | Assert.Equal(1, actual._metadata.Bundled.Count); 135 | Assert.Equal(5, actual._metadata.Unbundled.Count); 136 | Assert.Equal(0, actual._metadata.BundledIds.Count); 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /Tests/Policies/CountFlushPolicyTest.cs: -------------------------------------------------------------------------------- 1 | using Segment.Analytics; 2 | using Segment.Analytics.Policies; 3 | using Xunit; 4 | 5 | namespace Tests.Policies 6 | { 7 | public class CountFlushPolicyTest 8 | { 9 | [Fact] 10 | public void TestConstructor() 11 | { 12 | Assert.Equal(20, new CountFlushPolicy().FlushAt); 13 | Assert.Equal(30, new CountFlushPolicy(30).FlushAt); 14 | 15 | // ignores for values less than 1 16 | Assert.Equal(20, new CountFlushPolicy(0).FlushAt); 17 | Assert.Equal(20, new CountFlushPolicy(-1).FlushAt); 18 | Assert.Equal(20, new CountFlushPolicy(-2439872).FlushAt); 19 | } 20 | 21 | [Fact] 22 | public void TestFlush() 23 | { 24 | int flushAt = 10; 25 | var policy = new CountFlushPolicy(flushAt); 26 | 27 | // Should NOT flush before any events 28 | Assert.False(policy.ShouldFlush()); 29 | 30 | // all the first 9 events should not cause the policy to be flushed 31 | for (int i = 1; i < flushAt; i++) 32 | { 33 | policy.UpdateState(new ScreenEvent("test")); 34 | Assert.False(policy.ShouldFlush()); 35 | } 36 | 37 | // next event should trigger the flush 38 | policy.UpdateState(new ScreenEvent("test")); 39 | Assert.True(policy.ShouldFlush()); 40 | 41 | // Even if we somehow go over the flushAt event limit, the policy should still want to flush 42 | // events 43 | policy.UpdateState(new ScreenEvent("test")); 44 | Assert.True(policy.ShouldFlush()); 45 | 46 | // Only when we reset the policy will it not want to flush 47 | policy.Reset(); 48 | Assert.False(policy.ShouldFlush()); 49 | 50 | // The policy will then be ready to count another N events 51 | for (int i = 1; i < flushAt; i++) 52 | { 53 | policy.UpdateState(new ScreenEvent("test")); 54 | Assert.False(policy.ShouldFlush()); 55 | } 56 | 57 | // but once again the next event will trigger a flush request 58 | policy.UpdateState(new ScreenEvent("test")); 59 | Assert.True(policy.ShouldFlush()); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Tests/Policies/FrequencyFlushPolicyTest.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Moq; 3 | using Segment.Analytics; 4 | using Segment.Analytics.Policies; 5 | using Segment.Concurrent; 6 | using Xunit; 7 | 8 | namespace Tests.Policies 9 | { 10 | public class FrequencyFlushPolicyTest 11 | { 12 | private Mock _analytics; 13 | private FrequencyFlushPolicy _policy; 14 | 15 | public FrequencyFlushPolicyTest() 16 | { 17 | var config = new Configuration( 18 | writeKey: "123", 19 | autoAddSegmentDestination: false 20 | ); 21 | 22 | _policy = new FrequencyFlushPolicy(1000); 23 | _analytics = new Mock(config) {CallBase = true}; 24 | _analytics.Setup(o => o.AnalyticsScope).Returns(new Scope()); 25 | _analytics.Setup(o => o.FileIODispatcher) 26 | .Returns(new Dispatcher(new LimitedConcurrencyLevelTaskScheduler(1))); 27 | _analytics.Setup(o => o.Flush()).Verifiable(); 28 | } 29 | 30 | [Fact] 31 | public void TestShouldFlush() 32 | { 33 | // FrequencyFlushPolicy.ShouldFlush() should always return false 34 | Assert.False(_policy.ShouldFlush()); 35 | 36 | _policy.Schedule(_analytics.Object); 37 | Assert.False(_policy.ShouldFlush()); 38 | 39 | _policy.Unschedule(); 40 | Assert.False(_policy.ShouldFlush()); 41 | } 42 | 43 | [Fact] 44 | public async void TestFlushAtScheduled() 45 | { 46 | _policy.FlushIntervalInMills = 30 * 1000; 47 | _policy.Schedule(_analytics.Object); 48 | await Task.Delay(2500); 49 | _analytics.Verify(o => o.Flush(), Times.Once); 50 | } 51 | 52 | [Fact] 53 | public async void TestFlushPeriodically() 54 | { 55 | _policy.Schedule(_analytics.Object); 56 | await Task.Delay(1500); 57 | _analytics.Verify(o => o.Flush(), Times.Between(1, 2, Range.Inclusive)); 58 | } 59 | 60 | [Fact] 61 | public async void TestReschedule() 62 | { 63 | _policy.Schedule(_analytics.Object); 64 | await Task.Delay(1500); 65 | _analytics.Verify(o => o.Flush(), Times.Between(1, 2, Range.Inclusive)); 66 | 67 | _policy.Unschedule(); 68 | await Task.Delay(1500); 69 | // now that it is unscheduled, the count of Flush call should not be increased. 70 | _analytics.Verify(o => o.Flush(), Times.Between(1, 2, Range.Inclusive)); 71 | 72 | _policy.Schedule(_analytics.Object); 73 | await Task.Delay(1500); 74 | // now that it is scheduled again, the count of Flush should resume increasing. 75 | _analytics.Verify(o => o.Flush(), Times.AtLeast(3)); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Tests/Policies/StartupFlushPolicyTest.cs: -------------------------------------------------------------------------------- 1 | using Segment.Analytics; 2 | using Segment.Analytics.Policies; 3 | using Xunit; 4 | 5 | namespace Tests.Policies 6 | { 7 | public class StartupFlushPolicyTest 8 | { 9 | [Fact] 10 | public void TestFlush() 11 | { 12 | var policy = new StartupFlushPolicy(); 13 | 14 | // Should only flush the first time requested! 15 | Assert.True(policy.ShouldFlush()); 16 | 17 | // Should now not flush any more! 18 | for (int i = 0; i < 10; i++) 19 | { 20 | Assert.False(policy.ShouldFlush()); 21 | } 22 | 23 | // even you call reset; the policy will not want to flush. 24 | policy.Reset(); 25 | Assert.False(policy.ShouldFlush()); 26 | 27 | // Adding events has no effect and does not cause the policy to flush 28 | policy.UpdateState(new ScreenEvent("test")); 29 | Assert.False(policy.ShouldFlush()); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Tests/Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1;;net5.0;net6.0;net46 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | all 17 | 18 | 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | all 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Tests/Utilities/LoggingTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Moq; 3 | using Segment.Analytics; 4 | using Segment.Analytics.Utilities; 5 | using Segment.Serialization; 6 | using Segment.Sovran; 7 | using Xunit; 8 | 9 | namespace Tests.Utilities 10 | { 11 | public class LoggingTest 12 | { 13 | 14 | private readonly Store _store; 15 | 16 | private Settings _settings; 17 | 18 | private readonly Configuration _configuration; 19 | 20 | private readonly Mock _storage; 21 | 22 | public LoggingTest() 23 | { 24 | _store = new Store(true); 25 | _settings = new Settings 26 | { 27 | Integrations = new JsonObject 28 | { 29 | ["foo"] = "bar" 30 | } 31 | }; 32 | _configuration = new Configuration( 33 | writeKey: "123", 34 | autoAddSegmentDestination: false, 35 | useSynchronizeDispatcher: true, 36 | defaultSettings: _settings 37 | ); 38 | _storage = new Mock(); 39 | } 40 | 41 | [Fact] 42 | public void TestLog() 43 | { 44 | var logger = new Mock(); 45 | Analytics.Logger = logger.Object; 46 | var exception = new Exception(); 47 | _storage 48 | .Setup(o => o.Read(It.IsAny())) 49 | .Throws(exception); 50 | 51 | Segment.Analytics.System.DefaultState(_configuration, _storage.Object); 52 | 53 | logger.Verify(o => o.Log(LogLevel.Error, exception, It.IsAny()), Times.Exactly(1)); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Tests/Utilities/SettingsTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Moq; 7 | using Segment.Analytics; 8 | using Segment.Analytics.Utilities; 9 | using Segment.Serialization; 10 | using Segment.Sovran; 11 | using Tests.Utils; 12 | using Xunit; 13 | 14 | namespace Tests.Utilities 15 | { 16 | public class SettingsTest 17 | { 18 | private readonly Analytics _analytics; 19 | 20 | private Mock _storage; 21 | 22 | private Settings? _settings; 23 | 24 | public SettingsTest() 25 | { 26 | _settings = JsonUtility.FromJson( 27 | "{\"integrations\":{\"Segment.io\":{\"apiKey\":\"1vNgUqwJeCHmqgI9S1sOm9UHCyfYqbaQ\"}},\"plan\":{},\"edgeFunction\":{}}"); 28 | 29 | var mockHttpClient = new Mock(null, null, null); 30 | mockHttpClient 31 | .Setup(httpClient => httpClient.Settings()) 32 | .ReturnsAsync(_settings); 33 | 34 | _storage = new Mock(); 35 | _storage.Setup(Storage => Storage.RemoveFile("")).Returns(true); 36 | _storage.Setup(Storage => Storage.Read(StorageConstants.Events)).Returns("test,foo"); 37 | 38 | var config = new Configuration( 39 | writeKey: "123", 40 | storageProvider: new MockStorageProvider(_storage), 41 | autoAddSegmentDestination: false, 42 | useSynchronizeDispatcher: true, 43 | httpClientProvider: new MockHttpClientProvider(mockHttpClient) 44 | ); 45 | _analytics = new Analytics(config); 46 | } 47 | 48 | [Fact] 49 | public async Task PluginUpdatesWithInitalOnlyOnce() 50 | { 51 | var plugin = new Mock(); 52 | plugin.Setup(o => o.Key).Returns("mock"); 53 | plugin.Setup(o => o.Analytics).Returns(_analytics); // This would normally be set by Configure 54 | 55 | // Ideally we'd interrupt init somehow to test adding plugins before, but we don't have a good way 56 | _analytics.Add(plugin.Object); 57 | plugin.Verify(p => p.Update(It.IsAny(), UpdateType.Initial), Times.Once); 58 | plugin.Verify(p => p.Update(It.IsAny(), UpdateType.Refresh), Times.Never); 59 | 60 | // load settings 61 | await _analytics.CheckSettings(); 62 | plugin.Verify(p => p.Update(It.IsAny(), UpdateType.Initial), Times.Once); 63 | plugin.Verify(p => p.Update(It.IsAny(), UpdateType.Refresh), Times.Once); 64 | Segment.Analytics.System system = await _analytics.Store.CurrentState(); 65 | Assert.Contains(plugin.Object.GetHashCode(), system._initializedPlugins); 66 | 67 | // readd plugin (why would you do this?) 68 | _analytics.Remove(plugin.Object); 69 | _analytics.Add(plugin.Object); 70 | plugin.Verify(p => p.Update(It.IsAny(), UpdateType.Initial), Times.Exactly(2)); 71 | plugin.Verify(p => p.Update(It.IsAny(), UpdateType.Refresh), Times.Once); 72 | 73 | // load settings again 74 | await _analytics.CheckSettings(); 75 | plugin.Verify(p => p.Update(It.IsAny(), UpdateType.Initial), Times.Exactly(2)); 76 | plugin.Verify(p => p.Update(It.IsAny(), UpdateType.Refresh), Times.Exactly(2)); 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /Tests/Utilities/SystemInfoTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Segment.Analytics.Utilities; 3 | using Xunit; 4 | 5 | namespace Tests.Utilities 6 | { 7 | public class SystemInfoTest 8 | { 9 | 10 | [Fact] 11 | public void GetPlatformTest() 12 | { 13 | string sysinfo = SystemInfo.GetPlatform(); 14 | 15 | Assert.NotNull(sysinfo); 16 | } 17 | 18 | [Fact] 19 | public void GetOSTest() 20 | { 21 | string sysinfo = SystemInfo.GetOs(); 22 | 23 | Assert.NotNull(sysinfo); 24 | } 25 | 26 | #if NETSTANDARD2_0 27 | [Fact] 28 | public void GetAppFolderTest() 29 | { 30 | string path = SystemInfo.GetAppFolder(); 31 | 32 | Assert.Equal(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), path); 33 | } 34 | #endif 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Tests/Utils/Stubs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | using Moq; 4 | using Segment.Analytics; 5 | using Segment.Analytics.Utilities; 6 | using Segment.Serialization; 7 | 8 | namespace Tests.Utils 9 | { 10 | class FooBar : ISerializable 11 | { 12 | public string foo => "bar"; 13 | 14 | public void GetObjectData(SerializationInfo info, StreamingContext context) 15 | { 16 | info.AddValue("foo", "bar"); 17 | } 18 | 19 | public override bool Equals(object obj) 20 | { 21 | if (obj is FooBar fooBar) 22 | { 23 | return foo.Equals(fooBar.foo); 24 | } 25 | 26 | return false; 27 | } 28 | 29 | public override int GetHashCode() 30 | { 31 | return foo.GetHashCode(); 32 | } 33 | 34 | public JsonObject GetJsonObject() 35 | { 36 | return new JsonObject 37 | { 38 | ["foo"] = foo 39 | }; 40 | } 41 | } 42 | 43 | public class StubEventPlugin : EventPlugin 44 | { 45 | public override PluginType Type => PluginType.Before; 46 | } 47 | 48 | public class StubAfterEventPlugin : EventPlugin 49 | { 50 | public override PluginType Type => PluginType.After; 51 | } 52 | 53 | public class StubDestinationPlugin : DestinationPlugin 54 | { 55 | public override string Key { get; } 56 | 57 | public StubDestinationPlugin(string key) 58 | { 59 | Key = key; 60 | } 61 | } 62 | 63 | public class MockStorageProvider : IStorageProvider 64 | { 65 | public Mock Mock { get; set; } 66 | 67 | public MockStorageProvider(Mock mock) => Mock = mock; 68 | 69 | public IStorage CreateStorage(params object[] parameters) 70 | { 71 | return Mock.Object; 72 | } 73 | } 74 | 75 | public class MockHttpClientProvider : IHTTPClientProvider 76 | { 77 | public Mock Mock { get; set; } 78 | 79 | public MockHttpClientProvider(Mock mock) => Mock = mock; 80 | 81 | public HTTPClient CreateHTTPClient(string apiKey, string apiHost = null, string cdnHost = null) 82 | { 83 | return Mock.Object; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /upm_release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | absolute_path() { 4 | DIR="$(echo "$(cd "$(dirname "$1")"; pwd)")" 5 | case $(basename $1) in 6 | ..) echo "$(dirname $DIR)";; 7 | .) echo "$DIR";; 8 | *) echo "$DIR/$(basename $1)";; 9 | esac 10 | } 11 | 12 | # checking if a directory is provided 13 | if [ -z "$1" ] ; then 14 | echo "Please provide a directory to setup the sandbox. The directory should be outside analytics-csharp's directory" 15 | echo "Usage: $0 " 16 | exit 1 17 | fi 18 | if ! [ -d "$1" ]; then 19 | echo "$1 does not exist." 20 | exit 1 21 | fi 22 | if [[ "$(absolute_path "$1")" = $PWD* ]]; then 23 | echo "Please provide a directory outside analytics-csharp's directory" 24 | exit 1 25 | fi 26 | cd "$1" || exit 27 | 28 | 29 | echo "checking required tools..." 30 | 31 | # checking required tools 32 | if ! command -v git &> /dev/null 33 | then 34 | echo "git could not be found" 35 | exit 1 36 | fi 37 | if ! command -v nuget &> /dev/null 38 | then 39 | echo "nuget could not be found" 40 | exit 1 41 | fi 42 | if ! command -v jq &> /dev/null 43 | then 44 | echo "jq could not be found" 45 | exit 1 46 | fi 47 | 48 | echo "looking for unity executable path..." 49 | UNITY=$(find /Applications/Unity -type f -name 'Unity' | head -n 1) 50 | echo "Unity executable found at $UNITY" 51 | if [ -z "$UNITY" ] 52 | then 53 | echo "unity executable is not found. make sure you have installed unity" 54 | exit 55 | else 56 | echo "Unity executable found at $UNITY" 57 | fi 58 | 59 | echo "setting up release sandbox ..." 60 | rm -rf sandbox 61 | mkdir -m 777 sandbox 62 | cd sandbox 63 | 64 | # download analytics-csharp, so it's isolated 65 | git clone https://github.com/segmentio/Analytics-CSharp.git 66 | cd Analytics-CSharp || exit 67 | 68 | echo "fetching the current version of project ..." 69 | VERSION=$(grep '' Analytics-CSharp/Analytics-CSharp.csproj | sed "s@.*\(.*\).*@\1@") 70 | echo "copy README.md ..." 71 | README=$( Analytics-CSharp/package.json 85 | echo "$README" > Analytics-CSharp/README.md 86 | # remove all files in Plugins folder recursively 87 | rm -rf Analytics-CSharp/Plugins/* 88 | # download analytics-csharp and its dependencies from nuget 89 | nuget install Segment.Analytics.CSharp -Version "$VERSION" -OutputDirectory Analytics-CSharp/Plugins 90 | # remove dependencies that are not required 91 | declare -a deps=(Analytics-CSharp/Plugins/Coroutine.NET.* Analytics-CSharp/Plugins/Serialization.NET.* Analytics-CSharp/Plugins/Sovran.NET.* Analytics-CSharp/Plugins/Segment.Analytics.CSharp.*) 92 | for dir in Analytics-CSharp/Plugins/*; do 93 | if [ -d "$dir" ]; then 94 | in_deps=false 95 | for dep in "${deps[@]}"; do 96 | if [[ $dir == $dep ]]; then 97 | in_deps=true 98 | break 99 | fi 100 | done 101 | 102 | if [ $in_deps == false ]; then 103 | rm -rf "$dir" 104 | fi 105 | fi 106 | done 107 | # loop over all the libs and remove any non-netstandard1.3 libs 108 | for dir in Analytics-CSharp/Plugins/*; do 109 | if [ -d "$dir" ]; then 110 | for lib in "$dir"/lib/*; do 111 | if [ "$lib" != "$dir/lib/netstandard1.3" ]; then 112 | echo $lib 113 | rm -rf "$lib" 114 | fi 115 | done 116 | fi 117 | done 118 | 119 | echo "generating meta files ..." 120 | # launch unity to create a dummy head project 121 | "$UNITY" -batchmode -quit -createProject dummy 122 | # update the manifest of dummy head to import the package 123 | echo "$(jq '.dependencies += {"com.segment.analytics.csharp": "file:../../Analytics-CSharp"}' dummy/Packages/manifest.json)" > dummy/Packages/manifest.json 124 | # launch unity in quit mode to generate meta files 125 | "$UNITY" -batchmode -quit -projectPath dummy 126 | 127 | echo "releasing ..." 128 | # commit all the changes 129 | cd Analytics-CSharp || exit 130 | git add . 131 | git commit -m "prepare release $VERSION" 132 | # create and push a new tag, openupm will pick up this new tag and release it automatically 133 | git tag unity/"$VERSION" 134 | git push && git push --tags 135 | cd .. 136 | 137 | echo "cleaning up" 138 | # clean up sandbox 139 | cd .. 140 | rm -rf sandbox 141 | 142 | echo "done!" 143 | 144 | 145 | 146 | --------------------------------------------------------------------------------