├── .github
├── ISSUE_TEMPLATE
│ ├── back-up-firestore-to-storage-issue.md
│ └── trigger-github-issues-from-crashlytics-issue.md
├── script
│ └── extract_specific_changelog.sh
└── workflows
│ └── release.yml
├── .gitignore
├── README.md
├── _emulator
├── .firebaserc
├── .gitignore
├── firebase.json
├── firestore.indexes.json
├── firestore.rules
├── functions
│ ├── .gitignore
│ ├── index.js
│ ├── package-lock.json
│ └── package.json
└── storage.rules
├── _example
└── flutter_crashlytics_test
│ ├── .firebaserc
│ ├── .gitignore
│ ├── .metadata
│ ├── README.md
│ ├── analysis_options.yaml
│ ├── android
│ ├── .gitignore
│ ├── app
│ │ ├── build.gradle.kts
│ │ ├── google-services.json
│ │ └── src
│ │ │ ├── debug
│ │ │ └── AndroidManifest.xml
│ │ │ ├── main
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── kotlin
│ │ │ │ └── com
│ │ │ │ │ └── htsuruo
│ │ │ │ │ └── flutter_crashlytics_test
│ │ │ │ │ └── MainActivity.kt
│ │ │ └── res
│ │ │ │ ├── drawable-v21
│ │ │ │ └── launch_background.xml
│ │ │ │ ├── drawable
│ │ │ │ └── launch_background.xml
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── values-night
│ │ │ │ └── styles.xml
│ │ │ │ └── values
│ │ │ │ └── styles.xml
│ │ │ └── profile
│ │ │ └── AndroidManifest.xml
│ ├── build.gradle.kts
│ ├── gradle.properties
│ ├── gradle
│ │ └── wrapper
│ │ │ └── gradle-wrapper.properties
│ └── settings.gradle.kts
│ ├── firebase.json
│ ├── lib
│ ├── firebase_options.dart
│ └── main.dart
│ ├── pubspec.lock
│ └── pubspec.yaml
├── back-up-firestore-to-storage
├── CHANGELOG.md
├── POSTINSTALL.md
├── PREINSTALL.md
├── README.md
├── extension.yaml
├── functions
│ ├── .gitignore
│ ├── .mocharc.json
│ ├── integration-tests
│ │ ├── .firebaserc
│ │ ├── .gitignore
│ │ ├── data
│ │ │ ├── firebase-export-metadata.json
│ │ │ ├── firestore_export
│ │ │ │ ├── all_namespaces
│ │ │ │ │ └── all_kinds
│ │ │ │ │ │ ├── all_namespaces_all_kinds.export_metadata
│ │ │ │ │ │ └── output-0
│ │ │ │ └── firestore_export.overall_export_metadata
│ │ │ └── storage_export
│ │ │ │ └── buckets.json
│ │ ├── debug.sh
│ │ ├── firebase.json
│ │ ├── integration-test.spec.ts
│ │ └── storage.rules
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ │ ├── format.ts
│ │ └── index.ts
│ ├── test
│ │ └── timestamp_format.spec.ts
│ ├── tsconfig.dev.json
│ └── tsconfig.json
└── icon.png
└── trigger-github-issues-from-crashlytics
├── CHANGELOG.md
├── POSTINSTALL.md
├── PREINSTALL.md
├── README.md
├── extension.yaml
├── functions
├── .gitignore
├── .mocharc.json
├── integration-tests
│ ├── .firebaserc
│ ├── firebase.json
│ └── integration-test.spec.ts
├── package-lock.json
├── package.json
├── src
│ ├── github_api.ts
│ ├── index.ts
│ └── types.ts
├── tsconfig.dev.json
└── tsconfig.json
└── icon.png
/.github/ISSUE_TEMPLATE/back-up-firestore-to-storage-issue.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Back up Firestore to Storage issue
3 | about: A issue for Back up Firestore to Storage extensions
4 | title: "[back-up-firestore-to-storage] Here is issue title"
5 | labels: ''
6 | assignees: htsuruo
7 |
8 | ---
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/trigger-github-issues-from-crashlytics-issue.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Trigger GitHub issues from Crashlytics issue
3 | about: A issue for Trigger GitHub issues from Crashlytics extension
4 | title: "[trigger-github-issues-from-crashlytics] Here is issue title"
5 | labels: ''
6 | assignees: htsuruo
7 |
8 | ---
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.github/script/extract_specific_changelog.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Generated by ChatGPT
3 |
4 | target=""
5 | version=""
6 |
7 | # オプション解析を行う関数
8 | parse_options() {
9 | for arg in "$@"; do
10 | case $arg in
11 | --target=*)
12 | target="${arg#*=}"
13 | ;;
14 | --version=*)
15 | version="${arg#*=}"
16 | ;;
17 | esac
18 | done
19 | }
20 |
21 | # オプション解析の呼び出し
22 | parse_options "$@"
23 |
24 | # 必須オプションのチェック
25 | if [[ -z $target || -z $version ]]; then
26 | echo "Please input target extension or version."
27 | exit 1
28 | fi
29 |
30 |
31 | # run example: ./extract_specific_changelog.sh --target=back-up-firestore-to-storage --version=0.0.4
32 | extract_specific_version() {
33 | local is_matching_version=false
34 | local heading=""
35 | local content=""
36 |
37 | while IFS= read -r line; do
38 | if [[ $line =~ ^## ]]; then
39 | if [[ $is_matching_version = true ]]; then
40 | break
41 | fi
42 | heading="$line"
43 | local v=$(echo "$line" | sed 's/^## *//')
44 | if [[ $v == *"$version"* ]]; then
45 | is_matching_version=true
46 | fi
47 | elif [[ $is_matching_version = true ]]; then
48 | if [[ $line =~ ^- ]]; then
49 | content+="- ${line#*- }"$'\n'
50 | fi
51 | fi
52 | done < "./$target/CHANGELOG.md"
53 |
54 | if [[ $is_matching_version = true ]]; then
55 | echo "$heading"
56 | echo ""
57 | echo "$content"
58 | else
59 | echo "$version does not found in CHANGELOG."
60 | fi
61 | }
62 |
63 | extract_specific_version
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | extension:
7 | type: choice
8 | options:
9 | - 'back-up-firestore-to-storage'
10 | - 'trigger-github-issues-from-crashlytics'
11 | required: true
12 | description: 'The name of the extension'
13 | version:
14 | type: string
15 | required: true
16 | description: 'The version of the extension'
17 | commit:
18 | type: string
19 | required: false
20 | description: 'Target the commit id (Defaults to HEAD)'
21 |
22 | env:
23 | TARGET: ${{ github.event.inputs.extension }}
24 | VERSION_NUMBER: ${{ github.event.inputs.version }}
25 | TAG_NAME: ${{ github.event.inputs.extension }}/v${{ github.event.inputs.version }}
26 |
27 | jobs:
28 | push-tag:
29 | runs-on: ubuntu-latest
30 | permissions:
31 | contents: write
32 |
33 | steps:
34 | - uses: actions/checkout@v3
35 | with:
36 | ref: ${{ github.event.inputs.commit }}
37 |
38 | - name: Push tag
39 | run: |
40 | git tag ${{ env.TAG_NAME }}
41 | git push origin ${{ env.TAG_NAME }}
42 |
43 | create-draft-release:
44 | needs: push-tag
45 | runs-on: ubuntu-latest
46 | permissions:
47 | contents: write
48 |
49 | steps:
50 | - uses: actions/checkout@v3
51 | with:
52 | fetch-depth: 0
53 |
54 | - name: Extract the specific version CHANGELOG
55 | id: changelog
56 | # GITHUB_OUTPUTやGITHUB_ENVでは改行が扱えないので、一旦ファイルに書き出す
57 | run: |
58 | mkdir -p /tmp
59 | ($echo ./.github/script/extract_specific_changelog.sh \
60 | --target=${{ env.TARGET }} \
61 | --version=${{ env.VERSION_NUMBER }}) \
62 | > /tmp/body.md
63 |
64 | - name: Show CHANGELOG
65 | run: |
66 | cat /tmp/body.md
67 |
68 | # ref. https://github.com/ncipollo/release-action
69 | - name: Create draft release
70 | uses: ncipollo/release-action@v1
71 | with:
72 | tag: ${{ env.TAG_NAME }}
73 | bodyFile: /tmp/body.md
74 | generateReleaseNotes: true
75 | draft: true
76 | prerelease: false
77 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # firebase-extensions
2 |
3 | This is a collection of Firebase Extensions build by [htsuruo](https://github.com/htsuruo), designed to help you build better apps faster.
4 |
5 | ## List of Extensions
6 |
7 | - [back-up-firestore-to-storage](https://github.com/htsuruo/firebase-extensions/tree/main/back-up-firestore-to-storage)
8 | - Exports Firestore documents to Cloud Storage at any scheduled time.
9 | - [Install in Firebase Console](https://extensions.dev/extensions/htsuruo/back-up-firestore-to-storage)
10 | - [trigger-github-issues-from-crashlytics](https://github.com/htsuruo/firebase-extensions/tree/main/trigger-github-issues-from-crashlytics)
11 | - Automatically creates GitHub Issues triggered by Crashlytics alerts.
12 | - [Install in Firebase Console](https://extensions.dev/extensions/htsuruo/trigger-github-issues-from-crashlytics)
13 |
14 | ## References
15 |
16 | - [firebase/extensions](https://github.com/firebase/extensions)
17 | - [invertase/firebase-extensions](https://github.com/invertase/firebase-extensions)
18 | - [rowyio/firebase-extensions](https://github.com/rowyio/firebase-extensions)
19 | - [yamankatby/firebase-extensions](https://github.com/yamankatby/firebase-extensions)
20 |
--------------------------------------------------------------------------------
/_emulator/.firebaserc:
--------------------------------------------------------------------------------
1 | {
2 | "projects": {
3 | "default": "extensions-dev-3867c"
4 | },
5 | "targets": {},
6 | "etags": {
7 | "extensions-dev-3867c": {
8 | "extensionInstances": {
9 | "trigger-github-issues-from-crashlytics": "1f5a4836dbcd33e06b548ad36e44b14be235a9e11ec8cf981d42f08fbfe4740d",
10 | "back-up-firestore-to-storage": "e0d95b5e8100646a4fd46cfbc6c6276bbb1705f586806230b0ae48faa0c8331c"
11 | }
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/_emulator/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | firebase-debug.log*
8 | firebase-debug.*.log*
9 |
10 | # Firebase cache
11 | .firebase/
12 |
13 | # Firebase config
14 |
15 | # Uncomment this if you'd like others to create their own Firebase project.
16 | # For a team working on the same Firebase project(s), it is recommended to leave
17 | # it commented so all members can deploy to the same project(s) in .firebaserc.
18 | # .firebaserc
19 |
20 | # Runtime data
21 | pids
22 | *.pid
23 | *.seed
24 | *.pid.lock
25 |
26 | # Directory for instrumented libs generated by jscoverage/JSCover
27 | lib-cov
28 |
29 | # Coverage directory used by tools like istanbul
30 | coverage
31 |
32 | # nyc test coverage
33 | .nyc_output
34 |
35 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
36 | .grunt
37 |
38 | # Bower dependency directory (https://bower.io/)
39 | bower_components
40 |
41 | # node-waf configuration
42 | .lock-wscript
43 |
44 | # Compiled binary addons (http://nodejs.org/api/addons.html)
45 | build/Release
46 |
47 | # Dependency directories
48 | node_modules/
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Optional REPL history
57 | .node_repl_history
58 |
59 | # Output of 'npm pack'
60 | *.tgz
61 |
62 | # Yarn Integrity file
63 | .yarn-integrity
64 |
65 | # dotenv environment variables file
66 | .env
67 |
--------------------------------------------------------------------------------
/_emulator/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "firestore": {
3 | "rules": "firestore.rules",
4 | "indexes": "firestore.indexes.json"
5 | },
6 | "functions": [
7 | {
8 | "source": "functions",
9 | "codebase": "default",
10 | "ignore": [
11 | "node_modules",
12 | ".git",
13 | "firebase-debug.log",
14 | "firebase-debug.*.log"
15 | ]
16 | }
17 | ],
18 | "storage": {
19 | "rules": "storage.rules"
20 | },
21 | "emulators": {
22 | "auth": {
23 | "port": 9099
24 | },
25 | "functions": {
26 | "port": 5001
27 | },
28 | "firestore": {
29 | "port": 8080
30 | },
31 | "database": {
32 | "port": 9000
33 | },
34 | "hosting": {
35 | "port": 5000
36 | },
37 | "pubsub": {
38 | "port": 8085
39 | },
40 | "storage": {
41 | "port": 9199
42 | },
43 | "eventarc": {
44 | "port": 9299
45 | },
46 | "ui": {
47 | "enabled": true
48 | },
49 | "singleProjectMode": true
50 | },
51 | "extensions": {
52 | "trigger-github-issues-from-crashlytics": "../trigger-github-issues-from-crashlytics",
53 | "back-up-firestore-to-storage": "../back-up-firestore-to-storage/"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/_emulator/firestore.indexes.json:
--------------------------------------------------------------------------------
1 | {
2 | "indexes": [],
3 | "fieldOverrides": []
4 | }
5 |
--------------------------------------------------------------------------------
/_emulator/firestore.rules:
--------------------------------------------------------------------------------
1 | service cloud.firestore {
2 | match /databases/{database}/documents {
3 | match /{document=**} {
4 | // This rule allows anyone with your database reference to view, edit,
5 | // and delete all data in your database. It is useful for getting
6 | // started, but it is configured to expire after 30 days because it
7 | // leaves your app open to attackers. At that time, all client
8 | // requests to your database will be denied.
9 | //
10 | // Make sure to write security rules for your app before that time, or
11 | // else all client requests to your database will be denied until you
12 | // update your rules.
13 | allow read, write: if request.time < timestamp.date(2023, 6, 26);
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/_emulator/functions/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
--------------------------------------------------------------------------------
/_emulator/functions/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Import function triggers from their respective submodules:
3 | *
4 | * const {onCall} = require("firebase-functions/v2/https");
5 | * const {onDocumentWritten} = require("firebase-functions/v2/firestore");
6 | *
7 | * See a full list of supported triggers at https://firebase.google.com/docs/functions
8 | */
9 |
10 |
11 | // Create and deploy your first functions
12 | // https://firebase.google.com/docs/functions/get-started
13 |
14 | // exports.helloWorld = onRequest((request, response) => {
15 | // logger.info("Hello logs!", {structuredData: true});
16 | // response.send("Hello from Firebase!");
17 | // });
18 |
--------------------------------------------------------------------------------
/_emulator/functions/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "functions",
3 | "description": "Cloud Functions for Firebase",
4 | "scripts": {
5 | "serve": "firebase emulators:start --only functions",
6 | "shell": "firebase functions:shell",
7 | "start": "npm run shell",
8 | "deploy": "firebase deploy --only functions",
9 | "logs": "firebase functions:log"
10 | },
11 | "engines": {
12 | "node": "18"
13 | },
14 | "main": "index.js",
15 | "dependencies": {
16 | "firebase-admin": "^11.8.0",
17 | "firebase-functions": "^4.3.1"
18 | },
19 | "devDependencies": {
20 | "firebase-functions-test": "^3.1.0"
21 | },
22 | "private": true
23 | }
24 |
--------------------------------------------------------------------------------
/_emulator/storage.rules:
--------------------------------------------------------------------------------
1 | rules_version = '2';
2 |
3 | // Craft rules based on data in your Firestore database
4 | // allow write: if firestore.get(
5 | // /databases/(default)/documents/users/$(request.auth.uid)).data.isAdmin;
6 | service firebase.storage {
7 | match /b/{bucket}/o {
8 | match /{allPaths=**} {
9 | allow read, write: if false;
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/_example/flutter_crashlytics_test/.firebaserc:
--------------------------------------------------------------------------------
1 | ../../_emulator/.firebaserc
--------------------------------------------------------------------------------
/_example/flutter_crashlytics_test/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .build/
9 | .buildlog/
10 | .history
11 | .svn/
12 | .swiftpm/
13 | migrate_working_dir/
14 |
15 | # IntelliJ related
16 | *.iml
17 | *.ipr
18 | *.iws
19 | .idea/
20 |
21 | # The .vscode folder contains launch configuration and tasks you configure in
22 | # VS Code which you may wish to be included in version control, so this line
23 | # is commented out by default.
24 | #.vscode/
25 |
26 | # Flutter/Dart/Pub related
27 | **/doc/api/
28 | **/ios/Flutter/.last_build_id
29 | .dart_tool/
30 | .flutter-plugins
31 | .flutter-plugins-dependencies
32 | .pub-cache/
33 | .pub/
34 | /build/
35 |
36 | # Symbolication related
37 | app.*.symbols
38 |
39 | # Obfuscation related
40 | app.*.map.json
41 |
42 | # Android Studio will place build artifacts here
43 | /android/app/debug
44 | /android/app/profile
45 | /android/app/release
46 |
--------------------------------------------------------------------------------
/_example/flutter_crashlytics_test/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: "ea121f8859e4b13e47a8f845e4586164519588bc"
8 | channel: "stable"
9 |
10 | project_type: app
11 |
12 | # Tracks metadata for the flutter migrate command
13 | migration:
14 | platforms:
15 | - platform: root
16 | create_revision: ea121f8859e4b13e47a8f845e4586164519588bc
17 | base_revision: ea121f8859e4b13e47a8f845e4586164519588bc
18 | - platform: android
19 | create_revision: ea121f8859e4b13e47a8f845e4586164519588bc
20 | base_revision: ea121f8859e4b13e47a8f845e4586164519588bc
21 | - platform: macos
22 | create_revision: ea121f8859e4b13e47a8f845e4586164519588bc
23 | base_revision: ea121f8859e4b13e47a8f845e4586164519588bc
24 |
25 | # User provided section
26 |
27 | # List of Local paths (relative to this file) that should be
28 | # ignored by the migrate tool.
29 | #
30 | # Files that are not part of the templates will be ignored by default.
31 | unmanaged_files:
32 | - 'lib/main.dart'
33 | - 'ios/Runner.xcodeproj/project.pbxproj'
34 |
--------------------------------------------------------------------------------
/_example/flutter_crashlytics_test/README.md:
--------------------------------------------------------------------------------
1 | # flutter_crashlytics_test
2 |
3 | A new Flutter project.
4 |
--------------------------------------------------------------------------------
/_example/flutter_crashlytics_test/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # https://pub.dev/packages/pedantic_mono
2 | include: package:pedantic_mono/analysis_options.yaml
3 |
--------------------------------------------------------------------------------
/_example/flutter_crashlytics_test/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 | .cxx/
9 |
10 | # Remember to never publicly share your keystore.
11 | # See https://flutter.dev/to/reference-keystore
12 | key.properties
13 | **/*.keystore
14 | **/*.jks
15 |
--------------------------------------------------------------------------------
/_example/flutter_crashlytics_test/android/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.application")
3 | // START: FlutterFire Configuration
4 | id("com.google.gms.google-services")
5 | id("com.google.firebase.crashlytics")
6 | // END: FlutterFire Configuration
7 | id("kotlin-android")
8 | // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
9 | id("dev.flutter.flutter-gradle-plugin")
10 | }
11 |
12 | android {
13 | namespace = "com.htsuruo.flutter_crashlytics_test"
14 | compileSdk = flutter.compileSdkVersion
15 | ndkVersion = flutter.ndkVersion
16 |
17 | compileOptions {
18 | sourceCompatibility = JavaVersion.VERSION_11
19 | targetCompatibility = JavaVersion.VERSION_11
20 | }
21 |
22 | kotlinOptions {
23 | jvmTarget = JavaVersion.VERSION_11.toString()
24 | }
25 |
26 | defaultConfig {
27 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
28 | applicationId = "com.htsuruo.flutter_crashlytics_test"
29 | // You can update the following values to match your application needs.
30 | // For more information, see: https://flutter.dev/to/review-gradle-config.
31 | minSdk = flutter.minSdkVersion
32 | targetSdk = flutter.targetSdkVersion
33 | versionCode = flutter.versionCode
34 | versionName = flutter.versionName
35 | }
36 |
37 | buildTypes {
38 | release {
39 | // TODO: Add your own signing config for the release build.
40 | // Signing with the debug keys for now, so `flutter run --release` works.
41 | signingConfig = signingConfigs.getByName("debug")
42 | }
43 | }
44 | }
45 |
46 | flutter {
47 | source = "../.."
48 | }
49 |
--------------------------------------------------------------------------------
/_example/flutter_crashlytics_test/android/app/google-services.json:
--------------------------------------------------------------------------------
1 | {
2 | "project_info": {
3 | "project_number": "162074331013",
4 | "project_id": "extensions-dev-3867c",
5 | "storage_bucket": "extensions-dev-3867c.firebasestorage.app"
6 | },
7 | "client": [
8 | {
9 | "client_info": {
10 | "mobilesdk_app_id": "1:162074331013:android:f5830f1b7bf6ffd8ade6ec",
11 | "android_client_info": {
12 | "package_name": "com.htsuruo.flutter_crashlytics_test"
13 | }
14 | },
15 | "oauth_client": [],
16 | "api_key": [
17 | {
18 | "current_key": "AIzaSyDEmuRvikypsFsnyM71Z-S_EqAwCdkxVwo"
19 | }
20 | ],
21 | "services": {
22 | "appinvite_service": {
23 | "other_platform_oauth_client": []
24 | }
25 | }
26 | }
27 | ],
28 | "configuration_version": "1"
29 | }
--------------------------------------------------------------------------------
/_example/flutter_crashlytics_test/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/_example/flutter_crashlytics_test/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
15 |
19 |
23 |
24 |
25 |
26 |
27 |
28 |
30 |
33 |
34 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/_example/flutter_crashlytics_test/android/app/src/main/kotlin/com/htsuruo/flutter_crashlytics_test/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.htsuruo.flutter_crashlytics_test
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity : FlutterActivity()
6 |
--------------------------------------------------------------------------------
/_example/flutter_crashlytics_test/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/_example/flutter_crashlytics_test/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/_example/flutter_crashlytics_test/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/htsuruo/firebase-extensions/23dc5d1e2f59bfdda41e86164c1b2b330ee4030b/_example/flutter_crashlytics_test/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/_example/flutter_crashlytics_test/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/htsuruo/firebase-extensions/23dc5d1e2f59bfdda41e86164c1b2b330ee4030b/_example/flutter_crashlytics_test/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/_example/flutter_crashlytics_test/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/htsuruo/firebase-extensions/23dc5d1e2f59bfdda41e86164c1b2b330ee4030b/_example/flutter_crashlytics_test/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/_example/flutter_crashlytics_test/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/htsuruo/firebase-extensions/23dc5d1e2f59bfdda41e86164c1b2b330ee4030b/_example/flutter_crashlytics_test/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/_example/flutter_crashlytics_test/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/htsuruo/firebase-extensions/23dc5d1e2f59bfdda41e86164c1b2b330ee4030b/_example/flutter_crashlytics_test/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/_example/flutter_crashlytics_test/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/_example/flutter_crashlytics_test/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/_example/flutter_crashlytics_test/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/_example/flutter_crashlytics_test/android/build.gradle.kts:
--------------------------------------------------------------------------------
1 | allprojects {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | }
6 | }
7 |
8 | val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
9 | rootProject.layout.buildDirectory.value(newBuildDir)
10 |
11 | subprojects {
12 | val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
13 | project.layout.buildDirectory.value(newSubprojectBuildDir)
14 | }
15 | subprojects {
16 | project.evaluationDependsOn(":app")
17 | }
18 |
19 | tasks.register("clean") {
20 | delete(rootProject.layout.buildDirectory)
21 | }
22 |
--------------------------------------------------------------------------------
/_example/flutter_crashlytics_test/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/_example/flutter_crashlytics_test/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
6 |
--------------------------------------------------------------------------------
/_example/flutter_crashlytics_test/android/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | val flutterSdkPath = run {
3 | val properties = java.util.Properties()
4 | file("local.properties").inputStream().use { properties.load(it) }
5 | val flutterSdkPath = properties.getProperty("flutter.sdk")
6 | require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
7 | flutterSdkPath
8 | }
9 |
10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
11 |
12 | repositories {
13 | google()
14 | mavenCentral()
15 | gradlePluginPortal()
16 | }
17 | }
18 |
19 | plugins {
20 | id("dev.flutter.flutter-plugin-loader") version "1.0.0"
21 | id("com.android.application") version "8.7.0" apply false
22 | // START: FlutterFire Configuration
23 | id("com.google.gms.google-services") version("4.3.15") apply false
24 | id("com.google.firebase.crashlytics") version("2.8.1") apply false
25 | // END: FlutterFire Configuration
26 | id("org.jetbrains.kotlin.android") version "1.8.22" apply false
27 | }
28 |
29 | include(":app")
30 |
--------------------------------------------------------------------------------
/_example/flutter_crashlytics_test/firebase.json:
--------------------------------------------------------------------------------
1 | {"flutter":{"platforms":{"android":{"default":{"projectId":"extensions-dev-3867c","appId":"1:162074331013:android:f5830f1b7bf6ffd8ade6ec","fileOutput":"android/app/google-services.json"}},"dart":{"lib/firebase_options.dart":{"projectId":"extensions-dev-3867c","configurations":{"android":"1:162074331013:android:f5830f1b7bf6ffd8ade6ec"}}}}}}
--------------------------------------------------------------------------------
/_example/flutter_crashlytics_test/lib/firebase_options.dart:
--------------------------------------------------------------------------------
1 | // File generated by FlutterFire CLI.
2 | // ignore_for_file: type=lint
3 | import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
4 | import 'package:flutter/foundation.dart'
5 | show defaultTargetPlatform, kIsWeb, TargetPlatform;
6 |
7 | /// Default [FirebaseOptions] for use with your Firebase apps.
8 | ///
9 | /// Example:
10 | /// ```dart
11 | /// import 'firebase_options.dart';
12 | /// // ...
13 | /// await Firebase.initializeApp(
14 | /// options: DefaultFirebaseOptions.currentPlatform,
15 | /// );
16 | /// ```
17 | class DefaultFirebaseOptions {
18 | static FirebaseOptions get currentPlatform {
19 | if (kIsWeb) {
20 | throw UnsupportedError(
21 | 'DefaultFirebaseOptions have not been configured for web - '
22 | 'you can reconfigure this by running the FlutterFire CLI again.',
23 | );
24 | }
25 | switch (defaultTargetPlatform) {
26 | case TargetPlatform.android:
27 | return android;
28 | case TargetPlatform.iOS:
29 | throw UnsupportedError(
30 | 'DefaultFirebaseOptions have not been configured for ios - '
31 | 'you can reconfigure this by running the FlutterFire CLI again.',
32 | );
33 | case TargetPlatform.macOS:
34 | throw UnsupportedError(
35 | 'DefaultFirebaseOptions have not been configured for macos - '
36 | 'you can reconfigure this by running the FlutterFire CLI again.',
37 | );
38 | case TargetPlatform.windows:
39 | throw UnsupportedError(
40 | 'DefaultFirebaseOptions have not been configured for windows - '
41 | 'you can reconfigure this by running the FlutterFire CLI again.',
42 | );
43 | case TargetPlatform.linux:
44 | throw UnsupportedError(
45 | 'DefaultFirebaseOptions have not been configured for linux - '
46 | 'you can reconfigure this by running the FlutterFire CLI again.',
47 | );
48 | default:
49 | throw UnsupportedError(
50 | 'DefaultFirebaseOptions are not supported for this platform.',
51 | );
52 | }
53 | }
54 |
55 | static const FirebaseOptions android = FirebaseOptions(
56 | apiKey: 'AIzaSyDEmuRvikypsFsnyM71Z-S_EqAwCdkxVwo',
57 | appId: '1:162074331013:android:f5830f1b7bf6ffd8ade6ec',
58 | messagingSenderId: '162074331013',
59 | projectId: 'extensions-dev-3867c',
60 | storageBucket: 'extensions-dev-3867c.firebasestorage.app',
61 | );
62 | }
63 |
--------------------------------------------------------------------------------
/_example/flutter_crashlytics_test/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:firebase_core/firebase_core.dart';
2 | import 'package:firebase_crashlytics/firebase_crashlytics.dart';
3 | import 'package:flutter/foundation.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter_crashlytics_test/firebase_options.dart';
6 | import 'package:simple_logger/simple_logger.dart';
7 | import 'package:stack_trace/stack_trace.dart';
8 |
9 | final logger =
10 | SimpleLogger()
11 | ..onLogged = (log, info) {
12 | if (info.level >= Level.SEVERE) {
13 | FirebaseCrashlytics.instance.recordFlutterError(
14 | FlutterErrorDetails(
15 | exception: info.message,
16 | library: 'logger',
17 | context: DiagnosticsNode.message(log),
18 | stack: Trace.current(),
19 | ),
20 | );
21 | }
22 | };
23 |
24 | Future main() async {
25 | WidgetsFlutterBinding.ensureInitialized();
26 | await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
27 | FlutterError.onError = (errorDetails) {
28 | FirebaseCrashlytics.instance.recordFlutterError(errorDetails);
29 | };
30 | PlatformDispatcher.instance.onError = (error, stack) {
31 | FirebaseCrashlytics.instance.recordError(error, stack);
32 | return true;
33 | };
34 | runApp(const App());
35 | }
36 |
37 | class App extends StatelessWidget {
38 | const App({super.key});
39 |
40 | @override
41 | Widget build(BuildContext context) {
42 | return MaterialApp(theme: ThemeData.light(), home: const HomePage());
43 | }
44 | }
45 |
46 | class HomePage extends StatelessWidget {
47 | const HomePage({super.key});
48 | @override
49 | Widget build(BuildContext context) {
50 | return Scaffold(
51 | appBar: AppBar(),
52 | body: Center(
53 | child: Column(
54 | children: [
55 | OutlinedButton(
56 | onPressed: () {
57 | FirebaseCrashlytics.instance.crash();
58 | },
59 | child: const Text('Force crash - Fatal error'),
60 | ),
61 | OutlinedButton(
62 | onPressed: () {
63 | logger.severe('This is Non-Fatal error2');
64 | },
65 | child: const Text('logger - Non-Fatal error'),
66 | ),
67 | OutlinedButton(
68 | onPressed: () {
69 | throw UnsupportedError('This is Non-Fatal error');
70 | },
71 | child: const Text('UnsupportedError - Non-Fatal error'),
72 | ),
73 | ElevatedButton(
74 | onPressed: () {
75 | logger.severe('ElevatedButton Error');
76 | },
77 | child: const Text('logger - Non-Fatal error'),
78 | ),
79 | ElevatedButton(
80 | onPressed: () {
81 | throw UnimplementedError();
82 | },
83 | child: const Text('ElevatedButton logger - Non-Fatal error'),
84 | ),
85 | ElevatedButton(
86 | onPressed: () {
87 | Navigator.of(context).push(
88 | MaterialPageRoute(builder: (context) => const _Page2()),
89 | );
90 | },
91 | child: const Text('Navigate page'),
92 | ),
93 | ],
94 | ),
95 | ),
96 | );
97 | }
98 | }
99 |
100 | class _Page2 extends StatelessWidget {
101 | const _Page2();
102 |
103 | @override
104 | Widget build(BuildContext context) {
105 | return Scaffold(
106 | appBar: AppBar(title: const Text('')),
107 | body: Center(
108 | child: Column(
109 | children: [
110 | OutlinedButton(
111 | onPressed: () {
112 | throw UnsupportedError('This is Non-Fatal error');
113 | },
114 | child: const Text('UnsupportedError - Non-Fatal error'),
115 | ),
116 | ElevatedButton(
117 | onPressed: () {
118 | logger.severe('ElevatedButton Error');
119 | },
120 | child: const Text('logger - Non-Fatal error'),
121 | ),
122 | ],
123 | ),
124 | ),
125 | );
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/_example/flutter_crashlytics_test/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://dart.dev/tools/pub/glossary#lockfile
3 | packages:
4 | _flutterfire_internals:
5 | dependency: transitive
6 | description:
7 | name: _flutterfire_internals
8 | sha256: de9ecbb3ddafd446095f7e833c853aff2fa1682b017921fe63a833f9d6f0e422
9 | url: "https://pub.dev"
10 | source: hosted
11 | version: "1.3.54"
12 | async:
13 | dependency: transitive
14 | description:
15 | name: async
16 | sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
17 | url: "https://pub.dev"
18 | source: hosted
19 | version: "2.12.0"
20 | boolean_selector:
21 | dependency: transitive
22 | description:
23 | name: boolean_selector
24 | sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
25 | url: "https://pub.dev"
26 | source: hosted
27 | version: "2.1.2"
28 | characters:
29 | dependency: transitive
30 | description:
31 | name: characters
32 | sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
33 | url: "https://pub.dev"
34 | source: hosted
35 | version: "1.4.0"
36 | clock:
37 | dependency: transitive
38 | description:
39 | name: clock
40 | sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
41 | url: "https://pub.dev"
42 | source: hosted
43 | version: "1.1.2"
44 | collection:
45 | dependency: transitive
46 | description:
47 | name: collection
48 | sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
49 | url: "https://pub.dev"
50 | source: hosted
51 | version: "1.19.1"
52 | fake_async:
53 | dependency: transitive
54 | description:
55 | name: fake_async
56 | sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
57 | url: "https://pub.dev"
58 | source: hosted
59 | version: "1.3.2"
60 | firebase_core:
61 | dependency: "direct main"
62 | description:
63 | name: firebase_core
64 | sha256: "017d17d9915670e6117497e640b2859e0b868026ea36bf3a57feb28c3b97debe"
65 | url: "https://pub.dev"
66 | source: hosted
67 | version: "3.13.0"
68 | firebase_core_platform_interface:
69 | dependency: transitive
70 | description:
71 | name: firebase_core_platform_interface
72 | sha256: d7253d255ff10f85cfd2adaba9ac17bae878fa3ba577462451163bd9f1d1f0bf
73 | url: "https://pub.dev"
74 | source: hosted
75 | version: "5.4.0"
76 | firebase_core_web:
77 | dependency: transitive
78 | description:
79 | name: firebase_core_web
80 | sha256: "129a34d1e0fb62e2b488d988a1fc26cc15636357e50944ffee2862efe8929b23"
81 | url: "https://pub.dev"
82 | source: hosted
83 | version: "2.22.0"
84 | firebase_crashlytics:
85 | dependency: "direct main"
86 | description:
87 | name: firebase_crashlytics
88 | sha256: f3fa4a17c2f061b16b2e3ac7aaed889ae954b8952d0fd3e0009a9870cde7bbd2
89 | url: "https://pub.dev"
90 | source: hosted
91 | version: "4.3.5"
92 | firebase_crashlytics_platform_interface:
93 | dependency: transitive
94 | description:
95 | name: firebase_crashlytics_platform_interface
96 | sha256: cedfbe39927711c0e56fc38bfecbd89e17816b21698a3d88d63298c530ed375c
97 | url: "https://pub.dev"
98 | source: hosted
99 | version: "3.8.5"
100 | flutter:
101 | dependency: "direct main"
102 | description: flutter
103 | source: sdk
104 | version: "0.0.0"
105 | flutter_lints:
106 | dependency: transitive
107 | description:
108 | name: flutter_lints
109 | sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
110 | url: "https://pub.dev"
111 | source: hosted
112 | version: "5.0.0"
113 | flutter_test:
114 | dependency: "direct dev"
115 | description: flutter
116 | source: sdk
117 | version: "0.0.0"
118 | flutter_web_plugins:
119 | dependency: transitive
120 | description: flutter
121 | source: sdk
122 | version: "0.0.0"
123 | leak_tracker:
124 | dependency: transitive
125 | description:
126 | name: leak_tracker
127 | sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
128 | url: "https://pub.dev"
129 | source: hosted
130 | version: "10.0.8"
131 | leak_tracker_flutter_testing:
132 | dependency: transitive
133 | description:
134 | name: leak_tracker_flutter_testing
135 | sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
136 | url: "https://pub.dev"
137 | source: hosted
138 | version: "3.0.9"
139 | leak_tracker_testing:
140 | dependency: transitive
141 | description:
142 | name: leak_tracker_testing
143 | sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
144 | url: "https://pub.dev"
145 | source: hosted
146 | version: "3.0.1"
147 | lints:
148 | dependency: transitive
149 | description:
150 | name: lints
151 | sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
152 | url: "https://pub.dev"
153 | source: hosted
154 | version: "5.1.1"
155 | logging:
156 | dependency: transitive
157 | description:
158 | name: logging
159 | sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
160 | url: "https://pub.dev"
161 | source: hosted
162 | version: "1.3.0"
163 | matcher:
164 | dependency: transitive
165 | description:
166 | name: matcher
167 | sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
168 | url: "https://pub.dev"
169 | source: hosted
170 | version: "0.12.17"
171 | material_color_utilities:
172 | dependency: transitive
173 | description:
174 | name: material_color_utilities
175 | sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
176 | url: "https://pub.dev"
177 | source: hosted
178 | version: "0.11.1"
179 | meta:
180 | dependency: transitive
181 | description:
182 | name: meta
183 | sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
184 | url: "https://pub.dev"
185 | source: hosted
186 | version: "1.16.0"
187 | path:
188 | dependency: transitive
189 | description:
190 | name: path
191 | sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
192 | url: "https://pub.dev"
193 | source: hosted
194 | version: "1.9.1"
195 | pedantic_mono:
196 | dependency: "direct dev"
197 | description:
198 | name: pedantic_mono
199 | sha256: b032d4ba3f702a72d8c9a9084d1d1db917b2c072eb71ab4e85ebb0257a219674
200 | url: "https://pub.dev"
201 | source: hosted
202 | version: "1.30.3"
203 | plugin_platform_interface:
204 | dependency: transitive
205 | description:
206 | name: plugin_platform_interface
207 | sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
208 | url: "https://pub.dev"
209 | source: hosted
210 | version: "2.1.8"
211 | simple_logger:
212 | dependency: "direct main"
213 | description:
214 | name: simple_logger
215 | sha256: "1de79f22bf31e5c33b91e9e302394dac02d8269d474848d33153c3a15c08e970"
216 | url: "https://pub.dev"
217 | source: hosted
218 | version: "1.10.0"
219 | sky_engine:
220 | dependency: transitive
221 | description: flutter
222 | source: sdk
223 | version: "0.0.0"
224 | source_span:
225 | dependency: transitive
226 | description:
227 | name: source_span
228 | sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
229 | url: "https://pub.dev"
230 | source: hosted
231 | version: "1.10.1"
232 | stack_trace:
233 | dependency: "direct main"
234 | description:
235 | name: stack_trace
236 | sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
237 | url: "https://pub.dev"
238 | source: hosted
239 | version: "1.12.1"
240 | stream_channel:
241 | dependency: transitive
242 | description:
243 | name: stream_channel
244 | sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
245 | url: "https://pub.dev"
246 | source: hosted
247 | version: "2.1.4"
248 | string_scanner:
249 | dependency: transitive
250 | description:
251 | name: string_scanner
252 | sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
253 | url: "https://pub.dev"
254 | source: hosted
255 | version: "1.4.1"
256 | term_glyph:
257 | dependency: transitive
258 | description:
259 | name: term_glyph
260 | sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
261 | url: "https://pub.dev"
262 | source: hosted
263 | version: "1.2.2"
264 | test_api:
265 | dependency: transitive
266 | description:
267 | name: test_api
268 | sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
269 | url: "https://pub.dev"
270 | source: hosted
271 | version: "0.7.4"
272 | vector_math:
273 | dependency: transitive
274 | description:
275 | name: vector_math
276 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
277 | url: "https://pub.dev"
278 | source: hosted
279 | version: "2.1.4"
280 | vm_service:
281 | dependency: transitive
282 | description:
283 | name: vm_service
284 | sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
285 | url: "https://pub.dev"
286 | source: hosted
287 | version: "14.3.1"
288 | web:
289 | dependency: transitive
290 | description:
291 | name: web
292 | sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
293 | url: "https://pub.dev"
294 | source: hosted
295 | version: "1.1.1"
296 | sdks:
297 | dart: ">=3.7.2 <4.0.0"
298 | flutter: ">=3.22.0"
299 |
--------------------------------------------------------------------------------
/_example/flutter_crashlytics_test/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_crashlytics_test
2 | description: 'A new Flutter project.'
3 | publish_to: 'none'
4 | version: 0.1.1
5 |
6 | environment:
7 | sdk: ^3.7.2
8 |
9 | dependencies:
10 | firebase_core: ^3.13.0
11 | firebase_crashlytics: ^4.3.5
12 | flutter:
13 | sdk: flutter
14 | simple_logger: ^1.10.0
15 | stack_trace: ^1.12.1
16 |
17 | dev_dependencies:
18 | flutter_test:
19 | sdk: flutter
20 | pedantic_mono: ^1.30.3
21 |
22 | flutter:
23 | uses-material-design: true
24 |
--------------------------------------------------------------------------------
/back-up-firestore-to-storage/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## Version 0.1.0
2 |
3 | - Change buckets parameter type to `selectResource`
4 |
5 | ## Version 0.0.6
6 |
7 | - Fix setting the default value of the `TIME_ZONE` parameter to UTC.
8 | - Update docs
9 |
10 | ## Version 0.0.5
11 |
12 | - Fix the documentation of `TIMESTAMP_FORMAT` parameter by escaping
13 | - Update docs
14 |
15 | ## Version 0.0.4
16 |
17 | - Add optional `TIMESTAMP_FORMAT` parameter for object path (folder) name to export
18 |
19 | ## Version 0.0.3
20 |
21 | - Update docs
22 |
23 | ## Version 0.0.2
24 |
25 | - Change role for Cloud Storage to `storage.objectAdmin`
26 |
27 | ## Version 0.0.1
28 |
29 | - Initial Version
30 |
--------------------------------------------------------------------------------
/back-up-firestore-to-storage/POSTINSTALL.md:
--------------------------------------------------------------------------------
1 | # See it in action
2 |
3 | You just need to wait until the scheduled time in ${param:SCHEDULE}.
4 |
5 | # Using the extension
6 |
7 | When triggered by scheduled time, this extension creates a Firestore backup objects in [${param:BUCKET_NAME}](https://console.cloud.google.com/storage/browser/${param:BUCKET_NAME}).
8 |
9 | To learn more about Firebase Pub/Sub triggers, visit the [functions documentation](https://firebase.google.com/docs/functions/pubsub-events?gen=2nd).
10 |
11 | # Monitoring
12 |
13 | As a best practice, you can [monitor the activity](https://firebase.google.com/docs/extensions/manage-installed-extensions#monitor) of your installed extension, including checks on its health, usage, and logs.
14 |
--------------------------------------------------------------------------------
/back-up-firestore-to-storage/PREINSTALL.md:
--------------------------------------------------------------------------------
1 | By using this extension, export selected Firestore documents to Cloud Storage at any scheduled time. It depends on Google API's [exportDocuments](https://cloud.google.com/firestore/docs/reference/rest/v1/projects.databases/exportDocuments).
2 |
3 | This extension streamlines the creation of content outlined in the Schedule data exports section of [the official Firebase documentation](https://firebase.google.com/docs/firestore/solutions/schedule-export).It eliminates the need to manually create service accounts or configure Cloud Functions. Just install the extension, and with a single click, you're all set up.
4 |
5 | > Caution: Exporting data from Cloud Firestore will incur one read operation per document exported. However, these reads will not appear in the usage section of the console. Make sure you understand this before setting up recurring exports to avoid an unexpected bill.
6 |
7 | The features of this extension are as follows:
8 |
9 | - Exports documents of specified Firestore collection ID(s) at any scheduled time
10 | - Allows developers to set schedule the time to export
11 | - Supports both `Unix Crontab` and `App Engine syntax`
12 | - [unix-cron syntax](https://cloud.google.com/scheduler/docs/configuring/cron-job-schedules) (for example, '5 11 ** *')
13 | - [App Engine syntax](https://cloud.google.com/appengine/docs/standard/scheduling-jobs-with-cron-yaml#defining_the_cron_job_schedule) (for example, 'every 5 minutes')
14 |
15 | ## Additional setup
16 |
17 | If you want to back up to a bucket other than `[project-id].appspot.com`, you need to create the bucket before using this extension. It is **recommended** that creating the bucket with `Coldline` or `Archive` storage class, due to backup purposes.
18 |
19 | For more information about Storage class, please refer to:
20 | [Available storage classes](https://cloud.google.com/storage/docs/storage-classes#classes)
21 |
22 | ## Billing
23 |
24 | This extension uses other Firebase or Google Cloud Platform services which may have associated charges:
25 |
26 | - Cloud Functions
27 | - Cloud Firestore
28 | - Cloud Storage
29 |
30 | When you use Firebase Extensions, you're only charged for the underlying resources that you use. A paid-tier billing plan is only required if the extension uses a service that requires a paid-tier plan, for example calling to a Google Cloud Platform API or making outbound network requests to non-Google services. All Firebase services offer a free tier of usage. [Learn more about Firebase billing.](https://firebase.google.com/pricing)
31 |
--------------------------------------------------------------------------------
/back-up-firestore-to-storage/README.md:
--------------------------------------------------------------------------------
1 | # Back up Firestore to Storage
2 |
3 | **Author**: Hideki Tsuruoka (**[https://github.com/htsuruo](https://github.com/htsuruo)**)
4 |
5 | **Description**: Exports Firestore documents to Cloud Storage at any scheduled time.
6 |
7 | **Details**: By using this extension, export selected Firestore documents to Cloud Storage at any scheduled time. It depends on Google API's [exportDocuments](https://cloud.google.com/firestore/docs/reference/rest/v1/projects.databases/exportDocuments).
8 |
9 | This extension streamlines the creation of content outlined in the Schedule data exports section of [the official Firebase documentation](https://firebase.google.com/docs/firestore/solutions/schedule-export).It eliminates the need to manually create service accounts or configure Cloud Functions. Just install the extension, and with a single click, you're all set up.
10 |
11 | > Caution: Exporting data from Cloud Firestore will incur one read operation per document exported. However, these reads will not appear in the usage section of the console. Make sure you understand this before setting up recurring exports to avoid an unexpected bill.
12 |
13 | The features of this extension are as follows:
14 |
15 | - Exports documents of specified Firestore collection ID(s) at any scheduled time
16 | - Allows developers to set schedule the time to export
17 | - Supports both `Unix Crontab` and `App Engine syntax`
18 | - [unix-cron syntax](https://cloud.google.com/scheduler/docs/configuring/cron-job-schedules) (for example, '5 11 ** *')
19 | - [App Engine syntax](https://cloud.google.com/appengine/docs/standard/scheduling-jobs-with-cron-yaml#defining_the_cron_job_schedule) (for example, 'every 5 minutes')
20 |
21 | ## Additional setup
22 |
23 | If you want to back up to a bucket other than `[project-id].appspot.com`, you need to create the bucket before using this extension. It is **recommended** that creating the bucket with `Coldline` or `Archive` storage class, due to backup purposes.
24 |
25 | For more information about Storage class, please refer to:
26 | [Available storage classes](https://cloud.google.com/storage/docs/storage-classes#classes)
27 |
28 | ## Billing
29 |
30 | This extension uses other Firebase or Google Cloud Platform services which may have associated charges:
31 |
32 | - Cloud Functions
33 | - Cloud Firestore
34 | - Cloud Storage
35 |
36 | When you use Firebase Extensions, you're only charged for the underlying resources that you use. A paid-tier billing plan is only required if the extension uses a service that requires a paid-tier plan, for example calling to a Google Cloud Platform API or making outbound network requests to non-Google services. All Firebase services offer a free tier of usage. [Learn more about Firebase billing.](https://firebase.google.com/pricing)
37 |
38 | **Configuration Parameters:**
39 |
40 | - Cloud Storage bucket: Which resource do you want to use?
41 |
42 | - Cloud Storage prefix path (not including heading slash, filename): This is an optional Google Cloud Storage namespace path.
43 |
44 | - Firestore collection ids (separated by ','): Which collection ids to export. Unspecified means all collections.
45 |
46 | - The frequency at which you want to execute the backup: - This field can accept strings that use either syntax:
47 | - unix-cron syntax (for example, `5 11 * * *`)
48 | - App Engine syntax (for example, `every 5 minutes`)
49 |
50 | - The timestamp format for path name to export: If not set, **YYYY-MM-DDTHH\:mm:ss_SSS** is set as the default. This is same to the default folder name of `exportDocuments` API.
51 |
52 | - The time zone in which the schedule will run: Refer to [this document](https://cloud.google.com/looker/docs/reference/param-view-timezone-values).
53 |
54 | - Cloud Functions location: Where do you want to deploy the functions created for this extension? For help selecting a location, refer to the [location selection guide](https://firebase.google.com/docs/functions/locations).
55 |
56 | **Cloud Functions:**
57 |
58 | - **backupTransaction:** Schedule triggered function that executes backup Firestore to Cloud Storage
59 |
60 | **APIs Used**:
61 |
62 | - firestore.googleapis.com (Reason: Exports Firestore documents in a restorable format)
63 |
64 | **Access Required**:
65 |
66 | This extension will operate with the following project IAM roles:
67 |
68 | - datastore.importExportAdmin (Reason: Allows the extension to export Firestore documents data.)
69 |
70 | - storage.objectAdmin (Reason: Allows the extension to upload exported Firestore documents data to Cloud Storage.)
71 |
--------------------------------------------------------------------------------
/back-up-firestore-to-storage/extension.yaml:
--------------------------------------------------------------------------------
1 | # https://firebase.google.com/docs/extensions/reference/extension-yaml
2 |
3 | name: back-up-firestore-to-storage
4 | version: 0.1.0
5 | specVersion: v1beta
6 | license: Apache-2.0
7 | billingRequired: true
8 |
9 | displayName: Back up Firestore to Storage
10 | description: Exports Firestore documents to Cloud Storage at any scheduled time.
11 | icon: icon.png
12 | tags: [utilities]
13 |
14 | sourceUrl: https://github.com/htsuruo/firebase-extensions
15 |
16 | author:
17 | authorName: Hideki Tsuruoka
18 | email: tsuru.dev@gmail.com
19 | url: https://github.com/htsuruo
20 |
21 | apis:
22 | # ref. https://firebase.google.com/docs/firestore/reference/rest/v1beta1/projects.databases/exportDocuments?hl=en
23 | - apiName: firestore.googleapis.com
24 | reason: Exports Firestore documents in a restorable format
25 |
26 | # https://firebase.google.com/docs/extensions/reference/extension-yaml#roles-field
27 | roles:
28 | - role: datastore.importExportAdmin
29 | reason: Allows the extension to export Firestore documents data.
30 | # TODO(tsuruoka): バケットの作成まで担う場合は`storage.admin`まで必要
31 | - role: storage.objectAdmin
32 | reason: Allows the extension to upload exported Firestore documents data to Cloud Storage.
33 |
34 | # https://firebase.google.com/docs/extensions/reference/extension-yaml#resources-field
35 | resources:
36 | - name: backupTransaction
37 | type: firebaseextensions.v1beta.function
38 | description: >-
39 | Schedule triggered function that executes backup Firestore to Cloud Storage
40 | properties:
41 | location: ${param:LOCATION}
42 | scheduleTrigger:
43 | schedule: '${param:SCHEDULE}'
44 | timeZone: ${param:TIME_ZONE}
45 | runtime: nodejs18
46 | timeout: 540s
47 |
48 | # https://firebase.google.com/docs/extensions/reference/extension-yaml#params-field
49 | params:
50 | - param: BUCKET_NAME
51 | label: Cloud Storage bucket
52 | description: >-
53 | Which resource do you want to use?
54 | type: selectResource
55 | resourceType: storage.googleapis.com/Bucket
56 | required: true
57 |
58 | - param: PREFIX_PATH
59 | label: Cloud Storage prefix path (not including heading slash, filename)
60 | description: >-
61 | This is an optional Google Cloud Storage namespace path.
62 | type: string
63 | example: firestore-backup
64 | required: false
65 |
66 | - param: COLLECTION_IDS
67 | label: Firestore collection ids (separated by ',')
68 | description: >-
69 | Which collection ids to export. Unspecified means all collections.
70 | type: string
71 | example: users,posts
72 | required: false
73 |
74 | - param: SCHEDULE
75 | label: The frequency at which you want to execute the backup
76 | description: >-
77 | - This field can accept strings that use either syntax:
78 | - unix-cron syntax (for example, `5 11 * * *`)
79 | - App Engine syntax (for example, `every 5 minutes`)
80 | type: string
81 | example: 'every day 00:00'
82 | required: true
83 |
84 | - param: TIMESTAMP_FORMAT
85 | label: The timestamp format for path name to export
86 | # デフォルトフォーマットの記述箇所はコードブロックではエスケープが想定通りに機能しないのでBoldにしています。
87 | # ref. https://github.com/htsuruo/firebase-extensions/pull/9#issuecomment-1597340454
88 | description: >
89 | If not set, **YYYY-MM-DDTHH\:mm:ss_SSS** is set as the default. This is same to the default folder name of `exportDocuments` API.
90 | type: string
91 | required: false
92 |
93 | - param: TIME_ZONE
94 | label: The time zone in which the schedule will run
95 | description: >-
96 | Refer to [this document](https://cloud.google.com/looker/docs/reference/param-view-timezone-values).
97 | required: true
98 | # select形式では項目が多すぎて逆に選択の手間があるため、stringにした
99 | type: string
100 | # ref. https://www.debuggex.com/r/771ms2kxDLYJAz2N
101 | validationRegex: '^(UTC|[A-Za-z_]+/([A-Za-z_]+)*)$'
102 | example: 'Asia/Tokyo'
103 | default: 'UTC'
104 |
105 | - param: LOCATION
106 | label: Cloud Functions location
107 | description: >-
108 | Where do you want to deploy the functions created for this extension?
109 | For help selecting a location, refer to the [location selection
110 | guide](https://firebase.google.com/docs/functions/locations).
111 | required: true
112 | immutable: true
113 | type: select
114 | options:
115 | - label: Iowa (us-central1)
116 | value: us-central1
117 | - label: South Carolina (us-east1)
118 | value: us-east1
119 | - label: Northern Virginia (us-east4)
120 | value: us-east4
121 | - label: Los Angeles (us-west2)
122 | value: us-west2
123 | - label: Salt Lake City (us-west3)
124 | value: us-west3
125 | - label: Las Vegas (us-west4)
126 | value: us-west4
127 | - label: Warsaw (europe-central2)
128 | value: europe-central2
129 | - label: Belgium (europe-west1)
130 | value: europe-west1
131 | - label: London (europe-west2)
132 | value: europe-west2
133 | - label: Frankfurt (europe-west3)
134 | value: europe-west3
135 | - label: Zurich (europe-west6)
136 | value: europe-west6
137 | - label: Hong Kong (asia-east2)
138 | value: asia-east2
139 | - label: Tokyo (asia-northeast1)
140 | value: asia-northeast1
141 | - label: Osaka (asia-northeast2)
142 | value: asia-northeast2
143 | - label: Seoul (asia-northeast3)
144 | value: asia-northeast3
145 | - label: Mumbai (asia-south1)
146 | value: asia-south1
147 | - label: Jakarta (asia-southeast2)
148 | value: asia-southeast2
149 | - label: Montreal (northamerica-northeast1)
150 | value: northamerica-northeast1
151 | - label: Sao Paulo (southamerica-east1)
152 | value: southamerica-east1
153 | - label: Sydney (australia-southeast1)
154 | value: australia-southeast1
155 |
--------------------------------------------------------------------------------
/back-up-firestore-to-storage/functions/.gitignore:
--------------------------------------------------------------------------------
1 | ## Compiled JavaScript files
2 | **/*.js
3 | **/*.js.map
4 |
5 | # Typescript v1 declaration files
6 | typings/
7 |
8 | node_modules/
9 |
--------------------------------------------------------------------------------
/back-up-firestore-to-storage/functions/.mocharc.json:
--------------------------------------------------------------------------------
1 | {
2 | "require": "ts-node/register",
3 | "extensions": ["ts", "tsx"],
4 | "spec": ["integration-tests/**/*.spec.*", "test/**/*.spec.*"],
5 | "watch-files": ["src"]
6 | }
7 |
--------------------------------------------------------------------------------
/back-up-firestore-to-storage/functions/integration-tests/.firebaserc:
--------------------------------------------------------------------------------
1 | {
2 | "projects": {
3 | "default": "playground-c8a87"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/back-up-firestore-to-storage/functions/integration-tests/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | firebase-debug.log*
8 | firebase-debug.*.log*
9 |
10 | # Firebase cache
11 | .firebase/
12 |
13 | # Firebase config
14 |
15 | # Uncomment this if you'd like others to create their own Firebase project.
16 | # For a team working on the same Firebase project(s), it is recommended to leave
17 | # it commented so all members can deploy to the same project(s) in .firebaserc.
18 | # .firebaserc
19 |
20 | # Runtime data
21 | pids
22 | *.pid
23 | *.seed
24 | *.pid.lock
25 |
26 | # Directory for instrumented libs generated by jscoverage/JSCover
27 | lib-cov
28 |
29 | # Coverage directory used by tools like istanbul
30 | coverage
31 |
32 | # nyc test coverage
33 | .nyc_output
34 |
35 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
36 | .grunt
37 |
38 | # Bower dependency directory (https://bower.io/)
39 | bower_components
40 |
41 | # node-waf configuration
42 | .lock-wscript
43 |
44 | # Compiled binary addons (http://nodejs.org/api/addons.html)
45 | build/Release
46 |
47 | # Dependency directories
48 | node_modules/
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Optional REPL history
57 | .node_repl_history
58 |
59 | # Output of 'npm pack'
60 | *.tgz
61 |
62 | # Yarn Integrity file
63 | .yarn-integrity
64 |
65 | # dotenv environment variables file
66 | .env
67 |
--------------------------------------------------------------------------------
/back-up-firestore-to-storage/functions/integration-tests/data/firebase-export-metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "12.3.0",
3 | "firestore": {
4 | "version": "1.17.4",
5 | "path": "firestore_export",
6 | "metadata_file": "firestore_export/firestore_export.overall_export_metadata"
7 | },
8 | "storage": {
9 | "version": "12.3.0",
10 | "path": "storage_export"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/back-up-firestore-to-storage/functions/integration-tests/data/firestore_export/all_namespaces/all_kinds/all_namespaces_all_kinds.export_metadata:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/htsuruo/firebase-extensions/23dc5d1e2f59bfdda41e86164c1b2b330ee4030b/back-up-firestore-to-storage/functions/integration-tests/data/firestore_export/all_namespaces/all_kinds/all_namespaces_all_kinds.export_metadata
--------------------------------------------------------------------------------
/back-up-firestore-to-storage/functions/integration-tests/data/firestore_export/all_namespaces/all_kinds/output-0:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/htsuruo/firebase-extensions/23dc5d1e2f59bfdda41e86164c1b2b330ee4030b/back-up-firestore-to-storage/functions/integration-tests/data/firestore_export/all_namespaces/all_kinds/output-0
--------------------------------------------------------------------------------
/back-up-firestore-to-storage/functions/integration-tests/data/firestore_export/firestore_export.overall_export_metadata:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/htsuruo/firebase-extensions/23dc5d1e2f59bfdda41e86164c1b2b330ee4030b/back-up-firestore-to-storage/functions/integration-tests/data/firestore_export/firestore_export.overall_export_metadata
--------------------------------------------------------------------------------
/back-up-firestore-to-storage/functions/integration-tests/data/storage_export/buckets.json:
--------------------------------------------------------------------------------
1 | {
2 | "buckets": [
3 | {
4 | "id": "demo-test.appspot.com"
5 | }
6 | ]
7 | }
--------------------------------------------------------------------------------
/back-up-firestore-to-storage/functions/integration-tests/debug.sh:
--------------------------------------------------------------------------------
1 | # Google Cloud APIを利用するため、実プロジェクトでのテストが必要
2 | # 手元の環境に合わせて.firebasercのprojectを変更する必要があります。
3 | firebase emulators:start --import=./data --inspect-functions
--------------------------------------------------------------------------------
/back-up-firestore-to-storage/functions/integration-tests/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "emulators": {
3 | "functions": {
4 | "port": 5001
5 | },
6 | "ui": {
7 | "enabled": true
8 | },
9 | "firestore": {
10 | "port": 8080
11 | },
12 | "pubsub": {
13 | "port": 8085
14 | },
15 | "storage": {
16 | "port": 9199
17 | },
18 | "singleProjectMode": true
19 | },
20 | "extensions": {
21 | "back-up-firestore-to-storage": "../.."
22 | },
23 | "functions": [
24 | {
25 | "runtime": "nodejs18",
26 | "source": "../../functions",
27 | "codebase": "default",
28 | "ignore": [
29 | "node_modules",
30 | ".git",
31 | "firebase-debug.log",
32 | "firebase-debug.*.log"
33 | ]
34 | }
35 | ],
36 | "storage": {
37 | "rules": "storage.rules"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/back-up-firestore-to-storage/functions/integration-tests/integration-test.spec.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/htsuruo/firebase-extensions/23dc5d1e2f59bfdda41e86164c1b2b330ee4030b/back-up-firestore-to-storage/functions/integration-tests/integration-test.spec.ts
--------------------------------------------------------------------------------
/back-up-firestore-to-storage/functions/integration-tests/storage.rules:
--------------------------------------------------------------------------------
1 | rules_version = '2';
2 |
3 | // Craft rules based on data in your Firestore database
4 | // allow write: if firestore.get(
5 | // /databases/(default)/documents/users/$(request.auth.uid)).data.isAdmin;
6 | service firebase.storage {
7 | match /b/{bucket}/o {
8 | match /{allPaths=**} {
9 | allow read, write: if false;
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/back-up-firestore-to-storage/functions/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "functions",
3 | "scripts": {
4 | "lint": "eslint \"src/**/*\"",
5 | "lint:fix": "eslint \"src/**/*\" --fix",
6 | "build": "tsc",
7 | "build:watch": "tsc --watch",
8 | "mocha": "mocha '**/*.spec.ts'",
9 | "mocha:watch": "mocha --watch --watch-files '**/*.spec.ts'",
10 | "test": "(cd integration-tests && firebase emulators:exec 'npm run mocha' -P demo-test)"
11 | },
12 | "main": "lib/index.js",
13 | "dependencies": {
14 | "dayjs": "^1.11.8",
15 | "firebase-admin": "^11.9.0",
16 | "firebase-functions": "^4.2.0",
17 | "googleapis": "^118.0.0",
18 | "typescript": "^4.9.0"
19 | },
20 | "devDependencies": {
21 | "@types/chai": "^4.3.4",
22 | "@types/mocha": "^10.0.1",
23 | "@typescript-eslint/eslint-plugin": "^5.12.0",
24 | "@typescript-eslint/parser": "^5.12.0",
25 | "axios": "^1.3.2",
26 | "chai": "^4.3.7",
27 | "eslint": "^8.15.1",
28 | "eslint-config-google": "^0.14.0",
29 | "eslint-plugin-import": "^2.26.0",
30 | "mocha": "^10.2.0",
31 | "ts-node": "^10.4.0"
32 | },
33 | "private": true
34 | }
35 |
--------------------------------------------------------------------------------
/back-up-firestore-to-storage/functions/src/format.ts:
--------------------------------------------------------------------------------
1 | import * as dayjs from 'dayjs'
2 | import * as utc from 'dayjs/plugin/utc'
3 | import * as timezone from 'dayjs/plugin/timezone'
4 |
5 | // dayjsでtimezoneプラグインを利用できるようにする
6 | // 利用するプラグインはtimezoneのみだがutcプラグインに内部依存しているため両方の指定が必要
7 | // ref. https://github.com/iamkun/dayjs/issues/1584#issuecomment-895110206
8 | dayjs.extend(utc)
9 | dayjs.extend(timezone)
10 |
11 | // `exportDocuments`APIで`outputUriPrefix`が未指定の場合に生成される形式に準拠しフォーマットする
12 | export function formatTimestamp(params: {
13 | timestamp: string
14 | timeZone?: string
15 | format?: string
16 | }) {
17 | return dayjs(params.timestamp)
18 | .tz(params.timeZone)
19 | .format(params.format ?? 'YYYY-MM-DDTHH:mm:ss_SSS')
20 | }
21 |
--------------------------------------------------------------------------------
/back-up-firestore-to-storage/functions/src/index.ts:
--------------------------------------------------------------------------------
1 | import { pubsub, logger } from 'firebase-functions/v1'
2 | import { v1 } from '@google-cloud/firestore'
3 | import { HttpsError } from 'firebase-functions/v1/https'
4 | import { formatTimestamp } from './format'
5 | // import { Storage } from '@google-cloud/storage'
6 |
7 | const client = new v1.FirestoreAdminClient()
8 | // const storage = new Storage()
9 | const projectId = process.env.PROJECT_ID!
10 | const databaseName = client.databasePath(projectId, '(default)')
11 | const bucketName = process.env.BUCKET_NAME!
12 |
13 | // ref. https://firebase.google.com/docs/firestore/solutions/schedule-export?hl=en
14 | exports.backupTransaction = pubsub
15 | .schedule(`'${process.env.SCHEDULE!}'`)
16 | // .retryConfig({ retryCount: 1 })
17 | .onRun(async (context) => {
18 | let outputUriPrefix = `gs://${bucketName}`
19 |
20 | const prefixPath = process.env.PREFIX_PATH
21 | if (prefixPath) {
22 | outputUriPrefix += `/${prefixPath}`
23 | }
24 | outputUriPrefix += `/${formatTimestamp({
25 | timestamp: context.timestamp,
26 | timeZone: process.env.TIME_ZONE,
27 | format: process.env.TIMESTAMP_FORMAT,
28 | })}`
29 |
30 | await exportDocuments({ outputUriPrefix, retryIfAlreadyExists: true })
31 | })
32 |
33 | async function exportDocuments(params: {
34 | outputUriPrefix: string
35 | retryIfAlreadyExists: boolean
36 | }) {
37 | const { outputUriPrefix, retryIfAlreadyExists } = params
38 | try {
39 | await client.exportDocuments({
40 | name: databaseName,
41 | collectionIds: process.env.COLLECTION_IDS?.split(','),
42 | outputUriPrefix: outputUriPrefix,
43 | })
44 | logger.info(`✅ Backup ${databaseName} to ${outputUriPrefix} successfully.`)
45 | } catch (error: any) {
46 | if (
47 | retryIfAlreadyExists &&
48 | error.toString().includes('Path already exists')
49 | ) {
50 | retryWithUniqueSuffix(outputUriPrefix)
51 | return
52 | }
53 | logger.error(error, { structuredData: true })
54 | throw new HttpsError('internal', '🚨 Backup operation failed.')
55 | }
56 | }
57 |
58 | // Avoid object path name collisions
59 | async function retryWithUniqueSuffix(outputUriPrefix: string) {
60 | const newOutputUriPrefix = `${outputUriPrefix}-${generateUniqueString()}`
61 | logger.info(`Retry to export: ${newOutputUriPrefix}`)
62 | await exportDocuments({
63 | outputUriPrefix: newOutputUriPrefix,
64 | retryIfAlreadyExists: false,
65 | })
66 | }
67 |
68 | // Does not generate a completed unique string, but it is enough for this use case.
69 | function generateUniqueString() {
70 | return Date.now().toString(36) + Math.random().toString(36).substring(2)
71 | }
72 |
--------------------------------------------------------------------------------
/back-up-firestore-to-storage/functions/test/timestamp_format.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect } from 'chai'
2 | import { describe, it } from 'mocha'
3 | import { formatTimestamp } from '../src/format'
4 | import * as dayjs from 'dayjs'
5 |
6 | const defaultFormat = 'YYYY-MM-DDTHH:mm:ss_SSS'
7 |
8 | describe('formatTimestamp function test', () => {
9 | const date = new Date()
10 | const iso = date.toISOString()
11 | it('format parameter is undefined: YYYY-MM-DDTHH:mm:ss_SSS', () => {
12 | expect(formatTimestamp({ timestamp: iso })).to.equal(
13 | dayjs(date).format(defaultFormat)
14 | )
15 | })
16 | it('format parameter: YYYY-MM-DD', () => {
17 | const format = 'YYYY-MM-DD'
18 | expect(formatTimestamp({ timestamp: iso, format })).to.equal(
19 | dayjs(date).format(format)
20 | )
21 | })
22 | it('format parameter: YYYY-MM-DDTHH:mm:ssZ[Z]', () => {
23 | const format = 'YYYY-MM-DDTHH:mm:ssZ[Z]'
24 | expect(formatTimestamp({ timestamp: iso, format })).to.equal(
25 | dayjs(date).format(format)
26 | )
27 | })
28 | // Cannot detect invalid format string…
29 | it('format parameter is invalid: INVALID_STRING', () => {
30 | expect(
31 | formatTimestamp({ timestamp: iso, format: 'INVALID_STRING' })
32 | ).to.equal(dayjs(date).format('INVALID_STRING'))
33 | })
34 | })
35 |
--------------------------------------------------------------------------------
/back-up-firestore-to-storage/functions/tsconfig.dev.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": [
3 | ".eslintrc.js"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/back-up-firestore-to-storage/functions/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "noImplicitReturns": true,
5 | "noUnusedLocals": true,
6 | "outDir": "lib",
7 | "sourceMap": true,
8 | "strict": true,
9 | "target": "ES2022"
10 | },
11 | "compileOnSave": true,
12 | "include": ["src"]
13 | }
14 |
--------------------------------------------------------------------------------
/back-up-firestore-to-storage/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/htsuruo/firebase-extensions/23dc5d1e2f59bfdda41e86164c1b2b330ee4030b/back-up-firestore-to-storage/icon.png
--------------------------------------------------------------------------------
/trigger-github-issues-from-crashlytics/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## Version 1.0.0
2 |
3 | - Add new trigger from `regression` alert
4 | - Update runtime to `nodejs22`
5 | - Update docs
6 |
7 | ## Version 0.0.4
8 |
9 | - Update docs
10 |
11 | ## Version 0.0.3
12 |
13 | - Refactor
14 | - Update docs
15 |
16 | ## Version 0.0.2
17 |
18 | - Update docs
19 |
20 | ## Version 0.0.1
21 |
22 | - Initial Version
23 |
--------------------------------------------------------------------------------
/trigger-github-issues-from-crashlytics/POSTINSTALL.md:
--------------------------------------------------------------------------------
1 | # See it in action
2 |
3 | You can test out this extension right away!
4 |
5 | 1. Force a crash to test your implementation
6 | - [Test your Crashlytics implementation | Firebase Crashlytics](https://firebase.google.com/docs/crashlytics/test-implementation?platform=flutter)
7 |
8 | 2. Go to your repository issues page to confirm that the issue has been created.
9 | - https://github.com/${param:GITHUB_OWNER}/${param:GITHUB_REPO}/issues
10 |
11 | # Using the extension
12 |
13 | When triggered by a new Crashlytics fatal issue, this extension creates a GitHub issue in [`${param:GITHUB_OWNER}/${param:GITHUB_REPO}`](https://github.com/${param:GITHUB_OWNER}/${param:GITHUB_REPO}/issues).
14 |
15 | To learn more about Firebase Alerts triggers, visit the [functions documentation](https://firebase.google.com/docs/functions/alert-events).
16 |
17 | # Monitoring
18 |
19 | As a best practice, you can [monitor the activity](https://firebase.google.com/docs/extensions/manage-installed-extensions#monitor) of your installed extension, including checks on its health, usage, and logs.
20 |
--------------------------------------------------------------------------------
/trigger-github-issues-from-crashlytics/PREINSTALL.md:
--------------------------------------------------------------------------------
1 | By using this extension, you can create GitHub Issues on a selected repository, and attach required labels, triggered by new Crashlytics alerts.
2 |
3 | This extension streamlines the creation of content outlined in the Firebase Alerts triggers section of [the official Firebase documentation](https://firebase.google.com/docs/functions/alert-events#handle-crashlytics-alerts). It eliminates the need to manually create service accounts or configure Cloud Functions. Just install the extension, and with a single click, you're all set up.
4 |
5 | The features of this extension are as follows:
6 |
7 | - Automatically create a GitHub issue if a new Crashlytics issue occurs using the [GitHub API](https://docs.github.com/ja/rest/issues/issues?apiVersion=2022-11-28#create-an-issue).
8 | - Allow developers to set multiple required labels (e.g., `bugs`, `crashlytics`) for issues.
9 |
10 | | Crashlytics Issue | GitHub Issue |
11 | |--------|--------|
12 | |
|
|
13 |
14 | ## Supported crashlytics alerts
15 |
16 | - `crashlytics.newFatalIssue`: An event is sent when an application experiences a new fatal crash (not for any subsequent, identical events).
17 | - `crashlytics.newNonfatalIssue`: An event is sent when an application experiences a new non-fatal error (not for any subsequent, identical events).
18 | - `crashlytics.newAnrIssue`: An event is sent when an application experiences a new Application Not Responding (ANR) error (not for any subsequent, identical events).
19 | - `crashlytics.regression`: An event is sent when an application experiences a crash for an issue marked as closed for a previous application version.
20 |
21 | ### Not supported (Future work)
22 |
23 | - `crashlytics.stabilityDigest`: An event is sent when there is a notification of the top trending issues in Crashlytics.
24 | - `crashlytics.velocity`: An event is sent when a single issue is responsible for causing a significant number of application sessions to crash.
25 |
26 | ## Additional setup
27 |
28 | Before installing this extension, make sure that you've [created a personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) in your GitHub account to access the GitHub API(if your repository is organization, use GitHub Apps).
29 |
30 | Two approaches exist for creating access tokens, but `Fine-grained personal access tokens` are recommended.
31 |
32 | ### Permissions
33 |
34 | When creating a PAT (Personal Access Token), you need to give it the following permission:
35 | `repository permissions > Issues > Read and write`
36 |
37 |
38 |
39 | ## Billing
40 |
41 | This extension uses other Firebase or Google Cloud Platform services which may have associated charges:
42 |
43 | - Cloud Functions
44 | - Cloud Secret Manager
45 | - Crashlytics
46 |
47 | When you use Firebase Extensions, you're only charged for the underlying resources that you use. A paid-tier billing plan is only required if the extension uses a service that requires a paid-tier plan, for example calling to a Google Cloud Platform API or making outbound network requests to non-Google services. All Firebase services offer a free tier of usage. [Learn more about Firebase billing.](https://firebase.google.com/pricing)
48 |
--------------------------------------------------------------------------------
/trigger-github-issues-from-crashlytics/README.md:
--------------------------------------------------------------------------------
1 | # Trigger GitHub issues from Crashlytics
2 |
3 | **Author**: Hideki Tsuruoka (**[https://github.com/htsuruo](https://github.com/htsuruo)**)
4 |
5 | **Description**: Automatically creates GitHub Issues triggered by Crashlytics alerts.
6 |
7 | **Details**: By using this extension, you can create GitHub Issues on a selected repository, and attach required labels, triggered by new Crashlytics alerts.
8 |
9 | This extension streamlines the creation of content outlined in the Firebase Alerts triggers section of [the official Firebase documentation](https://firebase.google.com/docs/functions/alert-events#handle-crashlytics-alerts). It eliminates the need to manually create service accounts or configure Cloud Functions. Just install the extension, and with a single click, you're all set up.
10 |
11 | The features of this extension are as follows:
12 |
13 | - Automatically create a GitHub issue if a new Crashlytics issue occurs using the [GitHub API](https://docs.github.com/ja/rest/issues/issues?apiVersion=2022-11-28#create-an-issue).
14 | - Allow developers to set multiple required labels (e.g., `bugs`, `crashlytics`) for issues.
15 |
16 | | Crashlytics Issue | GitHub Issue |
17 | |--------|--------|
18 | |
|
|
19 |
20 | ## Supported crashlytics alerts
21 |
22 | - `crashlytics.newFatalIssue`: An event is sent when an application experiences a new fatal crash (not for any subsequent, identical events).
23 | - `crashlytics.newNonfatalIssue`: An event is sent when an application experiences a new non-fatal error (not for any subsequent, identical events).
24 | - `crashlytics.newAnrIssue`: An event is sent when an application experiences a new Application Not Responding (ANR) error (not for any subsequent, identical events).
25 | - `crashlytics.regression`: An event is sent when an application experiences a crash for an issue marked as closed for a previous application version.
26 |
27 | ### Not supported (Future work)
28 |
29 | - `crashlytics.stabilityDigest`: An event is sent when there is a notification of the top trending issues in Crashlytics.
30 | - `crashlytics.velocity`: An event is sent when a single issue is responsible for causing a significant number of application sessions to crash.
31 |
32 | ## Additional setup
33 |
34 | Before installing this extension, make sure that you've [created a personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) in your GitHub account to access the GitHub API(if your repository is organization, use GitHub Apps).
35 |
36 | Two approaches exist for creating access tokens, but `Fine-grained personal access tokens` are recommended.
37 |
38 | ### Permissions
39 |
40 | When creating a PAT (Personal Access Token), you need to give it the following permission:
41 | `repository permissions > Issues > Read and write`
42 |
43 |
44 |
45 | ## Billing
46 |
47 | This extension uses other Firebase or Google Cloud Platform services which may have associated charges:
48 |
49 | - Cloud Functions
50 | - Cloud Secret Manager
51 | - Crashlytics
52 |
53 | When you use Firebase Extensions, you're only charged for the underlying resources that you use. A paid-tier billing plan is only required if the extension uses a service that requires a paid-tier plan, for example calling to a Google Cloud Platform API or making outbound network requests to non-Google services. All Firebase services offer a free tier of usage. [Learn more about Firebase billing.](https://firebase.google.com/pricing)
54 |
55 | **Configuration Parameters:**
56 |
57 | - GitHub access token for your repository: Use PAT(Personal Access Token) or GitHub Apps Token
58 |
59 | - The owner or organization name for your repository
60 |
61 | - The name of your repository for creating issues
62 |
63 | - Labels to associate with the issue: The param requires camma(,) separated format Only users with push access can set labels for new issues. Labels are silently dropped otherwise.
64 |
65 | - The selection of alert type you want to trigger
66 |
67 | - Cloud Functions location: Where do you want to deploy the functions created for this extension? For help selecting a location, refer to the [location selection guide](https://firebase.google.com/docs/functions/locations).
68 |
69 | **Other Resources**:
70 |
71 | - createFatalIssue (firebaseextensions.v1beta.v2function)
72 |
73 | - createNonFatalIssue (firebaseextensions.v1beta.v2function)
74 |
75 | - createAnrIssue (firebaseextensions.v1beta.v2function)
76 |
77 | - regressionAlert (firebaseextensions.v1beta.v2function)
78 |
79 | **APIs Used**:
80 |
81 | - eventarc.googleapis.com (Reason: Powers all events and triggers)
82 |
83 | - run.googleapis.com (Reason: Powers 2nd-gen functions)
84 |
85 | **Access Required**:
86 |
87 | This extension will operate with the following project IAM roles:
88 |
89 | - firebasecrashlytics.viewer (Reason: Allows the extension to read Crashlytics reports.)
90 |
91 | - eventarc.eventReceiver (Reason: Allows the extension to trigger on alerts.)
92 |
--------------------------------------------------------------------------------
/trigger-github-issues-from-crashlytics/extension.yaml:
--------------------------------------------------------------------------------
1 | # https://firebase.google.com/docs/extensions/reference/extension-yaml
2 |
3 | name: trigger-github-issues-from-crashlytics
4 | version: 1.0.0
5 | specVersion: v1beta
6 |
7 | displayName: Trigger GitHub issues from Crashlytics
8 | description: >-
9 | Automatically creates GitHub Issues triggered by Crashlytics alerts.
10 | icon: icon.png
11 | tags: [utilities]
12 |
13 | license: Apache-2.0
14 | sourceUrl: https://github.com/htsuruo/firebase-extensions
15 |
16 | billingRequired: true
17 |
18 | author:
19 | authorName: Hideki Tsuruoka
20 | email: tsuru.dev@gmail.com
21 | url: https://github.com/htsuruo
22 |
23 | apis:
24 | - apiName: eventarc.googleapis.com
25 | reason: Powers all events and triggers
26 | - apiName: run.googleapis.com
27 | reason: Powers 2nd-gen functions
28 |
29 | # In a `roles` field, list any IAM access roles required for your extension to operate.
30 | # Learn more in the docs:
31 | # https://firebase.google.com/docs/extensions/reference/extension-yaml#roles-field
32 | roles:
33 | - role: firebasecrashlytics.viewer
34 | reason: Allows the extension to read Crashlytics reports.
35 | - role: eventarc.eventReceiver
36 | reason: Allows the extension to trigger on alerts.
37 |
38 | externalServices:
39 | - name: GitHub API
40 | pricingUri: https://github.com/pricing
41 |
42 | # In the `resources` field, list each of your extension's functions, including the trigger for each function.
43 | # Learn more in the docs:
44 | # https://firebase.google.com/docs/extensions/reference/extension-yaml#resources-field
45 | resources:
46 | - name: createFatalIssue
47 | type: firebaseextensions.v1beta.v2function
48 | description: >-
49 | New crashlytics fatal issue triggered function that creates GitHub Issues.
50 | properties:
51 | buildConfig:
52 | runtime: nodejs22
53 | serviceConfig:
54 | availableMemory: 512M
55 | location: ${LOCATION}
56 | eventTrigger:
57 | eventType: google.firebase.firebasealerts.alerts.v1.published
58 | triggerRegion: global
59 | eventFilters:
60 | - attribute: alerttype
61 | value: crashlytics.newFatalIssue
62 |
63 | - name: createNonFatalIssue
64 | type: firebaseextensions.v1beta.v2function
65 | description: >-
66 | New crashlytics non-fatal issue triggered function that creates GitHub Issues.
67 | properties:
68 | buildConfig:
69 | runtime: nodejs22
70 | serviceConfig:
71 | availableMemory: 512M
72 | location: ${LOCATION}
73 | eventTrigger:
74 | eventType: google.firebase.firebasealerts.alerts.v1.published
75 | triggerRegion: global
76 | eventFilters:
77 | - attribute: alerttype
78 | value: crashlytics.newNonfatalIssue
79 |
80 | - name: createAnrIssue
81 | type: firebaseextensions.v1beta.v2function
82 | description: >-
83 | New ANR issue triggered function that creates GitHub Issues.
84 | properties:
85 | buildConfig:
86 | runtime: nodejs22
87 | serviceConfig:
88 | availableMemory: 512M
89 | location: ${LOCATION}
90 | eventTrigger:
91 | eventType: google.firebase.firebasealerts.alerts.v1.published
92 | triggerRegion: global
93 | eventFilters:
94 | - attribute: alerttype
95 | value: crashlytics.newAnrIssue
96 |
97 | - name: regressionAlert
98 | type: firebaseextensions.v1beta.v2function
99 | description: >-
100 | Regression alert triggered function that creates GitHub Issues.
101 | properties:
102 | buildConfig:
103 | runtime: nodejs22
104 | serviceConfig:
105 | availableMemory: 512M
106 | location: ${LOCATION}
107 | eventTrigger:
108 | eventType: google.firebase.firebasealerts.alerts.v1.published
109 | triggerRegion: global
110 | eventFilters:
111 | - attribute: alerttype
112 | value: crashlytics.regression
113 |
114 | # In the `params` field, set up your extension's user-configured parameters.
115 | # Learn more in the docs:
116 | # https://firebase.google.com/docs/extensions/reference/extension-yaml#params-field
117 | params:
118 | - param: GITHUB_ACCESS_TOKEN
119 | label: GitHub access token for your repository
120 | description: >-
121 | Use PAT(Personal Access Token) or GitHub Apps Token
122 | type: secret
123 | required: true
124 | example: github_pat_xxx
125 |
126 | - param: GITHUB_OWNER
127 | label: The owner or organization name for your repository
128 | required: true
129 | type: string
130 |
131 | - param: GITHUB_REPO
132 | label: The name of your repository for creating issues
133 | required: true
134 | type: string
135 |
136 | - param: GITHUB_LABELS
137 | label: Labels to associate with the issue
138 | description: >-
139 | The param requires camma(,) separated format
140 | Only users with push access can set labels for new issues. Labels are silently dropped otherwise.
141 | type: string
142 | example: bug,crashlytics
143 | required: false
144 |
145 | - param: ALERTS
146 | label: The selection of alert type you want to trigger
147 | type: multiSelect
148 | required: true
149 | options:
150 | - label: New Fatal Issue
151 | value: newFatalIssue
152 | - label: New Non-Fatal Issue
153 | value: newNonfatalIssue
154 | - label: New ANR Issue
155 | value: newAnrIssue
156 | - label: Regression Alert
157 | value: regression
158 |
159 | - param: LOCATION
160 | label: Cloud Functions location
161 | description: >-
162 | Where do you want to deploy the functions created for this extension?
163 | For help selecting a location, refer to the [location selection
164 | guide](https://firebase.google.com/docs/functions/locations).
165 | type: select
166 | options:
167 | - label: Iowa (us-central1)
168 | value: us-central1
169 | - label: South Carolina (us-east1)
170 | value: us-east1
171 | - label: Northern Virginia (us-east4)
172 | value: us-east4
173 | - label: Los Angeles (us-west2)
174 | value: us-west2
175 | - label: Salt Lake City (us-west3)
176 | value: us-west3
177 | - label: Las Vegas (us-west4)
178 | value: us-west4
179 | - label: Warsaw (europe-central2)
180 | value: europe-central2
181 | - label: Belgium (europe-west1)
182 | value: europe-west1
183 | - label: London (europe-west2)
184 | value: europe-west2
185 | - label: Frankfurt (europe-west3)
186 | value: europe-west3
187 | - label: Zurich (europe-west6)
188 | value: europe-west6
189 | - label: Hong Kong (asia-east2)
190 | value: asia-east2
191 | - label: Tokyo (asia-northeast1)
192 | value: asia-northeast1
193 | - label: Osaka (asia-northeast2)
194 | value: asia-northeast2
195 | - label: Seoul (asia-northeast3)
196 | value: asia-northeast3
197 | - label: Mumbai (asia-south1)
198 | value: asia-south1
199 | - label: Jakarta (asia-southeast2)
200 | value: asia-southeast2
201 | - label: Montreal (northamerica-northeast1)
202 | value: northamerica-northeast1
203 | - label: Sao Paulo (southamerica-east1)
204 | value: southamerica-east1
205 | - label: Sydney (australia-southeast1)
206 | value: australia-southeast1
207 | required: true
208 | immutable: true
209 |
--------------------------------------------------------------------------------
/trigger-github-issues-from-crashlytics/functions/.gitignore:
--------------------------------------------------------------------------------
1 | ## Compiled JavaScript files
2 | **/*.js
3 | **/*.js.map
4 |
5 | # Typescript v1 declaration files
6 | typings/
7 |
8 | node_modules/
9 |
--------------------------------------------------------------------------------
/trigger-github-issues-from-crashlytics/functions/.mocharc.json:
--------------------------------------------------------------------------------
1 | {
2 | "require": "ts-node/register",
3 | "extensions": ["ts", "tsx"],
4 | "spec": [
5 | "integration-tests/**/*.spec.*"
6 | ],
7 | "watch-files": [
8 | "src"
9 | ]
10 | }
--------------------------------------------------------------------------------
/trigger-github-issues-from-crashlytics/functions/integration-tests/.firebaserc:
--------------------------------------------------------------------------------
1 | {
2 | "projects": {},
3 | "targets": {},
4 | "etags": {
5 | "extensions-dev-3867c": {
6 | "extensionInstances": {
7 | "trigger-github-issues-from-crashlytics": "8cf96cceb2e9df2be40c44ef16a34b34f409a764f6e915d1a2b6ed4d44ebce2d"
8 | }
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/trigger-github-issues-from-crashlytics/functions/integration-tests/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "functions": [
3 | {
4 | "source": "../../functions",
5 | "codebase": "default",
6 | "ignore": [
7 | "node_modules",
8 | ".git",
9 | "firebase-debug.log",
10 | "firebase-debug.*.log"
11 | ]
12 | }
13 | ],
14 | "emulators": {
15 | "functions": {
16 | "port": 5001
17 | },
18 | "eventarc": {
19 | "port": 9299
20 | },
21 | "ui": {
22 | "enabled": true
23 | },
24 | "singleProjectMode": true
25 | },
26 | "extensions": {
27 | "trigger-github-issues-from-crashlytics": "../../"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/trigger-github-issues-from-crashlytics/functions/integration-tests/integration-test.spec.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/htsuruo/firebase-extensions/23dc5d1e2f59bfdda41e86164c1b2b330ee4030b/trigger-github-issues-from-crashlytics/functions/integration-tests/integration-test.spec.ts
--------------------------------------------------------------------------------
/trigger-github-issues-from-crashlytics/functions/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "functions",
3 | "scripts": {
4 | "lint": "eslint \"src/**/*\"",
5 | "lint:fix": "eslint \"src/**/*\" --fix",
6 | "build": "tsc",
7 | "build:watch": "tsc --watch",
8 | "mocha": "mocha '**/*.spec.ts'",
9 | "test": "(cd integration-tests && firebase emulators:exec 'npm run mocha' -P demo-test)"
10 | },
11 | "main": "lib/index.js",
12 | "dependencies": {
13 | "@octokit/request-error": "^3.0.3",
14 | "@octokit/rest": "^19.0.11",
15 | "firebase-admin": "^11.5.0",
16 | "firebase-functions": "^4.2.0",
17 | "typescript": "^4.9.0"
18 | },
19 | "devDependencies": {
20 | "@types/chai": "^4.3.4",
21 | "@types/mocha": "^10.0.1",
22 | "@typescript-eslint/eslint-plugin": "^5.12.0",
23 | "@typescript-eslint/parser": "^5.12.0",
24 | "chai": "^4.3.7",
25 | "eslint": "^8.15.1",
26 | "eslint-config-google": "^0.14.0",
27 | "eslint-plugin-import": "^2.26.0",
28 | "mocha": "^10.2.0",
29 | "ts-node": "^10.4.0"
30 | },
31 | "private": true
32 | }
33 |
--------------------------------------------------------------------------------
/trigger-github-issues-from-crashlytics/functions/src/github_api.ts:
--------------------------------------------------------------------------------
1 | import { RequestError } from '@octokit/request-error'
2 | import { Octokit } from '@octokit/rest'
3 | import { logger } from 'firebase-functions/v2'
4 | import { CrashlyticsEvent } from 'firebase-functions/v2/alerts/crashlytics'
5 | import { CrashlyticsAlert, CrashlyticsPayload } from './types'
6 |
7 | const octokit = new Octokit({
8 | auth: process.env.GITHUB_ACCESS_TOKEN,
9 | })
10 |
11 | // ref. https://docs.github.com/ja/rest/issues/issues?apiVersion=2022-11-28#create-an-issue
12 | export async function createGitHubIssueIfEnabled(
13 | event: CrashlyticsEvent
14 | ) {
15 | const alertType = parseAlertType(event.alertType)
16 | logger.info(`alertType: ${alertType}`, { structuredData: true })
17 | if (!process.env.ALERTS?.split(',').includes(alertType)) {
18 | logger.warn(
19 | `Skip the creation of a GitHub issue because ${alertType} alert is not enabled`
20 | )
21 | return
22 | }
23 |
24 | try {
25 | const payload = event.data.payload
26 | await octokit.rest.issues.create({
27 | owner: process.env.GITHUB_OWNER!,
28 | repo: process.env.GITHUB_REPO!,
29 | title: payload.issue.title,
30 | body: makeBody(event),
31 | labels: process.env.GITHUB_LABELS?.split(','),
32 | })
33 | } catch (error) {
34 | if (error instanceof RequestError) {
35 | if (error.status) {
36 | // handle Octokit error
37 | // see https://github.com/octokit/request-error.js
38 | logger.error(error.message)
39 | }
40 | }
41 | logger.error(error, { structuredData: true })
42 | throw error
43 | }
44 | }
45 |
46 | // Examople payload
47 | // {
48 | // id: '2262421223909713056',
49 | // appid: '1:162074331013:android:f5830f1b7bf6ffd8ade6ec',
50 | // type: 'google.firebase.firebasealerts.alerts.v1.published',
51 | // alerttype: 'crashlytics.regression',
52 | // source: '//firebasealerts.googleapis.com/projects/162074331013',
53 | // project: '162074331013',
54 | // specversion: '1.0',
55 | // time: '2025-05-19T00:44:38.808893Z',
56 | // data: {
57 | // '@type': 'type.googleapis.com/google.events.firebase.firebasealerts.v1.AlertData',
58 | // createTime: '2025-05-19T00:44:38.808893Z',
59 | // endTime: '2025-05-19T00:44:38.808893Z',
60 | // payload: {
61 | // issue: [Object],
62 | // '@type': 'type.googleapis.com/google.events.firebase.firebasealerts.v1.CrashlyticsRegressionAlertPayload',
63 | // type: 'fatal',
64 | // resolveTime: '2025-05-18T07:00:00Z'
65 | // }
66 | // },
67 | // traceparent: '00-407a4a49f8d10296ddf36a791a38df95-6f41c394d4759033-01',
68 | // alertType: 'crashlytics.regression',
69 | // appId: '1:162074331013:android:f5830f1b7bf6ffd8ade6ec'
70 | // }
71 | function makeBody(event: CrashlyticsEvent) {
72 | logger.info(event, { structuredData: true })
73 | const { id, subtitle, appVersion } = event.data.payload.issue
74 | const alertType = parseAlertType(event.alertType)
75 | // TODO(tsuruoka): 本来はURLリンクを載せたいものの、`packageName`が取得できず`appId`のみなのでURLを生成できない問題
76 | // フォーマットは以下であることを確認したので、`packageName`が取得できるようになったらURLも表示できる
77 | // https://console.firebase.google.com/project/[PROJECT_ID]/crashlytics/app/[OS]:[PACKAGE_NAME]/issues/[ISSUE_ID]?time=last-seven-days
78 | const appId = event.appId
79 |
80 | return `
81 | ### ${subtitle}
82 |
83 | | Info | Value |
84 | |--------|--------|
85 | | appId | ${appId} |
86 | | alertType | ${alertType} |
87 | | id | ${id} |
88 | | appVersion | ${appVersion} |
89 | `
90 | }
91 |
92 | function parseAlertType(alertType: string) {
93 | return alertType.split('.').at(-1) as CrashlyticsAlert
94 | }
95 |
--------------------------------------------------------------------------------
/trigger-github-issues-from-crashlytics/functions/src/index.ts:
--------------------------------------------------------------------------------
1 | // ref. https://firebase.google.com/docs/functions/alert-events#node.js
2 | // ref. https://firebase.google.com/docs/extensions/publishers/functions#crashlytics
3 | import {
4 | CrashlyticsOptions,
5 | NewAnrIssuePayload,
6 | NewFatalIssuePayload,
7 | NewNonfatalIssuePayload,
8 | onNewAnrIssuePublished,
9 | onNewFatalIssuePublished,
10 | onNewNonfatalIssuePublished,
11 | onRegressionAlertPublished,
12 | RegressionAlertPayload,
13 | } from 'firebase-functions/v2/alerts/crashlytics'
14 | import { createGitHubIssueIfEnabled } from './github_api'
15 |
16 | const options: CrashlyticsOptions = {
17 | region: process.env.LOCATION,
18 | secrets: ['GITHUB_ACCESS_TOKEN'],
19 | }
20 |
21 | // 関数名は`4-63 characters`が制限なので、Extensionsの場合は以下の形式となるので簡素な名前をつけるのがベター
22 | // ext-trigger-github-issues-from-crashlytics-[FUNCTION_NAME]
23 |
24 | // New fatal issue
25 | exports.createFatalIssue = onNewFatalIssuePublished(options, (event) =>
26 | createGitHubIssueIfEnabled(event)
27 | )
28 |
29 | // New non-fatal issue
30 | exports.createNonFatalIssue = onNewNonfatalIssuePublished(options, (event) =>
31 | createGitHubIssueIfEnabled(event)
32 | )
33 |
34 | // New ANR issue
35 | exports.createAnrIssue = onNewAnrIssuePublished(options, (event) =>
36 | createGitHubIssueIfEnabled(event)
37 | )
38 |
39 | // Regression(Better use for report debugging)
40 | exports.regressionAlert = onRegressionAlertPublished(options, (event) =>
41 | createGitHubIssueIfEnabled(event)
42 | )
43 |
--------------------------------------------------------------------------------
/trigger-github-issues-from-crashlytics/functions/src/types.ts:
--------------------------------------------------------------------------------
1 | import {
2 | NewAnrIssuePayload,
3 | NewFatalIssuePayload,
4 | NewNonfatalIssuePayload,
5 | RegressionAlertPayload,
6 | } from 'firebase-functions/v2/alerts/crashlytics'
7 |
8 | export type CrashlyticsAlert =
9 | | 'newFatalIssue'
10 | | 'newNonfatalIssue'
11 | | 'newAnrIssue'
12 | | 'regression'
13 | // | 'stabilityDigest'
14 | // | 'velocity'
15 |
16 | // イベントトリガーで渡ってくるPayloadクラスには継承関係が無い(親クラスを持っていない)ので、
17 | // ジェネリクスで利用できるようにtypeで束ねる
18 | export type CrashlyticsPayload =
19 | | NewFatalIssuePayload
20 | | NewNonfatalIssuePayload
21 | | NewAnrIssuePayload
22 | | RegressionAlertPayload
23 | // | StabilityDigestPayload
24 | // | VelocityAlertPayload
25 |
--------------------------------------------------------------------------------
/trigger-github-issues-from-crashlytics/functions/tsconfig.dev.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": [
3 | ".eslintrc.js"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/trigger-github-issues-from-crashlytics/functions/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "noImplicitReturns": true,
5 | "noUnusedLocals": true,
6 | "outDir": "lib",
7 | "sourceMap": true,
8 | "strict": true,
9 | "target": "es2022"
10 | },
11 | "compileOnSave": true,
12 | "include": ["src"]
13 | }
14 |
--------------------------------------------------------------------------------
/trigger-github-issues-from-crashlytics/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/htsuruo/firebase-extensions/23dc5d1e2f59bfdda41e86164c1b2b330ee4030b/trigger-github-issues-from-crashlytics/icon.png
--------------------------------------------------------------------------------