├── .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 | | SCR-20230529-ukgw-2 | SCR-20230529-ukml | 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 | SCR-20230527-ogal-2 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 | | SCR-20230529-ukgw-2 | SCR-20230529-ukml | 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 | SCR-20230527-ogal-2 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 --------------------------------------------------------------------------------