diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 00000000..7e4337ee --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,20 @@ +{ + "env": { + "browser": true, + "commonjs": true, + "es2021": true + }, + "parserOptions": { + "ecmaVersion": 12, + "sourceType": "module" + }, + "rules": { + "arrow-parens": ["error", "always"], + "arrow-spacing": ["error"], + "no-const-assign": ["error"], + "prefer-const": ["error"], + "prefer-arrow-callback": ["error"], + "spaced-comment": ["error", "always"], + "semi": ["error", "always"] + } +} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..ee2c87f1 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,44 @@ +name: CI Tests + +on: + - pull_request + - push + +env: + CI: true + +jobs: + unit: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: + - 14.x + steps: + - uses: actions/checkout@v1 + - uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + + - name: Cache npm + uses: actions/cache@v1 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package.json') }} + + - name: Install dependencies + run: | + npm install + npm run bootstrap + + - name: Lint snippets + run: npm run lint + + - name: Compile snippets + run: npm run compile + + - name: Check generated snippets + run: | + npm run snippets + ./scripts/checkdirty.sh + diff --git a/.gitignore b/.gitignore index 074ab526..435f90ea 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,8 @@ package-lock.json -node_modules \ No newline at end of file +node_modules + +tsconfig.json +dist/ +.DS_Store +.idea +pnpm-lock.yaml diff --git a/README.md b/README.md index 75e2c2ac..b45ac156 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,28 @@ + # Firebase Web Snippets This repository holds code snippets used in Web documentation on [firebase.google.com](https://firebase.google.com/docs/). +These snippets are part of our documentation and best read in the context of a documentation page rather than used directly. If you're looking to get started with the Firebase Web SDK the best place to start is [quickstart-js](https://github.com/firebase/quickstart-js). + +## Example + +Consider this page: +https://firebase.google.com/docs/database/web/lists-of-data + +Each snippet in the page is dynamically included from the source in this repository, in this case mostly from this file: +https://github.com/firebase/snippets-web/blob/master/database/lists-of-data.js + +Each snippet has a "region tag" which is defined by `// [START tag]` and `// [END tag]` comments. The code between the tags can be included in our documentation. Keeping the code on GitHub, rather than hard-coded into the HTML of our documentation, allows us to ensure the code is correct and up to date. + ## Contributing We love contributions! See [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines. + +## Build Status + +[![Actions Status][gh-actions-badge]][gh-actions] + +[gh-actions]: https://github.com/firebase/snippets-web/actions +[gh-actions-badge]: https://github.com/firebase/snippets-web/workflows/CI%20Tests/badge.svg diff --git a/SNIPPETS.md b/SNIPPETS.md new file mode 100644 index 00000000..7d0f6876 --- /dev/null +++ b/SNIPPETS.md @@ -0,0 +1,26 @@ +# Generated Snippets + +## Overview + +The `snippets` directory contains snippets generated by the `separate-snippets` script. +Snippets in this folder should **never** be updated directly instead please +edit the source file (indicated by a comment at the top). + +## Regenerating the Snippets + +Run `npm run snippets` from the root of this repository. + +## Using the Separator + +For a file to be included in the separator script it must contain a comment like this: + +```js +// [SNIPPETS_SEPARATION enabled] +``` + +By default separated snippets will have their name suffixed with `_modular` +but you can override this with a commment: + +```js +// [SNIPPETS_SUFFIX _banana] +``` diff --git a/analytics-next/ecommerce.js b/analytics-next/ecommerce.js new file mode 100644 index 00000000..081aad48 --- /dev/null +++ b/analytics-next/ecommerce.js @@ -0,0 +1,284 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +// [START analytics_ecommerce_items] +// A pair of jeggings +const item_jeggings = { + item_id: 'SKU_123', + item_name: 'jeggings', + item_category: 'pants', + item_variant: 'black', + item_brand: 'Google', + price: 9.99 +}; + +// A pair of boots +const item_boots = { + item_id: 'SKU_456', + item_name: 'boots', + item_category: 'shoes', + item_variant: 'brown', + item_brand: 'Google', + price: 24.99 +}; + +// A pair of socks +const item_socks = { + item_id: 'SKU_789', + item_name: 'ankle_socks', + item_category: 'socks', + item_variant: 'red', + item_brand: 'Google', + price: 5.99 +}; +// [END analytics_ecommerce_items] + +function ecommerceViewItemList() { + // [START analytics_ecommerce_view_item_list] + const { getAnalytics, logEvent } = require("firebase/analytics"); + + // Prepare ecommerce params + const params1 = { + item_list_id: 'L001', + item_list_name: 'Related products', + items: [item_jeggings, item_boots, item_socks] + }; + + // Log event + const analytics = getAnalytics(); + logEvent(analytics, 'view_item_list', params1); + // [END analytics_ecommerce_view_item_list] +} + +function ecommerceSelectItem() { + // [START analytics_ecommerce_select_item] + const { getAnalytics, logEvent } = require("firebase/analytics"); + + // Prepare ecommerce event params + const params2 = { + item_list_id: 'L001', + item_list_name: 'Related products', + items: [item_jeggings] + }; + + // Log event + const analytics = getAnalytics(); + logEvent(analytics, 'select_item', params2); + // [END analytics_ecommerce_select_item] +} + +function ecommerceViewItemDetails() { + // [START analytics_ecommerce_view_item_details] + const { getAnalytics, logEvent } = require("firebase/analytics"); + + // Prepare ecommerce event params + const params3 = { + currency: 'USD', + value: 9.99, + items: [item_jeggings] + }; + + // Log event + const analytics = getAnalytics(); + logEvent(analytics, 'view_item', params3); + // [END analytics_ecommerce_view_item_details] +} + +function ecommerceAddCart() { + // [START analytics_ecommerce_add_cart] + const { getAnalytics, logEvent } = require("firebase/analytics"); + + // Specify order quantity + const item_jeggings_quantity = { + ...item_jeggings, + quantity: 2 + }; + + // Prepare ecommerce bundle + const params4 = { + currency: 'USD', + value: 19.98, + items: [item_jeggings_quantity] + }; + + // Log event when a product is added to a wishlist + const analytics = getAnalytics(); + logEvent(analytics, 'add_to_wishlist', params4); + + // Log event when a product is added to the cart + logEvent(analytics, 'add_to_cart', params4); + // [END analytics_ecommerce_add_cart] +} + +function ecommerceViewCart() { + // [START analytics_ecommerce_view_cart] + const { getAnalytics, logEvent } = require("firebase/analytics"); + + // Specify order quantity + const item_jeggings_quantity = { + ...item_jeggings, + quantity: 2 + }; + + const item_boots_quantity = { + ...item_boots, + quantity: 1 + }; + + // Prepare ecommerce params + const params5 = { + currency: 'USD', + value: 44.97, + items: [item_jeggings_quantity, item_boots_quantity] + }; + + // Log event when the cart is viewed + const analytics = getAnalytics(); + logEvent(analytics, 'view_cart', params5); + // [END analytics_ecommerce_view_cart] +} + +function ecommerceRemoveCart() { + // [START analytics_ecommerce_remove_cart] + const { getAnalytics, logEvent } = require("firebase/analytics"); + + // Prepare ecommerce params + const params6 = { + currency: 'USD', + value: 24.99, + items: [item_jeggings] + }; + + // Log event + const analytics = getAnalytics(); + logEvent(analytics, 'remove_from_cart', params6); + // [END analytics_ecommerce_remove_cart] +} + +function ecommerceCheckout() { + // [START analytics_ecommerce_checkout] + const { getAnalytics, logEvent } = require("firebase/analytics"); + + // Prepare ecommerce params + const params7 = { + currency: 'USD', + value: 14.98, // Total Revenue + coupon: 'SUMMER_FUN', + items: [item_jeggings] + }; + + // Log event + const analytics = getAnalytics(); + logEvent(analytics, 'begin_checkout', params7); + // [END analytics_ecommerce_checkout] +} + +function ecommerceShippingInfo() { + // [START analytics_ecommerce_shipping_info] + const { getAnalytics, logEvent } = require("firebase/analytics"); + + // Prepare ecommerce params + const params8 = { + currency: 'USD', + value: 14.98, // Total Revenue + coupon: 'SUMMER_FUN', + shipping_tier: 'Ground', + items: [item_jeggings] + }; + + // Log event + const analytics = getAnalytics(); + logEvent(analytics, 'add_shipping_info', params8); + // [END analytics_ecommerce_shipping_info] +} + +function ecommercePaymentInfo() { + // [START analytics_ecommerce_payment_info] + const { getAnalytics, logEvent } = require("firebase/analytics"); + + // Prepare ecommerce params + const params9 = { + currency: 'USD', + value: 14.98, // Total Revenue + coupon: 'SUMMER_FUN', + payment_type: 'Visa', + items: [item_jeggings] + }; + + // Log event + const analytics = getAnalytics(); + logEvent(analytics, 'add_payment_info', params9); + // [END analytics_ecommerce_payment_info] +} + +function ecommercePurchase() { + // [START analytics_ecommerce_purchase] + const { getAnalytics, logEvent } = require("firebase/analytics"); + + // Prepare ecommerce bundle + const params10 = { + transaction_id: 'T12345', + affiliation: 'Google Store', + currency: 'USD', + value: 14.98, // Total Revenue + tax: 2.85, + shipping: 5.34, + coupon: 'SUMMER_FUN', + items: [item_jeggings] + }; + + // Log event + const analytics = getAnalytics(); + logEvent(analytics, 'purchase', params10); + // [END analytics_ecommerce_purchase] +} + +function ecommerceRefund() { + // [START analytics_ecommerce_refund] + const { getAnalytics, logEvent } = require("firebase/analytics"); + + // Prepare ecommerce params + const params11 = { + transaction_id: 'T12345', // Required + affiliation: 'Google Store', + currency: 'USD', + value: 9.99, + items: [] + }; + + // (Optional) For partial refunds, define the item_id and quantity of refunded items + const refundedProduct = { + item_id: 'SKU_123', // Required + quantity: 1 // Required + }; + + params11.items.push(refundedProduct); + + // Log event + const analytics = getAnalytics(); + logEvent(analytics, 'refund', params11); + // [END analytics_ecommerce_refund] +} + +function ecommercePromotions() { + // [START analytics_ecommerce_promotions] + const { getAnalytics, logEvent } = require("firebase/analytics"); + + // Prepare ecommerce params + const params12 = { + promotion_id: 'ABC123', + promotion_name: 'Summer Sale', + creative_name: 'summer2020_promo.jpg', + creative_slot: 'featured_app_1', + location_id: 'HERO_BANNER', + items: [item_jeggings] + }; + + // Log event when a promotion is displayed + const analytics = getAnalytics(); + logEvent(analytics, 'view_promotion', params12); + + // Log event when a promotion is selected + logEvent(analytics, 'select_promotion', params12); + // [END analytics_ecommerce_promotions] +} diff --git a/analytics-next/index.js b/analytics-next/index.js new file mode 100644 index 00000000..2b736cde --- /dev/null +++ b/analytics-next/index.js @@ -0,0 +1,66 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +import { initializeApp } from "firebase/app"; + +function initialize() { + // [START analytics_initialize] + const { getAnalytics } = require("firebase/analytics"); + + const analytics = getAnalytics(); + // [END analytics_initialize] +} + +function logEvent() { + // [START analytics_log_event] + const { getAnalytics, logEvent } = require("firebase/analytics"); + + const analytics = getAnalytics(); + logEvent(analytics, 'notification_received'); + // [END analytics_log_event] +} + +function logEventParams() { + // [START analytics_log_event_params] + const { getAnalytics, logEvent } = require("firebase/analytics"); + + const analytics = getAnalytics(); + logEvent(analytics, 'select_content', { + content_type: 'image', + content_id: 'P12453' + }); + // [END analytics_log_event_params] +} + +function logEventCustomParams() { + // [START analytics_log_event_custom_params] + const { getAnalytics, logEvent } = require("firebase/analytics"); + + const analytics = getAnalytics(); + logEvent(analytics, 'goal_completion', { name: 'lever_puzzle'}); + // [END analytics_log_event_custom_params] +} + +function setUserProperties() { + // [START analytics_set_user_properties] + const { getAnalytics, setUserProperties } = require("firebase/analytics"); + + const analytics = getAnalytics(); + setUserProperties(analytics, { favorite_food: 'apples' }); + // [END analytics_set_user_properties] +} + +function recordScreenView() { + const screenName = ''; + const screenClass = ''; + + // [START analytics_record_screen_view] + const { getAnalytics, logEvent } = require("firebase/analytics"); + + const analytics = getAnalytics(); + logEvent(analytics, 'screen_view', { + firebase_screen: screenName, + firebase_screen_class: screenClass + }); + // [END analytics_record_screen_view] +} diff --git a/analytics-next/package.json b/analytics-next/package.json new file mode 100644 index 00000000..ee4e36ff --- /dev/null +++ b/analytics-next/package.json @@ -0,0 +1,11 @@ +{ + "name": "analytics-next", + "version": "1.0.0", + "scripts": { + "compile": "cp ../tsconfig.json.template ./tsconfig.json && tsc" + }, + "license": "Apache-2.0", + "dependencies": { + "firebase": "^10.0.0" + } +} diff --git a/analytics/ecommerce.js b/analytics/ecommerce.js new file mode 100644 index 00000000..6e5216bd --- /dev/null +++ b/analytics/ecommerce.js @@ -0,0 +1,248 @@ +import firebase from "firebase/app"; +import "firebase/analytics"; + +// [START analytics_ecommerce_items] +// A pair of jeggings +const item_jeggings = { + item_id: 'SKU_123', + item_name: 'jeggings', + item_category: 'pants', + item_variant: 'black', + item_brand: 'Google', + price: 9.99 +}; + +// A pair of boots +const item_boots = { + item_id: 'SKU_456', + item_name: 'boots', + item_category: 'shoes', + item_variant: 'brown', + item_brand: 'Google', + price: 24.99 +}; + +// A pair of socks +const item_socks = { + item_id: 'SKU_789', + item_name: 'ankle_socks', + item_category: 'socks', + item_variant: 'red', + item_brand: 'Google', + price: 5.99 +}; +// [END analytics_ecommerce_items] + +function ecommerceViewItemList() { + // [START analytics_ecommerce_view_item_list] + // Prepare ecommerce params + const params1 = { + item_list_id: 'L001', + item_list_name: 'Related products', + items: [item_jeggings, item_boots, item_socks] + }; + + // Log event + firebase.analytics().logEvent(firebase.analytics.EventName.VIEW_ITEM_LIST, params1); + // [END analytics_ecommerce_view_item_list] +} + +function ecommerceSelectItem() { + // [START analytics_ecommerce_select_item] + // Prepare ecommerce event params + const params2 = { + item_list_id: 'L001', + item_list_name: 'Related products', + items: [item_jeggings] + }; + + // Log event + firebase.analytics().logEvent(firebase.analytics.EventName.SELECT_ITEM, params2); + // [END analytics_ecommerce_select_item] +} + +function ecommerceViewItemDetails() { + // [START analytics_ecommerce_view_item_details] + // Prepare ecommerce event params + const params3 = { + currency: 'USD', + value: 9.99, + items: [item_jeggings] + }; + + // Log event + firebase.analytics().logEvent(firebase.analytics.EventName.VIEW_ITEM, params3); + // [END analytics_ecommerce_view_item_details] +} + +function ecommerceAddCart() { + // [START analytics_ecommerce_add_cart] + // Specify order quantity + const item_jeggings_quantity = { + ...item_jeggings, + quantity: 2 + }; + + // Prepare ecommerce bundle + const params4 = { + currency: 'USD', + value: 19.98, + items: [item_jeggings_quantity] + }; + + // Log event when a product is added to a wishlist + firebase.analytics().logEvent(firebase.analytics.EventName.ADD_TO_WISHLIST, params4); + + // Log event when a product is added to the cart + firebase.analytics().logEvent(firebase.analytics.EventName.ADD_TO_CART, params4); + // [END analytics_ecommerce_add_cart] +} + +function ecommerceViewCart() { + // [START analytics_ecommerce_view_cart] + // Specify order quantity + const item_jeggings_quantity = { + ...item_jeggings, + quantity: 2 + }; + + const item_boots_quantity = { + ...item_boots, + quantity: 1 + }; + + // Prepare ecommerce params + const params5 = { + currency: 'USD', + value: 44.97, + items: [item_jeggings_quantity, item_boots_quantity] + }; + + // Log event when the cart is viewed + firebase.analytics().logEvent(firebase.analytics.EventName.VIEW_CART, params5); + // [END analytics_ecommerce_view_cart] +} + +function ecommerceRemoveCart() { + // [START analytics_ecommerce_remove_cart] + // Prepare ecommerce params + const params6 = { + currency: 'USD', + value: 24.99, + items: [item_jeggings] + }; + + // Log event + firebase.analytics().logEvent(firebase.analytics.EventName.REMOVE_FROM_CART, params6); + // [END analytics_ecommerce_remove_cart] +} + +function ecommerceCheckout() { + // [START analytics_ecommerce_checkout] + // Prepare ecommerce params + const params7 = { + currency: 'USD', + value: 14.98, // Total Revenue + coupon: 'SUMMER_FUN', + items: [item_jeggings] + }; + + // Log event + firebase.analytics().logEvent(firebase.analytics.EventName.BEGIN_CHECKOUT, params7); + // [END analytics_ecommerce_checkout] +} + +function ecommerceShippingInfo() { + // [START analytics_ecommerce_shipping_info] + // Prepare ecommerce params + const params8 = { + currency: 'USD', + value: 14.98, // Total Revenue + coupon: 'SUMMER_FUN', + shipping_tier: 'Ground', + items: [item_jeggings] + }; + + // Log event + firebase.analytics().logEvent(firebase.analytics.EventName.ADD_SHIPPING_INFO, params8); + // [END analytics_ecommerce_shipping_info] +} + +function ecommercePaymentInfo() { + // [START analytics_ecommerce_payment_info] + // Prepare ecommerce params + const params9 = { + currency: 'USD', + value: 14.98, // Total Revenue + coupon: 'SUMMER_FUN', + payment_type: 'Visa', + items: [item_jeggings] + }; + + // Log event + firebase.analytics().logEvent(firebase.analytics.EventName.ADD_PAYMENT_INFO, params9); + // [END analytics_ecommerce_payment_info] +} + +function ecommercePurchase() { + // [START analytics_ecommerce_purchase] + // Prepare ecommerce bundle + const params10 = { + transaction_id: 'T12345', + affiliation: 'Google Store', + currency: 'USD', + value: 14.98, // Total Revenue + tax: 2.85, + shipping: 5.34, + coupon: 'SUMMER_FUN', + items: [item_jeggings] + }; + + // Log event + firebase.analytics().logEvent(firebase.analytics.EventName.PURCHASE, params10); + // [END analytics_ecommerce_purchase] +} + +function ecommerceRefund() { + // [START analytics_ecommerce_refund] + // Prepare ecommerce params + const params11 = { + transaction_id: 'T12345', // Required + affiliation: 'Google Store', + currency: 'USD', + value: 9.99, + items: [] + }; + + // (Optional) For partial refunds, define the item_id and quantity of refunded items + const refundedProduct = { + item_id: 'SKU_123', // Required + quantity: 1 // Required + }; + + params11.items.push(refundedProduct); + + // Log event + firebase.analytics().logEvent(firebase.analytics.EventName.REFUND, params11); + // [END analytics_ecommerce_refund] +} + +function ecommercePromotions() { + // [START analytics_ecommerce_promotions] + // Prepare ecommerce params + const params12 = { + promotion_id: 'ABC123', + promotion_name: 'Summer Sale', + creative_name: 'summer2020_promo.jpg', + creative_slot: 'featured_app_1', + location_id: 'HERO_BANNER', + items: [item_jeggings] + }; + + // Log event when a promotion is displayed + firebase.analytics().logEvent(firebase.analytics.EventName.VIEW_PROMOTION, params12); + + // Log event when a promotion is selected + firebase.analytics().logEvent(firebase.analytics.EventName.SELECT_PROMOTION, params12); + // [END analytics_ecommerce_promotions] +} diff --git a/analytics/index.js b/analytics/index.js new file mode 100644 index 00000000..76d2c289 --- /dev/null +++ b/analytics/index.js @@ -0,0 +1,52 @@ +import firebase from "firebase/app"; +import "firebase/analytics"; + +function initialize() { + // [START analytics_initialize] + const analytics = firebase.analytics(); + // [END analytics_initialize] +} + +function logEvent() { + // [START analytics_log_event] + firebase.analytics().logEvent('notification_received'); + // [END analytics_log_event] +} + +function logEventParams() { + const analytics = firebase.analytics(); + + // [START analytics_log_event_params] + analytics.logEvent('select_content', { + content_type: 'image', + content_id: 'P12453', + items: [{ name: 'Kittens' }] + }); + // [END analytics_log_event_params] +} + +function logEventCustomParams() { + const analytics = firebase.analytics(); + + // [START analytics_log_event_custom_params] + analytics.logEvent('goal_completion', { name: 'lever_puzzle'}); + // [END analytics_log_event_custom_params] +} + +function setUserProperties() { + // [START analytics_set_user_properties] + firebase.analytics().setUserProperties({favorite_food: 'apples'}); + // [END analytics_set_user_properties] +} + +function recordScreenView() { + const screenName = ''; + const screenClass = ''; + + // [START analytics_record_screen_view] + firebase.analytics().logEvent('screen_view', { + firebase_screen: screenName, + firebase_screen_class: screenClass + }); + // [END analytics_record_screen_view] +} diff --git a/analytics/package.json b/analytics/package.json new file mode 100644 index 00000000..b344a8a6 --- /dev/null +++ b/analytics/package.json @@ -0,0 +1,11 @@ +{ + "name": "analytics", + "version": "1.0.0", + "scripts": { + "compile": "cp ../tsconfig.json.template ./tsconfig.json && tsc" + }, + "license": "Apache-2.0", + "dependencies": { + "firebase": "^8.10.0" + } +} diff --git a/appcheck-next/index.js b/appcheck-next/index.js new file mode 100644 index 00000000..f5598ebd --- /dev/null +++ b/appcheck-next/index.js @@ -0,0 +1,110 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +function initialize() { + // [START appcheck_initialize] + const { initializeApp } = require("firebase/app"); + const { initializeAppCheck, ReCaptchaV3Provider } = require("firebase/app-check"); + + const app = initializeApp({ + // Your firebase configuration object + }); + + // Pass your reCAPTCHA v3 site key (public key) to activate(). Make sure this + // key is the counterpart to the secret key you set in the Firebase console. + const appCheck = initializeAppCheck(app, { + provider: new ReCaptchaV3Provider('abcdefghijklmnopqrstuvwxy-1234567890abcd'), + + // Optional argument. If true, the SDK automatically refreshes App Check + // tokens as needed. + isTokenAutoRefreshEnabled: true + }); + // [END appcheck_initialize] +} + +function customProvider() { + // [START appcheck_custom_provider] + const { CustomProvider } = require("firebase/app-check"); + + const appCheckCustomProvider = new CustomProvider({ + getToken: () => { + return new Promise((resolve, _reject) => { + // TODO: Logic to exchange proof of authenticity for an App Check token and + // expiration time. + + // [START_EXCLUDE] + const tokenFromServer = "abc1234"; + const expirationFromServer = 1234; + // [END_EXCLUDE] + + const appCheckToken = { + token: tokenFromServer, + expireTimeMillis: expirationFromServer * 1000 + }; + + resolve(appCheckToken); + }); + } + }); + // [END appcheck_custom_provider] + + return appCheckCustomProvider; +} + +function initializeCustomProvider() { + const appCheckCustomProvider = customProvider(); + + // [START appcheck_initialize_custom_provider] + const { initializeApp } = require("firebase/app"); + const { initializeAppCheck } = require("firebase/app-check"); + + const app = initializeApp({ + // Your firebase configuration object + }); + + const appCheck = initializeAppCheck(app, { + provider: appCheckCustomProvider, + + // Optional argument. If true, the SDK automatically refreshes App Check + // tokens as needed. + isTokenAutoRefreshEnabled: true + }); + // [END appcheck_initialize_custom_provider] +} + +function nonFirebase() { + const { initializeApp } = require("firebase/app"); + const app = initializeApp({ + // Your firebase configuration object + }); + const { ReCaptchaV3Provider } = require('firebase/app-check'); + const provider = new ReCaptchaV3Provider(''); + + // [START appcheck_nonfirebase] + const { initializeAppCheck, getToken } = require('firebase/app-check'); + + const appCheck = initializeAppCheck( + app, + { provider: provider } // ReCaptchaV3Provider or CustomProvider + ); + + const callApiWithAppCheckExample = async () => { + let appCheckTokenResponse; + try { + appCheckTokenResponse = await getToken(appCheck, /* forceRefresh= */ false); + } catch (err) { + // Handle any errors if the token was not retrieved. + return; + } + + // Include the App Check token with requests to your server. + const apiResponse = await fetch('https://yourbackend.example.com/yourApiEndpoint', { + headers: { + 'X-Firebase-AppCheck': appCheckTokenResponse.token, + } + }); + + // Handle response from your backend. + }; + // [END appcheck_nonfirebase] +} diff --git a/appcheck-next/package.json b/appcheck-next/package.json new file mode 100644 index 00000000..e863723e --- /dev/null +++ b/appcheck-next/package.json @@ -0,0 +1,11 @@ +{ + "name": "appcheck-next", + "version": "1.0.0", + "scripts": { + "compile": "cp ../tsconfig.json.template ./tsconfig.json && tsc" + }, + "license": "Apache-2.0", + "dependencies": { + "firebase": "^10.0.0" + } +} diff --git a/appcheck/index.js b/appcheck/index.js new file mode 100644 index 00000000..3721b16c --- /dev/null +++ b/appcheck/index.js @@ -0,0 +1,86 @@ +import firebase from "firebase/app"; +import "firebase/app-check"; + +function initialize() { + // [START appcheck_initialize] + firebase.initializeApp({ + // Your firebase configuration object + }); + + const appCheck = firebase.appCheck(); + // Pass your reCAPTCHA v3 site key (public key) to activate(). Make sure this + // key is the counterpart to the secret key you set in the Firebase console. + appCheck.activate( + 'abcdefghijklmnopqrstuvwxy-1234567890abcd', + + // Optional argument. If true, the SDK automatically refreshes App Check + // tokens as needed. + true); + // [END appcheck_initialize] +} + +function customProvider() { + // [START appcheck_custom_provider] + const appCheckCustomProvider = { + getToken: () => { + return new Promise((resolve, _reject) => { + // TODO: Logic to exchange proof of authenticity for an App Check token and + // expiration time. + + // [START_EXCLUDE] + const tokenFromServer = "abc1234"; + const expirationFromServer = 1234; + // [END_EXCLUDE] + + const appCheckToken = { + token: tokenFromServer, + expireTimeMillis: expirationFromServer * 1000 + }; + + resolve(appCheckToken); + }); + } + }; + // [END appcheck_custom_provider] + + return appCheckCustomProvider; +} + +function initializeCustomProvider() { + const appCheckCustomProvider = customProvider(); + + // [START appcheck_initialize_custom_provider] + firebase.initializeApp({ + // Your firebase configuration object + }); + + const appCheck = firebase.appCheck(); + appCheck.activate( + appCheckCustomProvider, + + // Optional argument. If true, the SDK automatically refreshes App Check + // tokens as needed. + true); + // [END appcheck_initialize_custom_provider] +} + +// [START appcheck_nonfirebase] +const callApiWithAppCheckExample = async () => { + let appCheckTokenResponse; + try { + appCheckTokenResponse = await firebase.appCheck().getToken(/* forceRefresh= */ false); + } catch (err) { + // Handle any errors if the token was not retrieved. + return; + } + + // Include the App Check token with requests to your server. + const apiResponse = await fetch('https://yourbackend.example.com/yourApiEndpoint', { + headers: { + 'X-Firebase-AppCheck': appCheckTokenResponse.token, + } + }); + + // Handle response from your backend. +}; +// [END appcheck_nonfirebase] diff --git a/appcheck/package.json b/appcheck/package.json new file mode 100644 index 00000000..d9d819fa --- /dev/null +++ b/appcheck/package.json @@ -0,0 +1,11 @@ +{ + "name": "appcheck", + "version": "1.0.0", + "scripts": { + "compile": "cp ../tsconfig.json.template ./tsconfig.json && tsc" + }, + "license": "Apache-2.0", + "dependencies": { + "firebase": "^8.10.0" + } +} diff --git a/auth-next/anonymous.js b/auth-next/anonymous.js new file mode 100644 index 00000000..33d5a936 --- /dev/null +++ b/auth-next/anonymous.js @@ -0,0 +1,19 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +function anonSignIn() { + // [START auth_anon_sign_in] + const { getAuth, signInAnonymously } = require("firebase/auth"); + + const auth = getAuth(); + signInAnonymously(auth) + .then(() => { + // Signed in.. + }) + .catch((error) => { + const errorCode = error.code; + const errorMessage = error.message; + // ... + }); + // [END auth_anon_sign_in] +} diff --git a/auth-next/apple.js b/auth-next/apple.js new file mode 100644 index 00000000..2777a085 --- /dev/null +++ b/auth-next/apple.js @@ -0,0 +1,209 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +// Docs: https://source.corp.google.com/piper///depot/google3/third_party/devsite/firebase/en/docs/auth/web/apple.md + +function appleProvider() { + // [START auth_apple_provider_create] + const { OAuthProvider } = require("firebase/auth"); + + const provider = new OAuthProvider('apple.com'); + // [END auth_apple_provider_create] + + // [START auth_apple_provider_scopes] + provider.addScope('email'); + provider.addScope('name'); + // [END auth_apple_provider_scopes] + + // [START auth_apple_provider_params] + provider.setCustomParameters({ + // Localize the Apple authentication screen in French. + locale: 'fr' + }); + // [END auth_apple_provider_params] +} + +function appleSignInPopup(provider) { + // [START auth_apple_signin_popup] + const { getAuth, signInWithPopup, OAuthProvider } = require("firebase/auth"); + + const auth = getAuth(); + signInWithPopup(auth, provider) + .then((result) => { + // The signed-in user info. + const user = result.user; + + // Apple credential + const credential = OAuthProvider.credentialFromResult(result); + const accessToken = credential.accessToken; + const idToken = credential.idToken; + + // IdP data available using getAdditionalUserInfo(result) + // ... + }) + .catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The credential that was used. + const credential = OAuthProvider.credentialFromError(error); + + // ... + }); + // [END auth_apple_signin_popup] +} + +function appleSignInRedirect(provider) { + // [START auth_apple_signin_redirect] + const { getAuth, signInWithRedirect } = require("firebase/auth"); + + const auth = getAuth(); + signInWithRedirect(auth, provider); + // [END auth_apple_signin_redirect] +} + +function appleSignInRedirectResult() { + // [START auth_apple_signin_redirect_result] + const { getAuth, getRedirectResult, OAuthProvider } = require("firebase/auth"); + + // Result from Redirect auth flow. + const auth = getAuth(); + getRedirectResult(auth) + .then((result) => { + const credential = OAuthProvider.credentialFromResult(result); + if (credential) { + // You can also get the Apple OAuth Access and ID Tokens. + const accessToken = credential.accessToken; + const idToken = credential.idToken; + } + // The signed-in user info. + const user = result.user; + }) + .catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The credential that was used. + const credential = OAuthProvider.credentialFromError(error); + + // ... + }); + // [END auth_apple_signin_redirect_result] +} + +function appleReauthenticatePopup() { + // [START auth_apple_reauthenticate_popup] + const { getAuth, reauthenticateWithPopup, OAuthProvider } = require("firebase/auth"); + + // Result from Redirect auth flow. + const auth = getAuth(); + const provider = new OAuthProvider('apple.com'); + + reauthenticateWithPopup(auth.currentUser, provider) + .then((result) => { + // User is re-authenticated with fresh tokens minted and can perform + // sensitive operations like account deletion, or updating their email + // address or password. + + // The signed-in user info. + const user = result.user; + + // You can also get the Apple OAuth Access and ID Tokens. + const credential = OAuthProvider.credentialFromResult(result); + const accessToken = credential.accessToken; + const idToken = credential.idToken; + + // ... + }) + .catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The credential that was used. + const credential = OAuthProvider.credentialFromError(error); + + // ... + }); + // [END auth_apple_reauthenticate_popup] +} + +function appleLinkFacebook() { + // [START auth_apple_link_facebook] + const { getAuth, linkWithPopup, FacebookAuthProvider } = require("firebase/auth"); + + const auth = getAuth(); + const provider = new FacebookAuthProvider(); + provider.addScope('user_birthday'); + + // Assuming the current user is an Apple user linking a Facebook provider. + linkWithPopup(auth.currentUser, provider) + .then((result) => { + // Facebook credential is linked to the current Apple user. + // ... + + // The user can now sign in to the same account + // with either Apple or Facebook. + }) + .catch((error) => { + // Handle error. + }); + // [END auth_apple_link_facebook] +} + +function appleNonceNode() { + // [START auth_apple_nonce_node] + const crypto = require("crypto"); + const string_decoder = require("string_decoder"); + + // Generate a new random string for each sign-in + const generateNonce = (length) => { + const decoder = new string_decoder.StringDecoder("ascii"); + const buf = Buffer.alloc(length); + let nonce = ""; + while (nonce.length < length) { + crypto.randomFillSync(buf); + nonce = decoder.write(buf); + } + return nonce.slice(0, length); + }; + + const unhashedNonce = generateNonce(10); + + // SHA256-hashed nonce in hex + const hashedNonceHex = crypto.createHash('sha256') + .update(unhashedNonce).digest().toString('hex'); + // [END auth_apple_nonce_node] +} + +function appleSignInNonce(appleIdToken, unhashedNonce,) { + // [START auth_apple_signin_nonce] + const { getAuth, signInWithCredential, OAuthProvider } = require("firebase/auth"); + + const auth = getAuth(); + + // Build Firebase credential with the Apple ID token. + const provider = new OAuthProvider('apple.com'); + const authCredential = provider.credential({ + idToken: appleIdToken, + rawNonce: unhashedNonce, + }); + + // Sign in with credential form the Apple user. + signInWithCredential(auth, authCredential) + .then((result) => { + // User signed in. + }) + .catch((error) => { + // An error occurred. If error.code == 'auth/missing-or-invalid-nonce', + // make sure you're sending the SHA256-hashed nonce as a hex string + // with your request to Apple. + console.log(error); + }); + // [END auth_apple_signin_nonce] +} diff --git a/auth-next/auth-state-persistence.js b/auth-next/auth-state-persistence.js new file mode 100644 index 00000000..2d36c429 --- /dev/null +++ b/auth-next/auth-state-persistence.js @@ -0,0 +1,48 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +function setPersistenceSession() { + const email = "..."; + const password = "..."; + + // [START auth_set_persistence_session] + const { getAuth, setPersistence, signInWithEmailAndPassword, browserSessionPersistence } = require("firebase/auth"); + + const auth = getAuth(); + setPersistence(auth, browserSessionPersistence) + .then(() => { + // Existing and future Auth states are now persisted in the current + // session only. Closing the window would clear any existing state even + // if a user forgets to sign out. + // ... + // New sign-in will be persisted with session persistence. + return signInWithEmailAndPassword(auth, email, password); + }) + .catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + }); + // [END auth_set_persistence_session] +} + +function setPersistenceNone() { + // [START auth_set_persistence_none] + const { getAuth, setPersistence, signInWithRedirect, inMemoryPersistence, GoogleAuthProvider } = require("firebase/auth"); + + const auth = getAuth(); + setPersistence(auth, inMemoryPersistence) + .then(() => { + const provider = new GoogleAuthProvider(); + // In memory persistence will be applied to the signed in Google user + // even though the persistence was set to 'none' and a page redirect + // occurred. + return signInWithRedirect(auth, provider); + }) + .catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + }); + // [END auth_set_persistence_none] +} diff --git a/auth-next/cordova.js b/auth-next/cordova.js new file mode 100644 index 00000000..ff91dfcc --- /dev/null +++ b/auth-next/cordova.js @@ -0,0 +1,64 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +// Docs: https://source.corp.google.com/piper///depot/google3/third_party/devsite/firebase/en/docs/auth/web/cordova.md + +function createGoogleProvider() { + // [START auth_create_google_provider] + const { GoogleAuthProvider } = require("firebase/auth/cordova"); + + const provider = new GoogleAuthProvider(); + // [END auth_create_google_provider] +} + +function cordovaSignInRedirect() { + // [START auth_cordova_sign_in_redirect] + const { getAuth, signInWithRedirect, getRedirectResult, GoogleAuthProvider } = require("firebase/auth/cordova"); + + const auth = getAuth(); + signInWithRedirect(auth, new GoogleAuthProvider()) + .then(() => { + return getRedirectResult(auth); + }) + .then((result) => { + const credential = GoogleAuthProvider.credentialFromResult(result); + + // This gives you a Google Access Token. + // You can use it to access the Google API. + const token = credential.accessToken; + + // The signed-in user info. + const user = result.user; + // ... + }).catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + }); + // [END auth_cordova_sign_in_redirect] +} + +function cordovaRedirectResult() { + // [START auth_cordova_redirect_result] + const { getAuth, getRedirectResult, GoogleAuthProvider } = require("firebase/auth/cordova"); + + const auth = getAuth(); + getRedirectResult(auth) + .then((result) => { + const credential = GoogleAuthProvider.credentialFromResult(result); + if (credential) { + // This gives you a Google Access Token. + // You can use it to access the Google API. + const token = credential.accessToken; + // The signed-in user info. + const user = result.user; + // ... + } + }) + .catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + }); + // [END auth_cordova_redirect_result] +} diff --git a/auth-next/custom-dependencies.js b/auth-next/custom-dependencies.js new file mode 100644 index 00000000..209b6767 --- /dev/null +++ b/auth-next/custom-dependencies.js @@ -0,0 +1,58 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +// Docs: https://source.corp.google.com/piper///depot/google3/third_party/devsite/firebase/en/docs/auth/web/custom-dependencies.md + +function getAuthEquivalent() { + // [START auth_get_auth_equivalent] + const {initializeAuth, browserLocalPersistence, browserPopupRedirectResolver, browserSessionPersistence, indexedDBLocalPersistence} = require("firebase/auth"); + const {initializeApp} = require("firebase/app"); + + const app = initializeApp({/** Your app config */}); + const auth = initializeAuth(app, { + persistence: [indexedDBLocalPersistence, browserLocalPersistence, browserSessionPersistence], + popupRedirectResolver: browserPopupRedirectResolver, + }); + // [END auth_get_auth_equivalent] +} + +function onlyBrowserLocal() { + // [START auth_only_browser_local] + const {initializeAuth, browserLocalPersistence} = require("firebase/auth"); + const {initializeApp} = require("firebase/app"); + + const app = initializeApp({/** Your app config */}); + const auth = initializeAuth(app, { + persistence: browserLocalPersistence, + // No popupRedirectResolver defined + }); + // [END auth_only_browser_local] +} + +function onlyIndexedDB() { + // [START auth_only_indexed_db] + const {initializeAuth, indexedDBLocalPersistence} = require("firebase/auth"); + const {initializeApp} = require("firebase/app"); + + const app = initializeApp({/** Your app config */}); + const auth = initializeAuth(app, { + persistence: indexedDBLocalPersistence, + // No popupRedirectResolver defined + }); + // [END auth_only_indexed_db] +} + +function signInRedirectManualDeps() { + // [START auth_sign_in_redirect_manual_deps] + const {initializeAuth, browserLocalPersistence, browserPopupRedirectResolver, indexedDBLocalPersistence, signInWithRedirect, GoogleAuthProvider} = require("firebase/auth"); + const {initializeApp} = require("firebase/app"); + + const app = initializeApp({/** Your app config */}); + const auth = initializeAuth(app, { + persistence: [indexedDBLocalPersistence, browserLocalPersistence], + }); + + // Later + signInWithRedirect(auth, new GoogleAuthProvider(), browserPopupRedirectResolver); + // [END auth_sign_in_redirect_manual_deps] +} \ No newline at end of file diff --git a/auth-next/custom-email-handler.js b/auth-next/custom-email-handler.js new file mode 100644 index 00000000..5fcc1f71 --- /dev/null +++ b/auth-next/custom-email-handler.js @@ -0,0 +1,147 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +// Docs: https://source.corp.google.com/piper///depot/google3/third_party/devsite/firebase/en/docs/auth/custom-email-handler.md + +function handleUserManagementQueryParams() { + // TODO: This helpers should be implemented by the developer + function getParameterByName(name) { + return ""; + } + + // [START auth_handle_mgmt_query_params] + const { initializeApp } = require("firebase/app"); + const { getAuth } = require("firebase/auth"); + + document.addEventListener('DOMContentLoaded', () => { + // TODO: Implement getParameterByName() + + // Get the action to complete. + const mode = getParameterByName('mode'); + // Get the one-time code from the query parameter. + const actionCode = getParameterByName('oobCode'); + // (Optional) Get the continue URL from the query parameter if available. + const continueUrl = getParameterByName('continueUrl'); + // (Optional) Get the language code if available. + const lang = getParameterByName('lang') || 'en'; + + // Configure the Firebase SDK. + // This is the minimum configuration required for the API to be used. + const config = { + 'apiKey': "YOUR_API_KEY" // Copy this key from the web initialization + // snippet found in the Firebase console. + }; + const app = initializeApp(config); + const auth = getAuth(app); + + // Handle the user management action. + switch (mode) { + case 'resetPassword': + // Display reset password handler and UI. + handleResetPassword(auth, actionCode, continueUrl, lang); + break; + case 'recoverEmail': + // Display email recovery handler and UI. + handleRecoverEmail(auth, actionCode, lang); + break; + case 'verifyEmail': + // Display email verification handler and UI. + handleVerifyEmail(auth, actionCode, continueUrl, lang); + break; + default: + // Error: invalid mode. + } + }, false); + // [END auth_handle_mgmt_query_params] +} + +// [START auth_handle_reset_password] +const { verifyPasswordResetCode, confirmPasswordReset } = require("firebase/auth"); + +function handleResetPassword(auth, actionCode, continueUrl, lang) { + // Localize the UI to the selected language as determined by the lang + // parameter. + + // Verify the password reset code is valid. + verifyPasswordResetCode(auth, actionCode).then((email) => { + const accountEmail = email; + + // TODO: Show the reset screen with the user's email and ask the user for + // the new password. + const newPassword = "..."; + + // Save the new password. + confirmPasswordReset(auth, actionCode, newPassword).then((resp) => { + // Password reset has been confirmed and new password updated. + + // TODO: Display a link back to the app, or sign-in the user directly + // if the page belongs to the same domain as the app: + // auth.signInWithEmailAndPassword(accountEmail, newPassword); + + // TODO: If a continue URL is available, display a button which on + // click redirects the user back to the app via continueUrl with + // additional state determined from that URL's parameters. + }).catch((error) => { + // Error occurred during confirmation. The code might have expired or the + // password is too weak. + }); + }).catch((error) => { + // Invalid or expired action code. Ask user to try to reset the password + // again. + }); +} +// [END auth_handle_reset_password] + +// [START auth_handle_recover_email] +const { checkActionCode, applyActionCode, sendPasswordResetEmail } = require("firebase/auth"); + +function handleRecoverEmail(auth, actionCode, lang) { + // Localize the UI to the selected language as determined by the lang + // parameter. + let restoredEmail = null; + // Confirm the action code is valid. + checkActionCode(auth, actionCode).then((info) => { + // Get the restored email address. + restoredEmail = info['data']['email']; + + // Revert to the old email. + return applyActionCode(auth, actionCode); + }).then(() => { + // Account email reverted to restoredEmail + + // TODO: Display a confirmation message to the user. + + // You might also want to give the user the option to reset their password + // in case the account was compromised: + sendPasswordResetEmail(auth, restoredEmail).then(() => { + // Password reset confirmation sent. Ask user to check their email. + }).catch((error) => { + // Error encountered while sending password reset code. + }); + }).catch((error) => { + // Invalid code. + }); +} +// [END auth_handle_recover_email] + +// [START auth_handle_verify_email] +function handleVerifyEmail(auth, actionCode, continueUrl, lang) { + // Localize the UI to the selected language as determined by the lang + // parameter. + // Try to apply the email verification code. + applyActionCode(auth, actionCode).then((resp) => { + // Email address has been verified. + + // TODO: Display a confirmation message to the user. + // You could also provide the user with a link back to the app. + + // TODO: If a continue URL is available, display a button which on + // click redirects the user back to the app via continueUrl with + // additional state determined from that URL's parameters. + }).catch((error) => { + // Code is invalid or expired. Ask the user to verify their email address + // again. + }); +} +// [END auth_handle_verify_email] + diff --git a/auth-next/custom.js b/auth-next/custom.js new file mode 100644 index 00000000..8975672e --- /dev/null +++ b/auth-next/custom.js @@ -0,0 +1,23 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +function signInCustom() { + const token = "token123"; + + // [START auth_sign_in_custom] + const { getAuth, signInWithCustomToken } = require("firebase/auth"); + + const auth = getAuth(); + signInWithCustomToken(auth, token) + .then((userCredential) => { + // Signed in + const user = userCredential.user; + // ... + }) + .catch((error) => { + const errorCode = error.code; + const errorMessage = error.message; + // ... + }); + // [END auth_sign_in_custom] +} diff --git a/auth-next/email-link-auth.js b/auth-next/email-link-auth.js new file mode 100644 index 00000000..fde67663 --- /dev/null +++ b/auth-next/email-link-auth.js @@ -0,0 +1,157 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +// Docs: https://source.corp.google.com/piper///depot/google3/third_party/devsite/firebase/en/docs/auth/web/email-link-auth.md + +function emailLinkActionCodeSettings() { + // [START auth_email_link_actioncode_settings] + const actionCodeSettings = { + // URL you want to redirect back to. The domain (www.example.com) for this + // URL must be in the authorized domains list in the Firebase Console. + url: 'https://www.example.com/finishSignUp?cartId=1234', + // This must be true. + handleCodeInApp: true, + iOS: { + bundleId: 'com.example.ios' + }, + android: { + packageName: 'com.example.android', + installApp: true, + minimumVersion: '12' + }, + // The domain must be configured in Firebase Hosting and owned by the project. + linkDomain: 'custom-domain.com' + }; + // [END auth_email_link_actioncode_settings] +} + +function emailLinkSend(email, actionCodeSettings) { + // [START auth_email_link_send] + const { getAuth, sendSignInLinkToEmail } = require("firebase/auth"); + + const auth = getAuth(); + sendSignInLinkToEmail(auth, email, actionCodeSettings) + .then(() => { + // The link was successfully sent. Inform the user. + // Save the email locally so you don't need to ask the user for it again + // if they open the link on the same device. + window.localStorage.setItem('emailForSignIn', email); + // ... + }) + .catch((error) => { + const errorCode = error.code; + const errorMessage = error.message; + // ... + }); + // [END auth_email_link_send] +} + +function emailLinkComplete() { + // [START email_link_complete] + const { getAuth, isSignInWithEmailLink, signInWithEmailLink } = require("firebase/auth"); + + // Confirm the link is a sign-in with email link. + const auth = getAuth(); + if (isSignInWithEmailLink(auth, window.location.href)) { + // Additional state parameters can also be passed via URL. + // This can be used to continue the user's intended action before triggering + // the sign-in operation. + // Get the email if available. This should be available if the user completes + // the flow on the same device where they started it. + let email = window.localStorage.getItem('emailForSignIn'); + if (!email) { + // User opened the link on a different device. To prevent session fixation + // attacks, ask the user to provide the associated email again. For example: + email = window.prompt('Please provide your email for confirmation'); + } + // The client SDK will parse the code from the link for you. + signInWithEmailLink(auth, email, window.location.href) + .then((result) => { + // Clear email from storage. + window.localStorage.removeItem('emailForSignIn'); + // You can access the new user by importing getAdditionalUserInfo + // and calling it with result: + // getAdditionalUserInfo(result) + // You can access the user's profile via: + // getAdditionalUserInfo(result)?.profile + // You can check if the user is new or existing: + // getAdditionalUserInfo(result)?.isNewUser + }) + .catch((error) => { + // Some error occurred, you can inspect the code: error.code + // Common errors could be invalid email and invalid or expired OTPs. + }); + } + // [END email_link_complete] +} + +function emailLinkLink(email) { + // [START auth_email_link_link] + const { getAuth, linkWithCredential, EmailAuthProvider } = require("firebase/auth"); + + // Construct the email link credential from the current URL. + const credential = EmailAuthProvider.credentialWithLink( + email, window.location.href); + + // Link the credential to the current user. + const auth = getAuth(); + linkWithCredential(auth.currentUser, credential) + .then((usercred) => { + // The provider is now successfully linked. + // The phone user can now sign in with their phone number or email. + }) + .catch((error) => { + // Some error occurred. + }); + // [END auth_email_link_link] +} + +function emailLinkReauth(email) { + // [START auth_email_link_reauth] + const { getAuth, reauthenticateWithCredential, EmailAuthProvider } = require("firebase/auth"); + + // Construct the email link credential from the current URL. + const credential = EmailAuthProvider.credentialWithLink( + email, window.location.href); + + // Re-authenticate the user with this credential. + const auth = getAuth(); + reauthenticateWithCredential(auth.currentUser, credential) + .then((usercred) => { + // The user is now successfully re-authenticated and can execute sensitive + // operations. + }) + .catch((error) => { + // Some error occurred. + }); + // [END auth_email_link_reauth] +} + +function emailLinkDifferentiate() { + // [START email_link_diferentiate] + const { getAuth, fetchSignInMethodsForEmail, EmailAuthProvider} = require("firebase/auth"); + + // After asking the user for their email. + const email = window.prompt('Please provide your email'); + + const auth = getAuth(); + fetchSignInMethodsForEmail(auth, email) + .then((signInMethods) => { + // This returns the same array as fetchProvidersForEmail but for email + // provider identified by 'password' string, signInMethods would contain 2 + // different strings: + // 'emailLink' if the user previously signed in with an email/link + // 'password' if the user has a password. + // A user could have both. + if (signInMethods.indexOf(EmailAuthProvider.EMAIL_PASSWORD_SIGN_IN_METHOD) != -1) { + // User can sign in with email/password. + } + if (signInMethods.indexOf(EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD) != -1) { + // User can sign in with email/link. + } + }) + .catch((error) => { + // Some error occurred, you can inspect the code: error.code + }); + // [END email_link_diferentiate] +} diff --git a/auth-next/email.js b/auth-next/email.js new file mode 100644 index 00000000..26391f81 --- /dev/null +++ b/auth-next/email.js @@ -0,0 +1,78 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +function signInWithEmailPassword() { + const email = "test@example.com"; + const password = "hunter2"; + + // [START auth_signin_password] + const { getAuth, signInWithEmailAndPassword } = require("firebase/auth"); + + const auth = getAuth(); + signInWithEmailAndPassword(auth, email, password) + .then((userCredential) => { + // Signed in + const user = userCredential.user; + // ... + }) + .catch((error) => { + const errorCode = error.code; + const errorMessage = error.message; + }); + // [END auth_signin_password] +} + +function signUpWithEmailPassword() { + const email = "test@example.com"; + const password = "hunter2"; + + // [START auth_signup_password] + const { getAuth, createUserWithEmailAndPassword } = require("firebase/auth"); + + const auth = getAuth(); + createUserWithEmailAndPassword(auth, email, password) + .then((userCredential) => { + // Signed up + const user = userCredential.user; + // ... + }) + .catch((error) => { + const errorCode = error.code; + const errorMessage = error.message; + // .. + }); + // [END auth_signup_password] +} + +function sendEmailVerification() { + // [START auth_send_email_verification] + const { getAuth, sendEmailVerification } = require("firebase/auth"); + + const auth = getAuth(); + sendEmailVerification(auth.currentUser) + .then(() => { + // Email verification sent! + // ... + }); + // [END auth_send_email_verification] +} + +function sendPasswordReset() { + const email = "sam@example.com"; + + // [START auth_send_password_reset] + const { getAuth, sendPasswordResetEmail } = require("firebase/auth"); + + const auth = getAuth(); + sendPasswordResetEmail(auth, email) + .then(() => { + // Password reset email sent! + // .. + }) + .catch((error) => { + const errorCode = error.code; + const errorMessage = error.message; + // .. + }); + // [END auth_send_password_reset] +} diff --git a/auth-next/emulator-suite.js b/auth-next/emulator-suite.js new file mode 100644 index 00000000..13b1ba95 --- /dev/null +++ b/auth-next/emulator-suite.js @@ -0,0 +1,24 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +function emulatorConnect() { + // [START auth_emulator_connect] + const { getAuth, connectAuthEmulator } = require("firebase/auth"); + + const auth = getAuth(); + connectAuthEmulator(auth, "http://127.0.0.1:9099"); + // [END auth_emulator_connect] +} + +function emulatorGoogleCredential() { + // [START auth_emulator_google_credential] + const { getAuth, signInWithCredential, GoogleAuthProvider } = require("firebase/auth"); + + const auth = getAuth(); + signInWithCredential(auth, GoogleAuthProvider.credential( + '{"sub": "abc123", "email": "foo@example.com", "email_verified": true}' + )); + // [END auth_emulator_google_credential] +} + + diff --git a/auth-next/facebook.js b/auth-next/facebook.js new file mode 100644 index 00000000..3c6feb8a --- /dev/null +++ b/auth-next/facebook.js @@ -0,0 +1,177 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +function facebookProvider() { + // [START auth_facebook_provider_create] + const { FacebookAuthProvider } = require("firebase/auth"); + + const provider = new FacebookAuthProvider(); + // [END auth_facebook_provider_create] + + // / [START auth_facebook_provider_scopes] + provider.addScope('user_birthday'); + // [END auth_facebook_provider_scopes] + + // [START auth_facebook_provider_params] + provider.setCustomParameters({ + 'display': 'popup' + }); + // [END auth_facebook_provider_params] +} + +function facebookSignInPopup(provider) { + // [START auth_facebook_signin_popup] + const { getAuth, signInWithPopup, FacebookAuthProvider } = require("firebase/auth"); + + const auth = getAuth(); + signInWithPopup(auth, provider) + .then((result) => { + // The signed-in user info. + const user = result.user; + + // This gives you a Facebook Access Token. You can use it to access the Facebook API. + const credential = FacebookAuthProvider.credentialFromResult(result); + const accessToken = credential.accessToken; + + // IdP data available using getAdditionalUserInfo(result) + // ... + }) + .catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The AuthCredential type that was used. + const credential = FacebookAuthProvider.credentialFromError(error); + + // ... + }); + // [END auth_facebook_signin_popup] +} + +function facebookSignInRedirectResult() { + // [START auth_facebook_signin_redirect_result] + const { getAuth, getRedirectResult, FacebookAuthProvider } = require("firebase/auth"); + + const auth = getAuth(); + getRedirectResult(auth) + .then((result) => { + // This gives you a Facebook Access Token. You can use it to access the Facebook API. + const credential = FacebookAuthProvider.credentialFromResult(result); + const token = credential.accessToken; + + const user = result.user; + // IdP data available using getAdditionalUserInfo(result) + // ... + }).catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // AuthCredential type that was used. + const credential = FacebookAuthProvider.credentialFromError(error); + // ... + }); + // [END auth_facebook_signin_redirect_result] +} + +function checkLoginState_wrapper() { + // See real implementation below + function isUserEqual(x, y) { + return true; + } + + // [START auth_facebook_callback] + const { getAuth, onAuthStateChanged, signInWithCredential, signOut, FacebookAuthProvider } = require("firebase/auth"); + const auth = getAuth(); + + function checkLoginState(response) { + if (response.authResponse) { + // User is signed-in Facebook. + const unsubscribe = onAuthStateChanged(auth, (firebaseUser) => { + unsubscribe(); + // Check if we are already signed-in Firebase with the correct user. + if (!isUserEqual(response.authResponse, firebaseUser)) { + // Build Firebase credential with the Facebook auth token. + const credential = FacebookAuthProvider.credential( + response.authResponse.accessToken); + + // Sign in with the credential from the Facebook user. + signInWithCredential(auth, credential) + .catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The AuthCredential type that was used. + const credential = FacebookAuthProvider.credentialFromError(error); + // ... + }); + } else { + // User is already signed-in Firebase with the correct user. + } + }); + } else { + // User is signed-out of Facebook. + signOut(auth); + } + } + // [END auth_facebook_callback] +} + +function isUserEqual_wrapper() { + // [START auth_facebook_checksameuser] + const { FacebookAuthProvider } = require("firebase/auth"); + + function isUserEqual(facebookAuthResponse, firebaseUser) { + if (firebaseUser) { + const providerData = firebaseUser.providerData; + for (let i = 0; i < providerData.length; i++) { + if (providerData[i].providerId === FacebookAuthProvider.PROVIDER_ID && + providerData[i].uid === facebookAuthResponse.userID) { + // We don't need to re-auth the Firebase connection. + return true; + } + } + } + return false; + } + // [END auth_facebook_checksameuser] +} + + +function authWithCredential(credential) { + // [START auth_facebook_signin_credential] + const { getAuth, signInWithCredential, FacebookAuthProvider } = require("firebase/auth"); + + // Sign in with the credential from the Facebook user. + const auth = getAuth(); + signInWithCredential(auth, credential) + .then((result) => { + // Signed in + const credential = FacebookAuthProvider.credentialFromResult(result); + }) + .catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The AuthCredential type that was used. + const credential = FacebookAuthProvider.credentialFromError(error); + // ... + }); + // [END auth_facebook_signin_credential] +} + +function facebookProviderCredential(accessToken) { + // [START auth_facebook_provider_credential] + const { FacebookAuthProvider } = require("firebase/auth"); + + const credential = FacebookAuthProvider.credential(accessToken); + // [END auth_facebook_provider_credential] +} + diff --git a/auth-next/github.js b/auth-next/github.js new file mode 100644 index 00000000..4c128721 --- /dev/null +++ b/auth-next/github.js @@ -0,0 +1,88 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +function githubProvider() { + // [START auth_github_provider_create] + const { GithubAuthProvider } = require("firebase/auth"); + + const provider = new GithubAuthProvider(); + // [END auth_github_provider_create] + + // [START auth_github_provider_scopes] + provider.addScope('repo'); + // [END auth_github_provider_scopes] + + // [START auth_github_provider_params] + provider.setCustomParameters({ + 'allow_signup': 'false' + }); + // [END auth_github_provider_params] +} + +function githubProviderCredential(token) { + // [START auth_github_provider_credential] + const { GithubAuthProvider } = require("firebase/auth"); + + const credential = GithubAuthProvider.credential(token); + // [END auth_github_provider_credential] +} + +function githubSignInPopup(provider) { + // [START auth_github_signin_popup] + const { getAuth, signInWithPopup, GithubAuthProvider } = require("firebase/auth"); + + const auth = getAuth(); + signInWithPopup(auth, provider) + .then((result) => { + // This gives you a GitHub Access Token. You can use it to access the GitHub API. + const credential = GithubAuthProvider.credentialFromResult(result); + const token = credential.accessToken; + + // The signed-in user info. + const user = result.user; + // IdP data available using getAdditionalUserInfo(result) + // ... + }).catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The AuthCredential type that was used. + const credential = GithubAuthProvider.credentialFromError(error); + // ... + }); + // [END auth_github_signin_popup] +} + +function githubSignInRedirectResult() { + // [START auth_github_signin_redirect_result] + const { getAuth, getRedirectResult, GithubAuthProvider } = require("firebase/auth"); + + const auth = getAuth(); + getRedirectResult(auth) + .then((result) => { + const credential = GithubAuthProvider.credentialFromResult(result); + if (credential) { + // This gives you a GitHub Access Token. You can use it to access the GitHub API. + const token = credential.accessToken; + // ... + } + + // The signed-in user info. + const user = result.user; + // IdP data available using getAdditionalUserInfo(result) + // ... + }).catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The AuthCredential type that was used. + const credential = GithubAuthProvider.credentialFromError(error); + // ... + }); + // [END auth_github_signin_redirect_result] +} + diff --git a/auth-next/google-signin.js b/auth-next/google-signin.js new file mode 100644 index 00000000..025eebd6 --- /dev/null +++ b/auth-next/google-signin.js @@ -0,0 +1,172 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +// Docs: https://source.corp.google.com/piper///depot/google3/third_party/devsite/firebase/en/docs/auth/web/google-signin.md + +function googleProvider() { + // [START auth_google_provider_create] + const { GoogleAuthProvider } = require("firebase/auth"); + + const provider = new GoogleAuthProvider(); + // [END auth_google_provider_create] + + // [START auth_google_provider_scopes] + provider.addScope('https://www.googleapis.com/auth/contacts.readonly'); + // [END auth_google_provider_scopes] + + // [START auth_google_provider_params] + provider.setCustomParameters({ + 'login_hint': 'user@example.com' + }); + // [END auth_google_provider_params] +} + +function googleSignInPopup(provider) { + // [START auth_google_signin_popup] + const { getAuth, signInWithPopup, GoogleAuthProvider } = require("firebase/auth"); + + const auth = getAuth(); + signInWithPopup(auth, provider) + .then((result) => { + // This gives you a Google Access Token. You can use it to access the Google API. + const credential = GoogleAuthProvider.credentialFromResult(result); + const token = credential.accessToken; + // The signed-in user info. + const user = result.user; + // IdP data available using getAdditionalUserInfo(result) + // ... + }).catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The AuthCredential type that was used. + const credential = GoogleAuthProvider.credentialFromError(error); + // ... + }); + // [END auth_google_signin_popup] +} + +function googleSignInRedirectResult() { + // [START auth_google_signin_redirect_result] + const { getAuth, getRedirectResult, GoogleAuthProvider } = require("firebase/auth"); + + const auth = getAuth(); + getRedirectResult(auth) + .then((result) => { + // This gives you a Google Access Token. You can use it to access Google APIs. + const credential = GoogleAuthProvider.credentialFromResult(result); + const token = credential.accessToken; + + // The signed-in user info. + const user = result.user; + // IdP data available using getAdditionalUserInfo(result) + // ... + }).catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The AuthCredential type that was used. + const credential = GoogleAuthProvider.credentialFromError(error); + // ... + }); + // [END auth_google_signin_redirect_result] +} + +function googleBuildAndSignIn(id_token) { + // [START auth_google_build_signin] + const { getAuth, signInWithCredential, GoogleAuthProvider } = require("firebase/auth"); + + // Build Firebase credential with the Google ID token. + const credential = GoogleAuthProvider.credential(id_token); + + // Sign in with credential from the Google user. + const auth = getAuth(); + signInWithCredential(auth, credential).catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The AuthCredential type that was used. + const credential = GoogleAuthProvider.credentialFromError(error); + // ... + }); + // [END auth_google_build_signin] +} + +function onSignIn_wrapper() { + // See real implementation below + function isUserEqual(x, y) { + return true; + } + + // [START auth_google_callback] + const { getAuth, onAuthStateChanged, signInWithCredential, GoogleAuthProvider } = require("firebase/auth"); + const auth = getAuth(); + + function onSignIn(googleUser) { + console.log('Google Auth Response', googleUser); + // We need to register an Observer on Firebase Auth to make sure auth is initialized. + const unsubscribe = onAuthStateChanged(auth, (firebaseUser) => { + unsubscribe(); + // Check if we are already signed-in Firebase with the correct user. + if (!isUserEqual(googleUser, firebaseUser)) { + // Build Firebase credential with the Google ID token. + const credential = GoogleAuthProvider.credential( + googleUser.getAuthResponse().id_token); + + // Sign in with credential from the Google user. + // [START auth_google_signin_credential] + signInWithCredential(auth, credential).catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The credential that was used. + const credential = GoogleAuthProvider.credentialFromError(error); + // ... + }); + // [END auth_google_signin_credential] + } else { + console.log('User already signed-in Firebase.'); + } + }); + } + // [END auth_google_callback] +} + +function isUserEqual_wrapper() { + // [START auth_google_checksameuser] + const { GoogleAuthProvider } = require("firebase/auth"); + + function isUserEqual(googleUser, firebaseUser) { + if (firebaseUser) { + const providerData = firebaseUser.providerData; + for (let i = 0; i < providerData.length; i++) { + if (providerData[i].providerId === GoogleAuthProvider.PROVIDER_ID && + providerData[i].uid === googleUser.getBasicProfile().getId()) { + // We don't need to reauth the Firebase connection. + return true; + } + } + } + return false; + } + // [END auth_google_checksameuser] +} + +function googleProviderCredential(idToken) { + // [START auth_google_provider_credential] + const { GoogleAuthProvider } = require("firebase/auth"); + + const credential = GoogleAuthProvider.credential(idToken); + // [END auth_google_provider_credential] +} + + + diff --git a/auth-next/index.js b/auth-next/index.js new file mode 100644 index 00000000..04ac7d03 --- /dev/null +++ b/auth-next/index.js @@ -0,0 +1,137 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +// ========================================================================================== +// Docs: Snippets in this file are "general purpose" and are used on more than one docs page +// ========================================================================================== + +function makeGoogleCredential(googleUser) { + // [START auth_make_google_credential] + const { GoogleAuthProvider } = require("firebase/auth"); + + const credential = GoogleAuthProvider.credential( + googleUser.getAuthResponse().id_token); + // [END auth_make_google_credential] +} + +function makeFacebookCredential(response) { + // [START auth_make_facebook_credential] + const { FacebookAuthProvider } = require("firebase/auth"); + + const credential = FacebookAuthProvider.credential( + response.authResponse.accessToken); + // [END auth_make_facebook_credential] +} + +function makeEmailCredential(email, password) { + // [START auth_make_email_credential] + const { EmailAuthProvider } = require("firebase/auth"); + + const credential = EmailAuthProvider.credential(email, password); + // [END auth_make_email_credential] +} + +function signOut() { + // [START auth_sign_out] + const { getAuth, signOut } = require("firebase/auth"); + + const auth = getAuth(); + signOut(auth).then(() => { + // Sign-out successful. + }).catch((error) => { + // An error happened. + }); + // [END auth_sign_out] +} + +function authStateListener() { + // [START auth_state_listener] + const { getAuth, onAuthStateChanged } = require("firebase/auth"); + + const auth = getAuth(); + onAuthStateChanged(auth, (user) => { + if (user) { + // User is signed in, see docs for a list of available properties + // https://firebase.google.com/docs/reference/js/v8/firebase.User + const uid = user.uid; + // ... + } else { + // User is signed out + // ... + } + }); + // [END auth_state_listener] +} + +function currentUser() { + // [START auth_current_user] + const { getAuth } = require("firebase/auth"); + + const auth = getAuth(); + const user = auth.currentUser; + + if (user) { + // User is signed in, see docs for a list of available properties + // https://firebase.google.com/docs/reference/js/v8/firebase.User + // ... + } else { + // No user is signed in. + } + // [END auth_current_user] +} + +function setLanguageCode() { + // [START auth_set_language_code] + const { getAuth } = require("firebase/auth"); + + const auth = getAuth(); + auth.languageCode = 'it'; + // To apply the default browser preference instead of explicitly setting it. + // auth.useDeviceLanguage(); + // [END auth_set_language_code] +} + +function authWithCredential(credential) { + // [START auth_signin_credential] + const { getAuth, signInWithCredential } = require("firebase/auth"); + + // Sign in with the credential from the user. + const auth = getAuth(); + signInWithCredential(auth, credential) + .then((result) => { + // Signed in + // ... + }) + .catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // ... + }); + // [END auth_signin_credential] +} + +function signInRedirect(provider) { + // [START auth_signin_redirect] + const { getAuth, signInWithRedirect } = require("firebase/auth"); + + const auth = getAuth(); + signInWithRedirect(auth, provider); + // [END auth_signin_redirect] +} + +function initializeWithCustomDomain() { + // [START auth_init_custom_domain] + const { initializeApp } = require("firebase/app"); + + const firebaseConfig = { + apiKey: "...", + // By default, authDomain is '[YOUR_APP].firebaseapp.com'. + // You may replace it with a custom domain. + authDomain: '[YOUR_CUSTOM_DOMAIN]' + }; + const firebaseApp = initializeApp(firebaseConfig); + // [END auth_init_custom_domain] +} diff --git a/auth-next/link-multiple-accounts.js b/auth-next/link-multiple-accounts.js new file mode 100644 index 00000000..1938aafc --- /dev/null +++ b/auth-next/link-multiple-accounts.js @@ -0,0 +1,242 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +const MyUserDataRepo = function() {}; + +MyUserDataRepo.prototype.merge = function(data1, data2) { + // TODO(you): How you implement this is specific to your application! + return { + ...data1, + ...data2, + }; +}; + +MyUserDataRepo.prototype.set = function(user, data) { + // TODO(you): How you implement this is specific to your application! +}; + +MyUserDataRepo.prototype.delete = function(user) { + // TODO(you): How you implement this is specific to your application! +}; + +MyUserDataRepo.prototype.get = function(user) { + // TODO(you): How you implement this is specific to your application! + return {}; +}; + +function getProviders() { + // [START auth_get_providers] + const { GoogleAuthProvider, FacebookAuthProvider, TwitterAuthProvider, GithubAuthProvider } = require("firebase/auth"); + + const googleProvider = new GoogleAuthProvider(); + const facebookProvider = new FacebookAuthProvider(); + const twitterProvider = new TwitterAuthProvider(); + const githubProvider = new GithubAuthProvider(); + // [END auth_get_providers] +} + +function simpleLink(credential) { + // [START auth_simple_link] + const { getAuth, linkWithCredential } = require("firebase/auth"); + + const auth = getAuth(); + linkWithCredential(auth.currentUser, credential) + .then((usercred) => { + const user = usercred.user; + console.log("Account linking success", user); + }).catch((error) => { + console.log("Account linking error", error); + }); + // [END auth_simple_link] +} + +function anonymousLink(credential) { + // [START auth_anonymous_link] + const { getAuth, linkWithCredential } = require("firebase/auth"); + + const auth = getAuth(); + linkWithCredential(auth.currentUser, credential) + .then((usercred) => { + const user = usercred.user; + console.log("Anonymous account successfully upgraded", user); + }).catch((error) => { + console.log("Error upgrading anonymous account", error); + }); + // [END auth_anonymous_link] +} + +function linkWithPopup() { + // [START auth_link_with_popup] + const { getAuth, linkWithPopup, GoogleAuthProvider } = require("firebase/auth"); + const provider = new GoogleAuthProvider(); + + const auth = getAuth(); + linkWithPopup(auth.currentUser, provider).then((result) => { + // Accounts successfully linked. + const credential = GoogleAuthProvider.credentialFromResult(result); + const user = result.user; + // ... + }).catch((error) => { + // Handle Errors here. + // ... + }); + // [END auth_link_with_popup] +} + +function linkWithRedirect() { + // [START auth_link_with_redirect] + const { getAuth, linkWithRedirect, GoogleAuthProvider } = require("firebase/auth"); + const provider = new GoogleAuthProvider(); + + const auth = getAuth(); + linkWithRedirect(auth.currentUser, provider) + .then(/* ... */) + .catch(/* ... */); + // [END auth_link_with_redirect] + + // [START auth_get_redirect_result] + const { getRedirectResult } = require("firebase/auth"); + getRedirectResult(auth).then((result) => { + const credential = GoogleAuthProvider.credentialFromResult(result); + if (credential) { + // Accounts successfully linked. + const user = result.user; + // ... + } + }).catch((error) => { + // Handle Errors here. + // ... + }); + // [END auth_get_redirect_result] +} + +function mergeAccounts(newCredential) { + // [START auth_merge_accounts] + const { getAuth, signInWithCredential, linkWithCredential, OAuthProvider } = require("firebase/auth"); + + // The implementation of how you store your user data depends on your application + const repo = new MyUserDataRepo(); + + // Get reference to the currently signed-in user + const auth = getAuth(); + const prevUser = auth.currentUser; + + // Get the data which you will want to merge. This should be done now + // while the app is still signed in as this user. + const prevUserData = repo.get(prevUser); + + // Delete the user's data now, we will restore it if the merge fails + repo.delete(prevUser); + + // Sign in user with the account you want to link to + signInWithCredential(auth, newCredential).then((result) => { + console.log("Sign In Success", result); + const currentUser = result.user; + const currentUserData = repo.get(currentUser); + + // Merge prevUser and currentUser data stored in Firebase. + // Note: How you handle this is specific to your application + const mergedData = repo.merge(prevUserData, currentUserData); + + const credential = OAuthProvider.credentialFromResult(result); + return linkWithCredential(prevUser, credential) + .then((linkResult) => { + // Sign in with the newly linked credential + const linkCredential = OAuthProvider.credentialFromResult(linkResult); + return signInWithCredential(auth, linkCredential); + }) + .then((signInResult) => { + // Save the merged data to the new user + repo.set(signInResult.user, mergedData); + }); + }).catch((error) => { + // If there are errors we want to undo the data merge/deletion + console.log("Sign In Error", error); + repo.set(prevUser, prevUserData); + }); + // [END auth_merge_accounts] +} + +function makeEmailCredential() { + const email = "test@test.com"; + const password = "abcde12345"; + + // [START auth_make_email_credential] + const { EmailAuthProvider } = require("firebase/auth"); + + const credential = EmailAuthProvider.credential(email, password); + // [END auth_make_email_credential] +} + +function unlink(providerId) { + // [START auth_unlink_provider] + const { getAuth, unlink } = require("firebase/auth"); + + const auth = getAuth(); + unlink(auth.currentUser, providerId).then(() => { + // Auth provider unlinked from account + // ... + }).catch((error) => { + // An error happened + // ... + }); + // [END auth_unlink_provider] +} + +function accountExistsPopup(auth, facebookProvider, goToApp, promptUserForPassword, promptUserForSignInMethod, getProviderForProviderId) { + // [START account_exists_popup] + const { signInWithPopup, signInWithEmailAndPassword, linkWithCredential } = require("firebase/auth"); + + // User tries to sign in with Facebook. + signInWithPopup(auth, facebookProvider).catch((error) => { + // User's email already exists. + if (error.code === 'auth/account-exists-with-different-credential') { + // The pending Facebook credential. + const pendingCred = error.credential; + // The provider account's email address. + const email = error.customData.email; + + // Present the user with a list of providers they might have + // used to create the original account. + // Then, ask the user to sign in with the existing provider. + const method = promptUserForSignInMethod(); + + if (method === 'password') { + // TODO: Ask the user for their password. + // In real scenario, you should handle this asynchronously. + const password = promptUserForPassword(); + signInWithEmailAndPassword(auth, email, password).then((result) => { + return linkWithCredential(result.user, pendingCred); + }).then(() => { + // Facebook account successfully linked to the existing user. + goToApp(); + }); + return; + } + + // All other cases are external providers. + // Construct provider object for that provider. + // TODO: Implement getProviderForProviderId. + const provider = getProviderForProviderId(method); + // At this point, you should let the user know that they already have an + // account with a different provider, and validate they want to sign in + // with the new provider. + // Note: Browsers usually block popups triggered asynchronously, so in + // real app, you should ask the user to click on a "Continue" button + // that will trigger signInWithPopup(). + signInWithPopup(auth, provider).then((result) => { + // Note: Identity Platform doesn't control the provider's sign-in + // flow, so it's possible for the user to sign in with an account + // with a different email from the first one. + + // Link the Facebook credential. We have access to the pending + // credential, so we can directly call the link method. + linkWithCredential(result.user, pendingCred).then((userCred) => { + // Success. + goToApp(); + }); + }); + } +}); +// [END account_exists_popup] +} diff --git a/auth-next/manage.js b/auth-next/manage.js new file mode 100644 index 00000000..bf603ccc --- /dev/null +++ b/auth-next/manage.js @@ -0,0 +1,168 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +function getUserProfile() { + // [START auth_get_user_profile] + const { getAuth } = require("firebase/auth"); + + const auth = getAuth(); + const user = auth.currentUser; + if (user !== null) { + // The user object has basic properties such as display name, email, etc. + const displayName = user.displayName; + const email = user.email; + const photoURL = user.photoURL; + const emailVerified = user.emailVerified; + + // The user's ID, unique to the Firebase project. Do NOT use + // this value to authenticate with your backend server, if + // you have one. Use User.getToken() instead. + const uid = user.uid; + } + // [END auth_get_user_profile] +} + +function getUserProfileProvider() { + // [START auth_get_user_profile_provider] + const { getAuth } = require("firebase/auth"); + + const auth = getAuth(); + const user = auth.currentUser; + + if (user !== null) { + user.providerData.forEach((profile) => { + console.log("Sign-in provider: " + profile.providerId); + console.log(" Provider-specific UID: " + profile.uid); + console.log(" Name: " + profile.displayName); + console.log(" Email: " + profile.email); + console.log(" Photo URL: " + profile.photoURL); + }); + } + // [END auth_get_user_profile_provider] +} + +function updateUserProfile() { + // [START auth_update_user_profile] + const { getAuth, updateProfile } = require("firebase/auth"); + const auth = getAuth(); + updateProfile(auth.currentUser, { + displayName: "Jane Q. User", photoURL: "https://example.com/jane-q-user/profile.jpg" + }).then(() => { + // Profile updated! + // ... + }).catch((error) => { + // An error occurred + // ... + }); + // [END auth_update_user_profile] +} + +function updateUserEmail() { + // [START auth_update_user_email] + const { getAuth, updateEmail } = require("firebase/auth"); + const auth = getAuth(); + updateEmail(auth.currentUser, "user@example.com").then(() => { + // Email updated! + // ... + }).catch((error) => { + // An error occurred + // ... + }); + // [END auth_update_user_email] +} + +function sendEmailVerification() { + // [START send_email_verification] + const { getAuth, sendEmailVerification } = require("firebase/auth"); + + const auth = getAuth(); + const user = auth.currentUser; + + sendEmailVerification(user).then(() => { + // Email sent. + }).catch((error) => { + // An error ocurred + // ... + }); + // [END send_email_verification] +} + +function updatePassword() { + function getASecureRandomPassword() { + return "correcthorsebatterystaple"; + } + + // [START auth_update_password] + const { getAuth, updatePassword } = require("firebase/auth"); + + const auth = getAuth(); + + const user = auth.currentUser; + const newPassword = getASecureRandomPassword(); + + updatePassword(user, newPassword).then(() => { + // Update successful. + }).catch((error) => { + // An error ocurred + // ... + }); + // [END auth_update_password] +} + +function sendPasswordReset() { + // [START auth_send_password_reset] + const { getAuth, sendPasswordResetEmail } = require("firebase/auth"); + + const auth = getAuth(); + const emailAddress = "user@example.com"; + + sendPasswordResetEmail(auth, emailAddress).then(() => { + // Email sent. + }).catch((error) => { + // An error ocurred + // ... + }); + // [END auth_send_password_reset] +} + +function deleteUser() { + // [START auth_delete_user] + const { getAuth, deleteUser } = require("firebase/auth"); + + const auth = getAuth(); + const user = auth.currentUser; + + deleteUser(user).then(() => { + // User deleted. + }).catch((error) => { + // An error ocurred + // ... + }); + // [END auth_delete_user] +} + +function reauthenticateWithCredential() { + /** + * @returns {object} + */ + function promptForCredentials() { + return {}; + } + + // [START auth_reauth_with_credential] + const { getAuth, reauthenticateWithCredential } = require("firebase/auth"); + + const auth = getAuth(); + const user = auth.currentUser; + + // TODO(you): prompt the user to re-provide their sign-in credentials + const credential = promptForCredentials(); + + reauthenticateWithCredential(user, credential).then(() => { + // User re-authenticated. + }).catch((error) => { + // An error ocurred + // ... + }); + // [END auth_reauth_with_credential] +} diff --git a/auth-next/microsoft-oauth.js b/auth-next/microsoft-oauth.js new file mode 100644 index 00000000..be0abffd --- /dev/null +++ b/auth-next/microsoft-oauth.js @@ -0,0 +1,134 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +// Docs: https://source.corp.google.com/piper///depot/google3/third_party/devsite/firebase/en/docs/auth/web/microsoft-oauth.md + +function msftCreateProvider() { + // [START auth_msft_create_provider] + const { OAuthProvider } = require("firebase/auth"); + + const provider = new OAuthProvider('microsoft.com'); + // [END auth_msft_create_provider] + + // [START auth_msft_provider_scopes] + provider.addScope('mail.read'); + provider.addScope('calendars.read'); + // [END auth_msft_provider_scopes] + + // [START auth_msft_provider_params] + provider.setCustomParameters({ + // Force re-consent. + prompt: 'consent', + // Target specific email with login hint. + login_hint: 'user@firstadd.onmicrosoft.com' + }); + // [END auth_msft_provider_params] + + // [START auth_msft_provider_params_tenant] + provider.setCustomParameters({ + // Optional "tenant" parameter in case you are using an Azure AD tenant. + // eg. '8eaef023-2b34-4da1-9baa-8bc8c9d6a490' or 'contoso.onmicrosoft.com' + // or "common" for tenant-independent tokens. + // The default value is "common". + tenant: 'TENANT_ID' + }); + // [END auth_msft_provider_params_tenant] +} + +function msftSignInPopup(provider) { + // [START auth_msft_signin_popup] + const { getAuth, signInWithPopup, OAuthProvider } = require("firebase/auth"); + + const auth = getAuth(); + signInWithPopup(auth, provider) + .then((result) => { + // User is signed in. + // IdP data available in result.additionalUserInfo.profile. + + // Get the OAuth access token and ID Token + const credential = OAuthProvider.credentialFromResult(result); + const accessToken = credential.accessToken; + const idToken = credential.idToken; + }) + .catch((error) => { + // Handle error. + }); + // [END auth_msft_signin_popup] +} + +function msftSignInRedirect(provider) { + // [START auth_msft_signin_redirect] + const { getAuth, signInWithRedirect } = require("firebase/auth"); + + const auth = getAuth(); + signInWithRedirect(auth, provider); + // [END auth_msft_signin_redirect] +} + +function msftSignInRedirectResult() { + // [START auth_msft_signin_redirect_result] + const { getAuth, getRedirectResult, OAuthProvider } = require("firebase/auth"); + + const auth = getAuth(); + getRedirectResult(auth) + .then((result) => { + // User is signed in. + // IdP data available in result.additionalUserInfo.profile. + + // Get the OAuth access token and ID Token + const credential = OAuthProvider.credentialFromResult(result); + const accessToken = credential.accessToken; + const idToken = credential.idToken; + }) + .catch((error) => { + // Handle error. + }); + // [END auth_msft_signin_redirect_result] +} + +function msftLinkWithPopup() { + // [START auth_msft_link_popup] + const { getAuth, linkWithPopup, OAuthProvider } = require("firebase/auth"); + + const provider = new OAuthProvider('microsoft.com'); + const auth = getAuth(); + + linkWithPopup(auth.currentUser, provider) + .then((result) => { + // Microsoft credential is linked to the current user. + // IdP data available in result.additionalUserInfo.profile. + + // Get the OAuth access token and ID Token + const credential = OAuthProvider.credentialFromResult(result); + const accessToken = credential.accessToken; + const idToken = credential.idToken; + }) + .catch((error) => { + // Handle error. + }); + // [END auth_msft_link_popup] +} + +function msftReauthPopup() { + // [START auth_msft_reauth_popup] + const { getAuth, reauthenticateWithPopup, OAuthProvider } = require("firebase/auth"); + + const provider = new OAuthProvider('microsoft.com'); + const auth = getAuth(); + reauthenticateWithPopup(auth.currentUser, provider) + .then((result) => { + // User is re-authenticated with fresh tokens minted and + // should be able to perform sensitive operations like account + // deletion and email or password update. + // IdP data available in result.additionalUserInfo.profile. + + // Get the OAuth access token and ID Token + const credential = OAuthProvider.credentialFromResult(result); + const accessToken = credential.accessToken; + const idToken = credential.idToken; + }) + .catch((error) => { + // Handle error. + }); + // [END auth_msft_reauth_popup] +} diff --git a/auth-next/multi-tenancy.js b/auth-next/multi-tenancy.js new file mode 100644 index 00000000..d7577f34 --- /dev/null +++ b/auth-next/multi-tenancy.js @@ -0,0 +1,347 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +function setTenant() { + // [START multitenant_set_tenant] + const { getAuth } = require("firebase/auth"); + const auth = getAuth(); + const tenantId = "TENANT_ID1"; + auth.tenantId = tenantId; + // [END multitenant_set_tenant] +} + +function switchTenantSingleAuth(auth) { + // [START multitenant_switch_tenant] + // One Auth instance + // Switch to tenant1 + auth.tenantId = "TENANT_ID1"; + // Switch to tenant2 + auth.tenantId = "TENANT_ID2"; + // Switch back to project level IdPs + auth.tenantId = null; + // [END multitenant_switch_tenant] +} + +function switchTenantMultiAuth(firebaseConfig1, firebaseConfig2) { + // [START multitenant_switch_tenant_multiinstance] + // Multiple Auth instances + const { initializeApp } = require("firebase/app"); + const { getAuth } = require("firebase/auth"); + const firebaseApp1 = initializeApp(firebaseConfig1, 'app1_for_tenantId1'); + const firebaseApp2 = initializeApp(firebaseConfig2, 'app2_for_tenantId2'); + + const auth1 = getAuth(firebaseApp1); + const auth2 = getAuth(firebaseApp2); + + auth1.tenantId = "TENANT_ID1"; + auth2.tenantId = "TENANT_ID2"; + // [END multitenant_switch_tenant_multiinstance] +} + +function passwordSignInWithTenantDemo(auth, email, password) { + // [START multitenant_signin_password_demo] + const { signInWithEmailAndPassword, onAuthStateChanged } = require("firebase/auth"); + // Switch to TENANT_ID1 + auth.tenantId = 'TENANT_ID1'; + + // Sign in with tenant + signInWithEmailAndPassword(auth, email, password) + .then((userCredential) => { + // User is signed in. + const user = userCredential.user; + // user.tenantId is set to 'TENANT_ID1'. + // Switch to 'TENANT_ID2'. + auth.tenantId = 'TENANT_ID2'; + // auth.currentUser still points to the user. + // auth.currentUser.tenantId is 'TENANT_ID1'. + }); + + // You could also get the current user from Auth state observer. + onAuthStateChanged(auth, (user) => { + if (user) { + // User is signed in. + // user.tenantId is set to 'TENANT_ID1'. + } else { + // No user is signed in. + } + }); + // [END multitenant_signin_password_demo] +} + +function signUpWithTenant(auth, email, password) { + // [START multitenant_signup_password] + const { createUserWithEmailAndPassword } = require("firebase/auth"); + auth.tenantId = 'TENANT_ID'; + + createUserWithEmailAndPassword(auth, email, password) + .then((userCredential) => { + // User is signed in. + // userCredential.user.tenantId is 'TENANT_ID'. + }).catch((error) => { + // Handle / display error. + // ... + }); + // [END multitenant_signup_password] +} + + +function passwordSignInWithTenant(auth, email, password) { + // [START multitenant_signin_password] + const { signInWithEmailAndPassword } = require("firebase/auth"); + auth.tenantId = 'TENANT_ID'; + + signInWithEmailAndPassword(auth, email, password) + .then((userCredential) => { + // User is signed in. + // userCredential.user.tenantId is 'TENANT_ID'. + }).catch((error) => { + // Handle / display error. + // ... + }); + // [END multitenant_signin_password] +} + +function samlSignInPopupTenant(auth, provider) { + // [START multitenant_signin_saml_popup] + const { signInWithPopup } = require("firebase/auth"); + // Switch to TENANT_ID1. + auth.tenantId = 'TENANT_ID1'; + + // Sign-in with popup. + signInWithPopup(auth, provider) + .then((userCredential) => { + // User is signed in. + const user = userCredential.user; + // user.tenantId is set to 'TENANT_ID1'. + // Provider data available from the result.user.getIdToken() + // or from result.user.providerData + }) + .catch((error) => { + // Handle / display error. + // ... + }); + // [END multitenant_signin_saml_popup] +} + +function samlSignInRedirectTenant(auth, provider) { + // [START multitenant_signin_saml_redirect] + const { signInWithRedirect, getRedirectResult } = require("firebase/auth"); + // Switch to TENANT_ID1. + auth.tenantId = 'TENANT_ID1'; + + // Sign-in with redirect. + signInWithRedirect(auth, provider); + + // After the user completes sign-in and returns to the app, you can get + // the sign-in result by calling getRedirectResult. However, if they sign out + // and sign in again with an IdP, no tenant is used. + getRedirectResult(auth) + .then((result) => { + // User is signed in. + // The tenant ID available in result.user.tenantId. + // Provider data available from the result.user.getIdToken() + // or from result.user.providerData + }) + .catch((error) => { + // Handle / display error. + // ... + }); + // [END multitenant_signin_saml_redirect] +} + +function sendSignInLinkToEmailTenant(auth, email, actionCodeSettings) { + // [START multitenant_send_emaillink] + const { sendSignInLinkToEmail } = require("firebase/auth"); + // Switch to TENANT_ID1 + auth.tenantId = 'TENANT_ID1'; + + sendSignInLinkToEmail(auth, email, actionCodeSettings) + .then(() => { + // The link was successfully sent. Inform the user. + // Save the email locally so you don't need to ask the user for it again + // if they open the link on the same device. + window.localStorage.setItem('emailForSignIn', email); + }) + .catch((error) => { + // Handle / display error. + // ... + }); + // [END multitenant_send_emaillink] +} + +function signInWithEmailLinkTenant(auth) { + // [START multitenant_signin_emaillink] + const { isSignInWithEmailLink, parseActionCodeURL, signInWithEmailLink } = require("firebase/auth"); + if (isSignInWithEmailLink(auth, window.location.href)) { + const actionCodeUrl = parseActionCodeURL(window.location.href); + if (actionCodeUrl.tenantId) { + auth.tenantId = actionCodeUrl.tenantId; + } + let email = window.localStorage.getItem('emailForSignIn'); + if (!email) { + // User opened the link on a different device. To prevent session fixation + // attacks, ask the user to provide the associated email again. For example: + email = window.prompt('Please provide your email for confirmation'); + } + // The client SDK will parse the code from the link for you. + signInWithEmailLink(auth, email, window.location.href) + .then((result) => { + // User is signed in. + // tenant ID available in result.user.tenantId. + // Clear email from storage. + window.localStorage.removeItem('emailForSignIn'); + }); + } + // [END multitenant_signin_emaillink] +} + +// Same as the code in auth/ since this is the admin SDK. +function createCustomTokenTenant(admin, uid) { + // [START multitenant_create_custom_token] + // Ensure you're using a tenant-aware auth instance + const tenantManager = admin.auth().tenantManager(); + const tenantAuth = tenantManager.authForTenant('TENANT_ID1'); + + // Create a custom token in the usual manner + tenantAuth.createCustomToken(uid) + .then((customToken) => { + // Send token back to client + }) + .catch((error) => { + console.log('Error creating custom token:', error); + }); + // [END multitenant_create_custom_token] +} + +function signInWithCustomTokenTenant(auth, token) { + // [START multitenant_signin_custom_token] + const { signInWithCustomToken } = require("firebase/auth"); + auth.tenantId = 'TENANT_ID1'; + + signInWithCustomToken(auth, token) + .catch((error) => { + // Handle / display error. + // ... + }); + // [END multitenant_signin_custom_token] +} + +function linkAccountTenant(auth, provider, email, password) { + // [START multitenant_account_linking] + const { signInWithPopup, EmailAuthProvider, linkWithCredential, SAMLAuthProvider, signInWithCredential } = require("firebase/auth"); + // Switch to TENANT_ID1 + auth.tenantId = 'TENANT_ID1'; + + // Sign-in with popup + signInWithPopup(auth, provider) + .then((userCredential) => { + // Existing user with e.g. SAML provider. + const prevUser = userCredential.user; + const emailCredential = + EmailAuthProvider.credential(email, password); + return linkWithCredential(prevUser, emailCredential) + .then((linkResult) => { + // Sign in with the newly linked credential + const linkCredential = SAMLAuthProvider.credentialFromResult(linkResult); + return signInWithCredential(auth, linkCredential); + }) + .then((signInResult) => { + // Handle sign in of merged user + // ... + }); + }) + .catch((error) => { + // Handle / display error. + // ... + }); + // [END multitenant_account_linking] +} + +function accountExistsPopupTenant(auth, samlProvider, googleProvider, goToApp) { + // [START multitenant_account_exists_popup] + const { signInWithPopup, fetchSignInMethodsForEmail, linkWithCredential } = require("firebase/auth"); + // Step 1. + // User tries to sign in to the SAML provider in that tenant. + auth.tenantId = 'TENANT_ID'; + signInWithPopup(auth, samlProvider) + .catch((error) => { + // An error happened. + if (error.code === 'auth/account-exists-with-different-credential') { + // Step 2. + // User's email already exists. + // The pending SAML credential. + const pendingCred = error.credential; + // The credential's tenantId if needed: error.tenantId + // The provider account's email address. + const email = error.customData.email; + // Get sign-in methods for this email. + fetchSignInMethodsForEmail(email, auth) + .then((methods) => { + // Step 3. + // Ask the user to sign in with existing Google account. + if (methods[0] == 'google.com') { + signInWithPopup(auth, googleProvider) + .then((result) => { + // Step 4 + // Link the SAML AuthCredential to the existing user. + linkWithCredential(result.user, pendingCred) + .then((linkResult) => { + // SAML account successfully linked to the existing + // user. + goToApp(); + }); + }); + } + }); + } + }); + // [END multitenant_account_exists_popup] +} + +function accountExistsRedirectTenant(auth, samlProvider, googleProvider, goToApp) { + // [START multitenant_account_exists_redirect] + const { signInWithRedirect, getRedirectResult, fetchSignInMethodsForEmail, linkWithCredential } = require("firebase/auth"); + // Step 1. + // User tries to sign in to SAML provider. + auth.tenantId = 'TENANT_ID'; + signInWithRedirect(auth, samlProvider); + var pendingCred; + // Redirect back from SAML IDP. auth.tenantId is null after redirecting. + getRedirectResult(auth).catch((error) => { + if (error.code === 'auth/account-exists-with-different-credential') { + // Step 2. + // User's email already exists. + const tenantId = error.tenantId; + // The pending SAML credential. + pendingCred = error.credential; + // The provider account's email address. + const email = error.customData.email; + // Need to set the tenant ID again as the page was reloaded and the + // previous setting was reset. + auth.tenantId = tenantId; + // Get sign-in methods for this email. + fetchSignInMethodsForEmail(auth, email) + .then((methods) => { + // Step 3. + // Ask the user to sign in with existing Google account. + if (methods[0] == 'google.com') { + signInWithRedirect(auth, googleProvider); + } + }); + } + }); + + // Redirect back from Google. auth.tenantId is null after redirecting. + getRedirectResult(auth).then((result) => { + // Step 4 + // Link the SAML AuthCredential to the existing user. + // result.user.tenantId is 'TENANT_ID'. + linkWithCredential(result.user, pendingCred) + .then((linkResult) => { + // SAML account successfully linked to the existing + // user. + goToApp(); + }); + }); + // [END multitenant_account_exists_redirect] +} \ No newline at end of file diff --git a/auth-next/oidc.js b/auth-next/oidc.js new file mode 100644 index 00000000..dae69fe9 --- /dev/null +++ b/auth-next/oidc.js @@ -0,0 +1,96 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +function oidcProvider() { + // [START auth_oidc_provider_create] + const { OAuthProvider } = require("firebase/auth"); + + const provider = new OAuthProvider("oidc.myProvider"); + // [END auth_oidc_provider_create] +} + +function oidcSignInPopup(provider) { + // [START auth_oidc_signin_popup] + const { getAuth, signInWithPopup, OAuthProvider } = require("firebase/auth"); + + const auth = getAuth(); + signInWithPopup(auth, provider) + .then((result) => { + // User is signed in. + const credential = OAuthProvider.credentialFromResult(result); + // This gives you an access token for the OIDC provider. You can use it to directly interact with that provider + }).catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The AuthCredential type that was used. + const credential = OAuthProvider.credentialFromError(error); + // Handle / display error. + // ... + }); + // [END auth_oidc_signin_popup] +} + +function oidcSignInRedirect(provider) { + // [START auth_oidc_signin_redirect] + const { getAuth, signInWithRedirect } = require("firebase/auth"); + + const auth = getAuth(); + signInWithRedirect(auth, provider); + // [END auth_oidc_signin_redirect] +} + +function oidcSignInRedirectResult(provider) { + // [START auth_oidc_signin_redirect_result] + const { getAuth, getRedirectResult, OAuthProvider } = require("firebase/auth"); + + const auth = getAuth(); + getRedirectResult(auth) + .then((result) => { + // User is signed in. + const credential = OAuthProvider.credentialFromResult(result); + // This gives you an access token for the OIDC provider. You can use it to directly interact with that provider + }) + .catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The AuthCredential type that was used. + const credential = OAuthProvider.credentialFromError(error); + // Handle / display error. + // ... + }); + // [END auth_oidc_signin_redirect_result] +} + +function oidcDirectSignIn(provider, oidcIdToken) { + // [START auth_oidc_direct_sign_in] + const { getAuth, OAuthProvider, signInWithCredential } = require("firebase/auth"); + + const auth = getAuth(); + const credential = provider.credential({ + idToken: oidcIdToken, + }); + signInWithCredential(auth, credential) + .then((result) => { + // User is signed in. + const newCredential = OAuthProvider.credentialFromResult(result); + // This gives you a new access token for the OIDC provider. You can use it to directly interact with that provider. + }) + .catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The AuthCredential type that was used. + const credential = OAuthProvider.credentialFromError(error); + // Handle / display error. + // ... + }); + // [END auth_oidc_direct_sign_in] +} \ No newline at end of file diff --git a/auth-next/package.json b/auth-next/package.json new file mode 100644 index 00000000..8d43bad1 --- /dev/null +++ b/auth-next/package.json @@ -0,0 +1,11 @@ +{ + "name": "auth-next", + "version": "1.0.0", + "scripts": { + "compile": "cp ../tsconfig.json.template ./tsconfig.json && tsc" + }, + "license": "Apache-2.0", + "dependencies": { + "firebase": "^10.0.0" + } +} diff --git a/auth-next/phone-auth.js b/auth-next/phone-auth.js new file mode 100644 index 00000000..99492f99 --- /dev/null +++ b/auth-next/phone-auth.js @@ -0,0 +1,124 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +// Mask the global 'window' for this snippet file +const window = { + recaptchaVerifier: undefined +}; + +function recaptchaVerifierInvisible() { + function onSignInSubmit() { + // TODO(you): Implement + } + + // [START auth_phone_recaptcha_verifier_invisible] + const { getAuth, RecaptchaVerifier } = require("firebase/auth"); + + const auth = getAuth(); + window.recaptchaVerifier = new RecaptchaVerifier(auth, 'sign-in-button', { + 'size': 'invisible', + 'callback': (response) => { + // reCAPTCHA solved, allow signInWithPhoneNumber. + onSignInSubmit(); + } + }); + // [END auth_phone_recaptcha_verifier_invisible] +} + +function recaptchaVerifierVisible() { + // [START auth_phone_recaptcha_verifier_visible] + const { getAuth, RecaptchaVerifier } = require("firebase/auth"); + + const auth = getAuth(); + window.recaptchaVerifier = new RecaptchaVerifier(auth, 'recaptcha-container', { + 'size': 'normal', + 'callback': (response) => { + // reCAPTCHA solved, allow signInWithPhoneNumber. + // ... + }, + 'expired-callback': () => { + // Response expired. Ask user to solve reCAPTCHA again. + // ... + } + }); + // [END auth_phone_recaptcha_verifier_visible] +} + +function recaptchaVerifierSimple() { + // [START auth_phone_recaptcha_verifier_simple] + const { getAuth, RecaptchaVerifier } = require("firebase/auth"); + + const auth = getAuth(); + window.recaptchaVerifier = new RecaptchaVerifier(auth, 'recaptcha-container', {}); + // [END auth_phone_recaptcha_verifier_simple] +} + +function recaptchaRender() { + const { RecaptchaVerifier } = require("firebase/auth"); + + /** @type {RecaptchaVerifier} */ + const recaptchaVerifier = window.recaptchaVerifier; + + // [START auth_phone_recaptcha_render] + recaptchaVerifier.render().then((widgetId) => { + window.recaptchaWidgetId = widgetId; + }); + // [END auth_phone_recaptcha_render] +} + +function phoneSignIn() { + function getPhoneNumberFromUserInput() { + return "+15558675309"; + } + + // [START auth_phone_signin] + const { getAuth, signInWithPhoneNumber } = require("firebase/auth"); + + const phoneNumber = getPhoneNumberFromUserInput(); + const appVerifier = window.recaptchaVerifier; + + const auth = getAuth(); + signInWithPhoneNumber(auth, phoneNumber, appVerifier) + .then((confirmationResult) => { + // SMS sent. Prompt user to type the code from the message, then sign the + // user in with confirmationResult.confirm(code). + window.confirmationResult = confirmationResult; + // ... + }).catch((error) => { + // Error; SMS not sent + // ... + }); + // [END auth_phone_signin] +} + +function verifyCode() { + function getCodeFromUserInput() { + return "1234"; + } + + // TODO(samstern): Import ConfirmationResult type + /** @type {*} */ + const confirmationResult = undefined; + + // [START auth_phone_verify_code] + const code = getCodeFromUserInput(); + confirmationResult.confirm(code).then((result) => { + // User signed in successfully. + const user = result.user; + // ... + }).catch((error) => { + // User couldn't sign in (bad verification code?) + // ... + }); + // [END auth_phone_verify_code] +} + +function getRecaptchaResponse() { + const recaptchaWidgetId = "..."; + const grecaptcha = {}; + + // [START auth_get_recaptcha_response] + const recaptchaResponse = grecaptcha.getResponse(recaptchaWidgetId); + // [END auth_get_recaptcha_response] +} + diff --git a/auth-next/saml.js b/auth-next/saml.js new file mode 100644 index 00000000..cdecce56 --- /dev/null +++ b/auth-next/saml.js @@ -0,0 +1,68 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +function samlProvider() { + // [START auth_saml_provider_create] + const { SAMLAuthProvider } = require("firebase/auth"); + + const provider = new SAMLAuthProvider("saml.myProvider"); + // [END auth_saml_provider_create] +} + +function samlSignInPopup(provider) { + // [START auth_saml_signin_popup] + const { getAuth, signInWithPopup, SAMLAuthProvider } = require("firebase/auth"); + + const auth = getAuth(); + signInWithPopup(auth, provider) + .then((result) => { + // User is signed in. + // Provider data available from the result.user.getIdToken() + // or from result.user.providerData + }).catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The AuthCredential type that was used. + const credential = SAMLAuthProvider.credentialFromError(error); + // Handle / display error. + // ... + }); + // [END auth_saml_signin_popup] +} + +function samlSignInRedirect(provider) { + // [START auth_saml_signin_redirect] + const { getAuth, signInWithRedirect } = require("firebase/auth"); + + const auth = getAuth(); + signInWithRedirect(auth, provider); + // [END auth_saml_signin_redirect] +} + +function samlSignInRedirectResult(provider) { + // [START auth_saml_signin_redirect_result] + const { getAuth, getRedirectResult, SAMLAuthProvider } = require("firebase/auth"); + + const auth = getAuth(); + getRedirectResult(auth) + .then((result) => { + // User is signed in. + // Provider data available from the result.user.getIdToken() + // or from result.user.providerData + }) + .catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The AuthCredential type that was used. + const credential = SAMLAuthProvider.credentialFromError(error); + // Handle / display error. + // ... + }); + // [END auth_saml_signin_redirect_result] +} \ No newline at end of file diff --git a/auth-next/service-worker-sessions.js b/auth-next/service-worker-sessions.js new file mode 100644 index 00000000..abef386b --- /dev/null +++ b/auth-next/service-worker-sessions.js @@ -0,0 +1,171 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +// Docs: https://source.corp.google.com/piper///depot/google3/third_party/devsite/firebase/en/docs/auth/web/service-worker-sessions.md + +function svcGetIdToken() { + // [START auth_svc_get_idtoken] + const { getAuth, getIdToken } = require("firebase/auth"); + + const auth = getAuth(); + getIdToken(auth.currentUser) + .then((idToken) => { + // idToken can be passed back to server. + }) + .catch((error) => { + // Error occurred. + }); + // [END auth_svc_get_idtoken] +} + +function svcSubscribe(config) { + // [START auth_svc_subscribe] + const { initializeApp } = require("firebase/app"); + const { getAuth, onAuthStateChanged, getIdToken } = require("firebase/auth"); + + // Initialize the Firebase app in the service worker script. + initializeApp(config); + + /** + * Returns a promise that resolves with an ID token if available. + * @return {!Promise} The promise that resolves with an ID token if + * available. Otherwise, the promise resolves with null. + */ + const auth = getAuth(); + const getIdTokenPromise = () => { + return new Promise((resolve, reject) => { + const unsubscribe = onAuthStateChanged(auth, (user) => { + unsubscribe(); + if (user) { + getIdToken(user).then((idToken) => { + resolve(idToken); + }, (error) => { + resolve(null); + }); + } else { + resolve(null); + } + }); + }); + }; + // [END auth_svc_subscribe] +} + +function svcIntercept() { + // See above + function getIdTokenPromise() { + return Promise.resolve("id-token"); + } + + // [START auth_svc_intercept] + const getOriginFromUrl = (url) => { + // https://stackoverflow.com/questions/1420881/how-to-extract-base-url-from-a-string-in-javascript + const pathArray = url.split('/'); + const protocol = pathArray[0]; + const host = pathArray[2]; + return protocol + '//' + host; + }; + + // Get underlying body if available. Works for text and json bodies. + const getBodyContent = (req) => { + return Promise.resolve().then(() => { + if (req.method !== 'GET') { + if (req.headers.get('Content-Type').indexOf('json') !== -1) { + return req.json() + .then((json) => { + return JSON.stringify(json); + }); + } else { + return req.text(); + } + } + }).catch((error) => { + // Ignore error. + }); + }; + + self.addEventListener('fetch', (event) => { + /** @type {FetchEvent} */ + const evt = event; + + const requestProcessor = (idToken) => { + let req = evt.request; + let processRequestPromise = Promise.resolve(); + // For same origin https requests, append idToken to header. + if (self.location.origin == getOriginFromUrl(evt.request.url) && + (self.location.protocol == 'https:' || + self.location.hostname == 'localhost') && + idToken) { + // Clone headers as request headers are immutable. + const headers = new Headers(); + req.headers.forEach((val, key) => { + headers.append(key, val); + }); + // Add ID token to header. + headers.append('Authorization', 'Bearer ' + idToken); + processRequestPromise = getBodyContent(req).then((body) => { + try { + req = new Request(req.url, { + method: req.method, + headers: headers, + mode: 'same-origin', + credentials: req.credentials, + cache: req.cache, + redirect: req.redirect, + referrer: req.referrer, + body, + // bodyUsed: req.bodyUsed, + // context: req.context + }); + } catch (e) { + // This will fail for CORS requests. We just continue with the + // fetch caching logic below and do not pass the ID token. + } + }); + } + return processRequestPromise.then(() => { + return fetch(req); + }); + }; + // Fetch the resource after checking for the ID token. + // This can also be integrated with existing logic to serve cached files + // in offline mode. + evt.respondWith(getIdTokenPromise().then(requestProcessor, requestProcessor)); + }); + // [END auth_svc_intercept] +} + +function svcListenActivate(clients) { + // [START auth_svc_listen_activate] + self.addEventListener('activate', (event) => { + event.waitUntil(clients.claim()); + }); + // [END auth_svc_listen_activate] +} + +function svcRegister() { + // [START auth_svc_register] + // Install servicerWorker if supported on sign-in/sign-up page. + if ('serviceWorker' in navigator) { + navigator.serviceWorker.register('/service-worker.js', {scope: '/'}); + } + // [END auth_svc_register] +} + +function svcSignInEmail(email, password) { + // [START auth_svc_sign_in_email] + const { getAuth, signInWithEmailAndPassword } = require("firebase/auth"); + + // Sign in screen. + const auth = getAuth(); + signInWithEmailAndPassword(auth, email, password) + .then((result) => { + // Redirect to profile page after sign-in. The service worker will detect + // this and append the ID token to the header. + window.location.assign('/profile'); + }) + .catch((error) => { + // Error occurred. + }); + // [END auth_svc_sign_in_email] +} diff --git a/auth-next/twitter.js b/auth-next/twitter.js new file mode 100644 index 00000000..f96e574b --- /dev/null +++ b/auth-next/twitter.js @@ -0,0 +1,85 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +function twitterProvider() { + // [START auth_twitter_provider_create] + const { TwitterAuthProvider } = require("firebase/auth"); + + const provider = new TwitterAuthProvider(); + // [END auth_twitter_provider_create] + + // [START auth_twitter_provider_params] + provider.setCustomParameters({ + 'lang': 'es' + }); + // [END auth_twitter_provider_params] +} + +function twitterSignInPopup(provider) { + // [START auth_twitter_signin_popup] + const { getAuth, signInWithPopup, TwitterAuthProvider } = require("firebase/auth"); + + const auth = getAuth(); + signInWithPopup(auth, provider) + .then((result) => { + // This gives you a the Twitter OAuth 1.0 Access Token and Secret. + // You can use these server side with your app's credentials to access the Twitter API. + const credential = TwitterAuthProvider.credentialFromResult(result); + const token = credential.accessToken; + const secret = credential.secret; + + // The signed-in user info. + const user = result.user; + // IdP data available using getAdditionalUserInfo(result) + // ... + }).catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The AuthCredential type that was used. + const credential = TwitterAuthProvider.credentialFromError(error); + // ... + }); + // [END auth_twitter_signin_popup] +} + +function twitterSignInRedirectResult() { + // [START auth_twitter_signin_redirect_result] + const { getAuth, getRedirectResult, TwitterAuthProvider } = require("firebase/auth"); + + const auth = getAuth(); + getRedirectResult(auth) + .then((result) => { + // This gives you a the Twitter OAuth 1.0 Access Token and Secret. + // You can use these server side with your app's credentials to access the Twitter API. + const credential = TwitterAuthProvider.credentialFromResult(result); + const token = credential.accessToken; + const secret = credential.secret; + // ... + + // The signed-in user info. + const user = result.user; + // IdP data available using getAdditionalUserInfo(result) + // ... + }).catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The AuthCredential type that was used. + const credential = TwitterAuthProvider.credentialFromError(error); + // ... + }); + // [END auth_twitter_signin_redirect_result] +} + +function twitterProviderCredential(accessToken, secret) { + // [START auth_twitter_provider_credential] + const { TwitterAuthProvider } = require("firebase/auth"); + + const credential = TwitterAuthProvider.credential(accessToken, secret); + // [END auth_twitter_provider_credential] +} \ No newline at end of file diff --git a/auth-next/yahoo-oauth.js b/auth-next/yahoo-oauth.js new file mode 100644 index 00000000..e08150e7 --- /dev/null +++ b/auth-next/yahoo-oauth.js @@ -0,0 +1,126 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +// Docs: https://source.corp.google.com/piper///depot/google3/third_party/devsite/firebase/en/docs/auth/web/yahoo-oauth.md + +function yahooProvider() { + // [START auth_yahoo_provider_create] + const { OAuthProvider } = require("firebase/auth"); + + const provider = new OAuthProvider('yahoo.com'); + // [END auth_yahoo_provider_create] + + // [START auth_yahoo_provider_scopes] + // Request access to Yahoo Mail API. + provider.addScope('mail-r'); + // Request read/write access to user contacts. + // This must be preconfigured in the app's API permissions. + provider.addScope('sdct-w'); + // [END auth_yahoo_provider_scopes] + + // [START auth_yahoo_provider_params] + provider.setCustomParameters({ + // Prompt user to re-authenticate to Yahoo. + prompt: 'login', + // Localize to French. + language: 'fr' + }); + // [END auth_yahoo_provider_params] +} + +function yahooSignInPopup(provider) { + // [START auth_yahoo_signin_popup] + const { getAuth, signInWithPopup, OAuthProvider } = require("firebase/auth"); + + const auth = getAuth(); + signInWithPopup(auth, provider) + .then((result) => { + // IdP data available in result.additionalUserInfo.profile + // ... + + // Yahoo OAuth access token and ID token can be retrieved by calling: + const credential = OAuthProvider.credentialFromResult(result); + const accessToken = credential.accessToken; + const idToken = credential.idToken; + }) + .catch((error) => { + // Handle error. + }); + // [END auth_yahoo_signin_popup] +} + +function yahooSignInRedirect(provider) { + // [START auth_yahoo_signin_redirect] + const { getAuth, signInWithRedirect } = require("firebase/auth"); + + const auth = getAuth(); + signInWithRedirect(auth, provider); + // [END auth_yahoo_signin_redirect] +} + +function yahooSigninRedirectResult() { + // [START auth_yahoo_signin_redirect_result] + const { getAuth, getRedirectResult, OAuthProvider } = require("firebase/auth"); + + const auth = getAuth(); + getRedirectResult(auth) + .then((result) => { + // IdP data available in result.additionalUserInfo.profile + // ... + + // Yahoo OAuth access token and ID token can be retrieved by calling: + const credential = OAuthProvider.credentialFromResult(result); + const accessToken = credential.accessToken; + const idToken = credential.idToken; + }) + .catch((error) => { + // Handle error. + }); + // [END auth_yahoo_signin_redirect_result] +} + +function yahooLinkPopup() { + // [START auth_yahoo_link_popup] + const { getAuth, linkWithPopup, OAuthProvider } = require("firebase/auth"); + + const provider = new OAuthProvider('yahoo.com'); + const auth = getAuth(); + linkWithPopup(auth.currentUser, provider) + .then((result) => { + // Yahoo credential is linked to the current user. + // IdP data available in result.additionalUserInfo.profile. + + // Get the OAuth access token and ID Token + const credential = OAuthProvider.credentialFromResult(result); + const accessToken = credential.accessToken; + const idToken = credential.idToken; + }) + .catch((error) => { + // Handle error. + }); + // [END auth_yahoo_link_popup] +} + +function yahooReauthPopup() { + // [START auth_yahoo_reauth_popup] + const { getAuth, reauthenticateWithPopup, OAuthProvider } = require("firebase/auth"); + + const provider = new OAuthProvider('yahoo.com'); + const auth = getAuth(); + reauthenticateWithPopup(auth.currentUser, provider) + .then((result) => { + // User is re-authenticated with fresh tokens minted and + // should be able to perform sensitive operations like account + // deletion and email or password update. + // IdP data available in result.additionalUserInfo.profile. + + // Get the OAuth access token and ID Token + const credential = OAuthProvider.credentialFromResult(result); + const accessToken = credential.accessToken; + const idToken = credential.idToken; + }) + .catch((error) => { + // Handle error. + }); + // [END auth_yahoo_reauth_popup] +} diff --git a/auth/anonymous.js b/auth/anonymous.js new file mode 100644 index 00000000..ee618c3d --- /dev/null +++ b/auth/anonymous.js @@ -0,0 +1,19 @@ +// These samples are intended for Web so this import would normally be +// done in HTML however using modules here is more convenient for +// ensuring sample correctness offline. +import firebase from "firebase/app"; +import "firebase/auth"; + +function anonSignIn() { + // [START auth_anon_sign_in] + firebase.auth().signInAnonymously() + .then(() => { + // Signed in.. + }) + .catch((error) => { + var errorCode = error.code; + var errorMessage = error.message; + // ... + }); + // [END auth_anon_sign_in] +} diff --git a/auth/apple.js b/auth/apple.js new file mode 100644 index 00000000..00e2598e --- /dev/null +++ b/auth/apple.js @@ -0,0 +1,208 @@ +// These samples are intended for Web so this import would normally be +// done in HTML however using modules here is more convenient for +// ensuring sample correctness offline. +import firebase from "firebase/app"; +import "firebase/auth"; + +// Docs: https://source.corp.google.com/piper///depot/google3/third_party/devsite/firebase/en/docs/auth/web/apple.md + +function appleProvider() { + // [START auth_apple_provider_create] + var provider = new firebase.auth.OAuthProvider('apple.com'); + // [END auth_apple_provider_create] + + // [START auth_apple_provider_scopes] + provider.addScope('email'); + provider.addScope('name'); + // [END auth_apple_provider_scopes] + + // [START auth_apple_provider_params] + provider.setCustomParameters({ + // Localize the Apple authentication screen in French. + locale: 'fr' + }); + // [END auth_apple_provider_params] +} + +function appleSignInPopup(provider) { + // [START auth_apple_signin_popup] + firebase + .auth() + .signInWithPopup(provider) + .then((result) => { + /** @type {firebase.auth.OAuthCredential} */ + var credential = result.credential; + + // The signed-in user info. + var user = result.user; + + // You can also get the Apple OAuth Access and ID Tokens. + var accessToken = credential.accessToken; + var idToken = credential.idToken; + + // IdP data available using getAdditionalUserInfo(result) + // ... + }) + .catch((error) => { + // Handle Errors here. + var errorCode = error.code; + var errorMessage = error.message; + // The email of the user's account used. + var email = error.email; + // The firebase.auth.AuthCredential type that was used. + var credential = error.credential; + + // ... + }); + // [END auth_apple_signin_popup] +} + +function appleSignInRedirect(provider) { + // [START auth_apple_signin_redirect] + firebase.auth().signInWithRedirect(provider); + // [END auth_apple_signin_redirect] +} + +function appleSignInRedirectResult() { + // [START auth_apple_signin_redirect_result] + // Result from Redirect auth flow. + firebase + .auth() + .getRedirectResult() + .then((result) => { + if (result.credential) { + /** @type {firebase.auth.OAuthCredential} */ + var credential = result.credential; + + // You can get the Apple OAuth Access and ID Tokens. + var accessToken = credential.accessToken; + var idToken = credential.idToken; + + // IdP data available in result.additionalUserInfo.profile. + // ... + } + // The signed-in user info. + var user = result.user; + }) + .catch((error) => { + // Handle Errors here. + var errorCode = error.code; + var errorMessage = error.message; + // The email of the user's account used. + var email = error.email; + // The firebase.auth.AuthCredential type that was used. + var credential = error.credential; + + // ... + }); + // [END auth_apple_signin_redirect_result] +} + +function appleReauthenticatePopup() { + // [START auth_apple_reauthenticate_popup] + const provider = new firebase.auth.OAuthProvider('apple.com'); + + firebase + .auth() + .currentUser + .reauthenticateWithPopup(provider) + .then((result) => { + // User is re-authenticated with fresh tokens minted and can perform + // sensitive operations like account deletion, or updating their email + // address or password. + /** @type {firebase.auth.OAuthCredential} */ + var credential = result.credential; + + // The signed-in user info. + var user = result.user; + // You can also get the Apple OAuth Access and ID Tokens. + var accessToken = credential.accessToken; + var idToken = credential.idToken; + + // IdP data available in result.additionalUserInfo.profile. + // ... + }) + .catch((error) => { + // Handle Errors here. + var errorCode = error.code; + var errorMessage = error.message; + // The email of the user's account used. + var email = error.email; + // The firebase.auth.AuthCredential type that was used. + var credential = error.credential; + + // ... + }); + // [END auth_apple_reauthenticate_popup] +} + +function appleLinkFacebook() { + // [START auth_apple_link_facebook] + const provider = new firebase.auth.FacebookAuthProvider(); + provider.addScope('user_birthday'); + + // Assuming the current user is an Apple user linking a Facebook provider. + firebase.auth().currentUser.linkWithPopup(provider) + .then((result) => { + // Facebook credential is linked to the current Apple user. + // Facebook additional data available in result.additionalUserInfo.profile, + + // Additional Facebook OAuth access token can also be retrieved. + // result.credential.accessToken + + // The user can now sign in to the same account + // with either Apple or Facebook. + }) + .catch((error) => { + // Handle error. + }); + // [END auth_apple_link_facebook] +} + +function appleNonceNode() { + // [START auth_apple_nonce_node] + const crypto = require("crypto"); + const string_decoder = require("string_decoder"); + + // Generate a new random string for each sign-in + const generateNonce = function(length) { + const decoder = new string_decoder.StringDecoder("ascii"); + const buf = Buffer.alloc(length); + var nonce = ""; + while (nonce.length < length) { + crypto.randomFillSync(buf); + nonce = decoder.write(buf); + } + return nonce.slice(0, length); + }; + + const unhashedNonce = generateNonce(10); + + // SHA256-hashed nonce in hex + const hashedNonceHex = crypto.createHash('sha256') + .update(unhashedNonce).digest().toString('hex'); + // [END auth_apple_nonce_node] +} + +function appleSignInNonce(appleIdToken, unhashedNonce,) { + // [START auth_apple_signin_nonce] + // Build Firebase credential with the Apple ID token. + const provider = new firebase.auth.OAuthProvider('apple.com'); + const authCredential = provider.credential({ + idToken: appleIdToken, + rawNonce: unhashedNonce, + }); + + // Sign in with credential form the Apple user. + firebase.auth().signInWithCredential(authCredential) + .then((result) => { + // User signed in. + }) + .catch((error) => { + // An error occurred. If error.code == 'auth/missing-or-invalid-nonce', + // make sure you're sending the SHA256-hashed nonce as a hex string + // with your request to Apple. + console.log(error); + }); + // [END auth_apple_signin_nonce] +} diff --git a/auth/auth-state-persistence.js b/auth/auth-state-persistence.js new file mode 100644 index 00000000..c85f1da2 --- /dev/null +++ b/auth/auth-state-persistence.js @@ -0,0 +1,45 @@ +// These samples are intended for Web so this import would normally be +// done in HTML however using modules here is more convenient for +// ensuring sample correctness offline. +import firebase from "firebase/app"; +import "firebase/auth"; + +function setPersistenceSession() { + var email = "..."; + var password = "..."; + + // [START auth_set_persistence_session] + firebase.auth().setPersistence(firebase.auth.Auth.Persistence.SESSION) + .then(() => { + // Existing and future Auth states are now persisted in the current + // session only. Closing the window would clear any existing state even + // if a user forgets to sign out. + // ... + // New sign-in will be persisted with session persistence. + return firebase.auth().signInWithEmailAndPassword(email, password); + }) + .catch((error) => { + // Handle Errors here. + var errorCode = error.code; + var errorMessage = error.message; + }); + // [END auth_set_persistence_session] +} + +function setPersistenceNone() { + // [START auth_set_persistence_none] + firebase.auth().setPersistence(firebase.auth.Auth.Persistence.NONE) + .then(() => { + var provider = new firebase.auth.GoogleAuthProvider(); + // In memory persistence will be applied to the signed in Google user + // even though the persistence was set to 'none' and a page redirect + // occurred. + return firebase.auth().signInWithRedirect(provider); + }) + .catch((error) => { + // Handle Errors here. + var errorCode = error.code; + var errorMessage = error.message; + }); + // [END auth_set_persistence_none] +} diff --git a/auth/cordova.js b/auth/cordova.js new file mode 100644 index 00000000..e7c3fc40 --- /dev/null +++ b/auth/cordova.js @@ -0,0 +1,57 @@ +// These samples are intended for Web so this import would normally be +// done in HTML however using modules here is more convenient for +// ensuring sample correctness offline. +import firebase from "firebase/app"; +import "firebase/auth"; + +// Docs: https://source.corp.google.com/piper///depot/google3/third_party/devsite/firebase/en/docs/auth/web/cordova.md + +function createGoogleProvider() { + // [START auth_create_google_provider] + var provider = new firebase.auth.GoogleAuthProvider(); + // [END auth_create_google_provider] +} + +function cordovaSignInRedirect(provider) { + // [START auth_cordova_sign_in_redirect] + firebase.auth().signInWithRedirect(provider).then(() => { + return firebase.auth().getRedirectResult(); + }).then((result) => { + /** @type {firebase.auth.OAuthCredential} */ + var credential = result.credential; + + // This gives you a Google Access Token. + // You can use it to access the Google API. + var token = credential.accessToken; + // The signed-in user info. + var user = result.user; + // ... + }).catch((error) => { + // Handle Errors here. + var errorCode = error.code; + var errorMessage = error.message; + }); + // [END auth_cordova_sign_in_redirect] +} + +function cordovaRedirectResult() { + // [START auth_cordova_redirect_result] + firebase.auth().getRedirectResult().then((result) => { + if (result.credential) { + /** @type {firebase.auth.OAuthCredential} */ + var credential = result.credential; + + // This gives you a Google Access Token. + // You can use it to access the Google API. + var token = credential.accessToken; + // The signed-in user info. + var user = result.user; + // ... + } + }).catch((error) => { + // Handle Errors here. + var errorCode = error.code; + var errorMessage = error.message; + }); + // [END auth_cordova_redirect_result] +} diff --git a/auth/custom-email-handler.js b/auth/custom-email-handler.js new file mode 100644 index 00000000..18a97043 --- /dev/null +++ b/auth/custom-email-handler.js @@ -0,0 +1,143 @@ +// These samples are intended for Web so this import would normally be +// done in HTML however using modules here is more convenient for +// ensuring sample correctness offline. +import firebase from "firebase/app"; +import "firebase/auth"; + +// Docs: https://source.corp.google.com/piper///depot/google3/third_party/devsite/firebase/en/docs/auth/custom-email-handler.md + +function handleUserManagementQueryParams() { + // TODO: This helpers should be implemented by the developer + function getParameterByName(name) { + return ""; + } + + // [START auth_handle_mgmt_query_params] + document.addEventListener('DOMContentLoaded', () => { + // TODO: Implement getParameterByName() + + // Get the action to complete. + var mode = getParameterByName('mode'); + // Get the one-time code from the query parameter. + var actionCode = getParameterByName('oobCode'); + // (Optional) Get the continue URL from the query parameter if available. + var continueUrl = getParameterByName('continueUrl'); + // (Optional) Get the language code if available. + var lang = getParameterByName('lang') || 'en'; + + // Configure the Firebase SDK. + // This is the minimum configuration required for the API to be used. + var config = { + 'apiKey': "YOU_API_KEY" // Copy this key from the web initialization + // snippet found in the Firebase console. + }; + var app = firebase.initializeApp(config); + var auth = app.auth(); + + // Handle the user management action. + switch (mode) { + case 'resetPassword': + // Display reset password handler and UI. + handleResetPassword(auth, actionCode, continueUrl, lang); + break; + case 'recoverEmail': + // Display email recovery handler and UI. + handleRecoverEmail(auth, actionCode, lang); + break; + case 'verifyEmail': + // Display email verification handler and UI. + handleVerifyEmail(auth, actionCode, continueUrl, lang); + break; + default: + // Error: invalid mode. + } + }, false); + // [END auth_handle_mgmt_query_params] +} + +// [START auth_handle_reset_password] +function handleResetPassword(auth, actionCode, continueUrl, lang) { + // Localize the UI to the selected language as determined by the lang + // parameter. + + // Verify the password reset code is valid. + auth.verifyPasswordResetCode(actionCode).then((email) => { + var accountEmail = email; + + // TODO: Show the reset screen with the user's email and ask the user for + // the new password. + var newPassword = "..."; + + // Save the new password. + auth.confirmPasswordReset(actionCode, newPassword).then((resp) => { + // Password reset has been confirmed and new password updated. + + // TODO: Display a link back to the app, or sign-in the user directly + // if the page belongs to the same domain as the app: + // auth.signInWithEmailAndPassword(accountEmail, newPassword); + + // TODO: If a continue URL is available, display a button which on + // click redirects the user back to the app via continueUrl with + // additional state determined from that URL's parameters. + }).catch((error) => { + // Error occurred during confirmation. The code might have expired or the + // password is too weak. + }); + }).catch((error) => { + // Invalid or expired action code. Ask user to try to reset the password + // again. + }); +} +// [END auth_handle_reset_password] + +// [START auth_handle_recover_email] +function handleRecoverEmail(auth, actionCode, lang) { + // Localize the UI to the selected language as determined by the lang + // parameter. + var restoredEmail = null; + // Confirm the action code is valid. + auth.checkActionCode(actionCode).then((info) => { + // Get the restored email address. + restoredEmail = info['data']['email']; + + // Revert to the old email. + return auth.applyActionCode(actionCode); + }).then(() => { + // Account email reverted to restoredEmail + + // TODO: Display a confirmation message to the user. + + // You might also want to give the user the option to reset their password + // in case the account was compromised: + auth.sendPasswordResetEmail(restoredEmail).then(() => { + // Password reset confirmation sent. Ask user to check their email. + }).catch((error) => { + // Error encountered while sending password reset code. + }); + }).catch((error) => { + // Invalid code. + }); +} +// [END auth_handle_recover_email] + +// [START auth_handle_verify_email] +function handleVerifyEmail(auth, actionCode, continueUrl, lang) { + // Localize the UI to the selected language as determined by the lang + // parameter. + // Try to apply the email verification code. + auth.applyActionCode(actionCode).then((resp) => { + // Email address has been verified. + + // TODO: Display a confirmation message to the user. + // You could also provide the user with a link back to the app. + + // TODO: If a continue URL is available, display a button which on + // click redirects the user back to the app via continueUrl with + // additional state determined from that URL's parameters. + }).catch((error) => { + // Code is invalid or expired. Ask the user to verify their email address + // again. + }); +} +// [END auth_handle_verify_email] + diff --git a/auth/custom.js b/auth/custom.js new file mode 100644 index 00000000..97713714 --- /dev/null +++ b/auth/custom.js @@ -0,0 +1,22 @@ +// These samples are intended for Web so this import would normally be +// done in HTML however using modules here is more convenient for +// ensuring sample correctness offline. +import firebase from "firebase/app"; +import "firebase/auth"; + +function signInCustom() { + var token = "token123"; + // [START auth_sign_in_custom] + firebase.auth().signInWithCustomToken(token) + .then((userCredential) => { + // Signed in + var user = userCredential.user; + // ... + }) + .catch((error) => { + var errorCode = error.code; + var errorMessage = error.message; + // ... + }); + // [END auth_sign_in_custom] +} diff --git a/auth/email-link-auth.js b/auth/email-link-auth.js new file mode 100644 index 00000000..ce1e22ec --- /dev/null +++ b/auth/email-link-auth.js @@ -0,0 +1,143 @@ +// These samples are intended for Web so this import would normally be +// done in HTML however using modules here is more convenient for +// ensuring sample correctness offline. +import firebase from "firebase/app"; +import "firebase/auth"; + +// Docs: https://source.corp.google.com/piper///depot/google3/third_party/devsite/firebase/en/docs/auth/web/email-link-auth.md + +function emailLinkActionCodeSettings() { + // [START auth_email_link_actioncode_settings] + var actionCodeSettings = { + // URL you want to redirect back to. The domain (www.example.com) for this + // URL must be in the authorized domains list in the Firebase Console. + url: 'https://www.example.com/finishSignUp?cartId=1234', + // This must be true. + handleCodeInApp: true, + iOS: { + bundleId: 'com.example.ios' + }, + android: { + packageName: 'com.example.android', + installApp: true, + minimumVersion: '12' + }, + dynamicLinkDomain: 'example.page.link' + }; + // [END auth_email_link_actioncode_settings] +} + +function emailLinkSend(email, actionCodeSettings) { + // [START auth_email_link_send] + firebase.auth().sendSignInLinkToEmail(email, actionCodeSettings) + .then(() => { + // The link was successfully sent. Inform the user. + // Save the email locally so you don't need to ask the user for it again + // if they open the link on the same device. + window.localStorage.setItem('emailForSignIn', email); + // ... + }) + .catch((error) => { + var errorCode = error.code; + var errorMessage = error.message; + // ... + }); + // [END auth_email_link_send] +} + +function emailLinkComplete() { + // [START email_link_complete] + // Confirm the link is a sign-in with email link. + if (firebase.auth().isSignInWithEmailLink(window.location.href)) { + // Additional state parameters can also be passed via URL. + // This can be used to continue the user's intended action before triggering + // the sign-in operation. + // Get the email if available. This should be available if the user completes + // the flow on the same device where they started it. + var email = window.localStorage.getItem('emailForSignIn'); + if (!email) { + // User opened the link on a different device. To prevent session fixation + // attacks, ask the user to provide the associated email again. For example: + email = window.prompt('Please provide your email for confirmation'); + } + // The client SDK will parse the code from the link for you. + firebase.auth().signInWithEmailLink(email, window.location.href) + .then((result) => { + // Clear email from storage. + window.localStorage.removeItem('emailForSignIn'); + // You can access the new user via result.user + // Additional user info profile not available via: + // result.additionalUserInfo.profile == null + // You can check if the user is new or existing: + // result.additionalUserInfo.isNewUser + }) + .catch((error) => { + // Some error occurred, you can inspect the code: error.code + // Common errors could be invalid email and invalid or expired OTPs. + }); + } + // [END email_link_complete] +} + +function emailLinkLink(email) { + // [START auth_email_link_link] + // Construct the email link credential from the current URL. + var credential = firebase.auth.EmailAuthProvider.credentialWithLink( + email, window.location.href); + + // Link the credential to the current user. + firebase.auth().currentUser.linkWithCredential(credential) + .then((usercred) => { + // The provider is now successfully linked. + // The phone user can now sign in with their phone number or email. + }) + .catch((error) => { + // Some error occurred. + }); + // [END auth_email_link_link] +} + +function emailLinkReauth(email) { + // [START auth_email_link_reauth] + // Construct the email link credential from the current URL. + var credential = firebase.auth.EmailAuthProvider.credentialWithLink( + email, window.location.href); + + // Re-authenticate the user with this credential. + firebase.auth().currentUser.reauthenticateWithCredential(credential) + .then((usercred) => { + // The user is now successfully re-authenticated and can execute sensitive + // operations. + }) + .catch((error) => { + // Some error occurred. + }); + // [END auth_email_link_reauth] +} + +function emailLinkDifferentiate() { + // [START email_link_diferentiate] + // After asking the user for their email. + var email = window.prompt('Please provide your email'); + firebase.auth().fetchSignInMethodsForEmail(email) + .then((signInMethods) => { + // This returns the same array as fetchProvidersForEmail but for email + // provider identified by 'password' string, signInMethods would contain 2 + // different strings: + // 'emailLink' if the user previously signed in with an email/link + // 'password' if the user has a password. + // A user could have both. + if (signInMethods.indexOf( + firebase.auth.EmailAuthProvider.EMAIL_PASSWORD_SIGN_IN_METHOD) != -1) { + // User can sign in with email/password. + } + if (signInMethods.indexOf( + firebase.auth.EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD) != -1) { + // User can sign in with email/link. + } + }) + .catch((error) => { + // Some error occurred, you can inspect the code: error.code + }); + // [END email_link_diferentiate] +} diff --git a/auth/email.js b/auth/email.js new file mode 100644 index 00000000..5520f71c --- /dev/null +++ b/auth/email.js @@ -0,0 +1,66 @@ +// These samples are intended for Web so this import would normally be +// done in HTML however using modules here is more convenient for +// ensuring sample correctness offline. +import firebase from "firebase/app"; +import "firebase/auth"; + +function signInWithEmailPassword() { + var email = "test@example.com"; + var password = "hunter2"; + // [START auth_signin_password] + firebase.auth().signInWithEmailAndPassword(email, password) + .then((userCredential) => { + // Signed in + var user = userCredential.user; + // ... + }) + .catch((error) => { + var errorCode = error.code; + var errorMessage = error.message; + }); + // [END auth_signin_password] +} + +function signUpWithEmailPassword() { + var email = "test@example.com"; + var password = "hunter2"; + // [START auth_signup_password] + firebase.auth().createUserWithEmailAndPassword(email, password) + .then((userCredential) => { + // Signed in + var user = userCredential.user; + // ... + }) + .catch((error) => { + var errorCode = error.code; + var errorMessage = error.message; + // .. + }); + // [END auth_signup_password] +} + +function sendEmailVerification() { + // [START auth_send_email_verification] + firebase.auth().currentUser.sendEmailVerification() + .then(() => { + // Email verification sent! + // ... + }); + // [END auth_send_email_verification] +} + +function sendPasswordReset() { + const email = "sam@example.com"; + // [START auth_send_password_reset] + firebase.auth().sendPasswordResetEmail(email) + .then(() => { + // Password reset email sent! + // .. + }) + .catch((error) => { + var errorCode = error.code; + var errorMessage = error.message; + // .. + }); + // [END auth_send_password_reset] +} diff --git a/auth/emulator-suite.js b/auth/emulator-suite.js new file mode 100644 index 00000000..b0f815e2 --- /dev/null +++ b/auth/emulator-suite.js @@ -0,0 +1,18 @@ +import firebase from "firebase/app"; +import "firebase/auth"; + +function emulatorConnect() { + // [START auth_emulator_connect] + const auth = firebase.auth(); + auth.useEmulator("http://127.0.0.1:9099"); + // [END auth_emulator_connect] +} + +function emulatorGoogleCredential() { + // [START auth_emulator_google_credential] + const auth = firebase.auth(); + auth.signInWithCredential(firebase.auth.GoogleAuthProvider.credential( + '{"sub": "abc123", "email": "foo@example.com", "email_verified": true}' + )); + // [END auth_emulator_google_credential] +} diff --git a/auth/facebook.js b/auth/facebook.js new file mode 100644 index 00000000..75e99d43 --- /dev/null +++ b/auth/facebook.js @@ -0,0 +1,163 @@ +// These samples are intended for Web so this import would normally be +// done in HTML however using modules here is more convenient for +// ensuring sample correctness offline. +import firebase from "firebase/app"; +import "firebase/auth"; + +function facebookProvider() { + // [START auth_facebook_provider_create] + var provider = new firebase.auth.FacebookAuthProvider(); + // [END auth_facebook_provider_create] + + // / [START auth_facebook_provider_scopes] + provider.addScope('user_birthday'); + // [END auth_facebook_provider_scopes] + + // [START auth_facebook_provider_params] + provider.setCustomParameters({ + 'display': 'popup' + }); + // [END auth_facebook_provider_params] +} + +function facebookSignInPopup(provider) { + // [START auth_facebook_signin_popup] + firebase + .auth() + .signInWithPopup(provider) + .then((result) => { + /** @type {firebase.auth.OAuthCredential} */ + var credential = result.credential; + + // The signed-in user info. + var user = result.user; + // IdP data available in result.additionalUserInfo.profile. + // ... + + // This gives you a Facebook Access Token. You can use it to access the Facebook API. + var accessToken = credential.accessToken; + + // ... + }) + .catch((error) => { + // Handle Errors here. + var errorCode = error.code; + var errorMessage = error.message; + // The email of the user's account used. + var email = error.email; + // The firebase.auth.AuthCredential type that was used. + var credential = error.credential; + + // ... + }); + // [END auth_facebook_signin_popup] +} + +function facebookSignInRedirectResult() { + // [START auth_facebook_signin_redirect_result] + firebase.auth() + .getRedirectResult() + .then((result) => { + if (result.credential) { + /** @type {firebase.auth.OAuthCredential} */ + var credential = result.credential; + + // This gives you a Facebook Access Token. You can use it to access the Facebook API. + var token = credential.accessToken; + // ... + } + // The signed-in user info. + var user = result.user; + // IdP data available in result.additionalUserInfo.profile. + // ... + }).catch((error) => { + // Handle Errors here. + var errorCode = error.code; + var errorMessage = error.message; + // The email of the user's account used. + var email = error.email; + // The firebase.auth.AuthCredential type that was used. + var credential = error.credential; + // ... + }); + // [END auth_facebook_signin_redirect_result] +} + +// [START auth_facebook_callback] +function checkLoginState(response) { + if (response.authResponse) { + // User is signed-in Facebook. + var unsubscribe = firebase.auth().onAuthStateChanged((firebaseUser) => { + unsubscribe(); + // Check if we are already signed-in Firebase with the correct user. + if (!isUserEqual(response.authResponse, firebaseUser)) { + // Build Firebase credential with the Facebook auth token. + var credential = firebase.auth.FacebookAuthProvider.credential( + response.authResponse.accessToken); + + // Sign in with the credential from the Facebook user. + firebase.auth().signInWithCredential(credential) + .catch((error) => { + // Handle Errors here. + var errorCode = error.code; + var errorMessage = error.message; + // The email of the user's account used. + var email = error.email; + // The firebase.auth.AuthCredential type that was used. + var credential = error.credential; + // ... + }); + } else { + // User is already signed-in Firebase with the correct user. + } + }); + } else { + // User is signed-out of Facebook. + firebase.auth().signOut(); + } +} +// [END auth_facebook_callback] + +// [START auth_facebook_checksameuser] +function isUserEqual(facebookAuthResponse, firebaseUser) { + if (firebaseUser) { + var providerData = firebaseUser.providerData; + for (var i = 0; i < providerData.length; i++) { + if (providerData[i].providerId === firebase.auth.FacebookAuthProvider.PROVIDER_ID && + providerData[i].uid === facebookAuthResponse.userID) { + // We don't need to re-auth the Firebase connection. + return true; + } + } + } + return false; +} +// [END auth_facebook_checksameuser] + +function authWithCredential(credential) { + // [START auth_facebook_signin_credential] + // Sign in with the credential from the Facebook user. + firebase.auth().signInWithCredential(credential) + .then((result) => { + // Signed in + var credential = result.credential; + // ... + }) + .catch((error) => { + // Handle Errors here. + var errorCode = error.code; + var errorMessage = error.message; + // The email of the user's account used. + var email = error.email; + // The firebase.auth.AuthCredential type that was used. + var credential = error.credential; + // ... + }); + // [END auth_facebook_signin_credential] +} + +function facebookProviderCredential(accessToken) { + // [START auth_facebook_provider_credential] + var credential = firebase.auth.FacebookAuthProvider.credential(accessToken); + // [END auth_facebook_provider_credential] +} diff --git a/auth/firebaseui.js b/auth/firebaseui.js new file mode 100644 index 00000000..dbbf9031 --- /dev/null +++ b/auth/firebaseui.js @@ -0,0 +1,301 @@ +// These samples are intended for Web so this import would normally be +// done in HTML however using modules here is more convenient for +// ensuring sample correctness offline. +import firebase from "firebase/app"; +import "firebase/auth"; + +import * as firebaseui from "firebaseui"; + +// Docs: https://source.corp.google.com/piper///depot/google3/third_party/devsite/firebase/en/docs/auth/web/firebaseui.md + +function fuiInit() { + // [START auth_fui_init] + var ui = new firebaseui.auth.AuthUI(firebase.auth()); + // [END auth_fui_init] + + return ui; +} + +function fuiStartEmail() { + var ui = fuiInit(); + // [START auth_fui_start_email] + ui.start('#firebaseui-auth-container', { + signInOptions: [ + firebase.auth.EmailAuthProvider.PROVIDER_ID + ], + // Other config options... + }); + // [END auth_fui_start_email] +} + +function fuiStartEmailOptions() { + var ui = fuiInit(); + // [START auth_fui_start_email_options] + ui.start('#firebaseui-auth-container', { + signInOptions: [ + { + provider: firebase.auth.EmailAuthProvider.PROVIDER_ID, + requireDisplayName: false + } + ] + }); + // [END auth_fui_start_email_options] +} + +function fuiStartEmailLink() { + var ui = fuiInit(); + // [START auth_fui_start_email_link] + ui.start('#firebaseui-auth-container', { + signInOptions: [ + { + provider: firebase.auth.EmailAuthProvider.PROVIDER_ID, + signInMethod: firebase.auth.EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD + } + ], + // Other config options... + }); + // [END auth_fui_start_email_link] +} + +function fuiEmailLinkResult(uiConfig) { + var ui = fuiInit(); + // [START auth_fui_email_link_result] + // Is there an email link sign-in? + if (ui.isPendingRedirect()) { + ui.start('#firebaseui-auth-container', uiConfig); + } + // This can also be done via: + if (firebase.auth().isSignInWithEmailLink(window.location.href)) { + ui.start('#firebaseui-auth-container', uiConfig); + } + // [END auth_fui_email_link_result] +} + +function fuiStartEmailLinkOptions() { + var ui = fuiInit(); + // [START auth_fui_start_email_link_options] + ui.start('#firebaseui-auth-container', { + signInOptions: [ + { + provider: firebase.auth.EmailAuthProvider.PROVIDER_ID, + signInMethod: firebase.auth.EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD, + // Allow the user the ability to complete sign-in cross device, + // including the mobile apps specified in the ActionCodeSettings + // object below. + forceSameDevice: false, + // Used to define the optional firebase.auth.ActionCodeSettings if + // additional state needs to be passed along request and whether to open + // the link in a mobile app if it is installed. + emailLinkSignIn: () => { + return { + // Additional state showPromo=1234 can be retrieved from URL on + // sign-in completion in signInSuccess callback by checking + // window.location.href. + url: 'https://www.example.com/completeSignIn?showPromo=1234', + // Custom FDL domain. + dynamicLinkDomain: 'example.page.link', + // Always true for email link sign-in. + handleCodeInApp: true, + // Whether to handle link in iOS app if installed. + iOS: { + bundleId: 'com.example.ios' + }, + // Whether to handle link in Android app if opened in an Android + // device. + android: { + packageName: 'com.example.android', + installApp: true, + minimumVersion: '12' + } + }; + } + } + ] + }); + // [END auth_fui_start_email_link_options] +} + +function fuiStartOauth() { + var ui = fuiInit(); + // [START auth_fui_start_oauth] + ui.start('#firebaseui-auth-container', { + signInOptions: [ + // List of OAuth providers supported. + firebase.auth.GoogleAuthProvider.PROVIDER_ID, + firebase.auth.FacebookAuthProvider.PROVIDER_ID, + firebase.auth.TwitterAuthProvider.PROVIDER_ID, + firebase.auth.GithubAuthProvider.PROVIDER_ID + ], + // Other config options... + }); + // [END auth_fui_start_oauth] +} + +function fuiStartOauthOptions() { + var ui = fuiInit(); + // [START auth_fui_start_oauth_options] + ui.start('#firebaseui-auth-container', { + signInOptions: [ + { + provider: firebase.auth.GoogleAuthProvider.PROVIDER_ID, + scopes: [ + 'https://www.googleapis.com/auth/contacts.readonly' + ], + customParameters: { + // Forces account selection even when one account + // is available. + prompt: 'select_account' + } + }, + { + provider: firebase.auth.FacebookAuthProvider.PROVIDER_ID, + scopes: [ + 'public_profile', + 'email', + 'user_likes', + 'user_friends' + ], + customParameters: { + // Forces password re-entry. + auth_type: 'reauthenticate' + } + }, + firebase.auth.TwitterAuthProvider.PROVIDER_ID, // Twitter does not support scopes. + firebase.auth.EmailAuthProvider.PROVIDER_ID // Other providers don't need to be given as object. + ] + }); + // [END auth_fui_start_oauth_options] +} + +function fuiStartPhone() { + var ui = fuiInit(); + // [START auth_fui_start_phone] + ui.start('#firebaseui-auth-container', { + signInOptions: [ + firebase.auth.PhoneAuthProvider.PROVIDER_ID + ], + // Other config options... + }); + // [END auth_fui_start_phone] +} + +function fuiStartPhoneOptions() { + var ui = fuiInit(); + // [START auth_fui_start_phone_options] + ui.start('#firebaseui-auth-container', { + signInOptions: [ + { + provider: firebase.auth.PhoneAuthProvider.PROVIDER_ID, + recaptchaParameters: { + type: 'image', // 'audio' + size: 'normal', // 'invisible' or 'compact' + badge: 'bottomleft' // ' bottomright' or 'inline' applies to invisible. + }, + defaultCountry: 'GB', // Set default country to the United Kingdom (+44). + // For prefilling the national number, set defaultNationNumber. + // This will only be observed if only phone Auth provider is used since + // for multiple providers, the NASCAR screen will always render first + // with a 'sign in with phone number' button. + defaultNationalNumber: '1234567890', + // You can also pass the full phone number string instead of the + // 'defaultCountry' and 'defaultNationalNumber'. However, in this case, + // the first country ID that matches the country code will be used to + // populate the country selector. So for countries that share the same + // country code, the selected country may not be the expected one. + // In that case, pass the 'defaultCountry' instead to ensure the exact + // country is selected. The 'defaultCountry' and 'defaultNationaNumber' + // will always have higher priority than 'loginHint' which will be ignored + // in their favor. In this case, the default country will be 'GB' even + // though 'loginHint' specified the country code as '+1'. + loginHint: '+11234567890' + } + ] + }); + // [END auth_fui_start_phone_options] +} + +function fuiConfig() { + // [START auth_fui_config] + var uiConfig = { + callbacks: { + signInSuccessWithAuthResult: (authResult, redirectUrl) => { + // User successfully signed in. + // Return type determines whether we continue the redirect automatically + // or whether we leave that to developer to handle. + return true; + }, + uiShown: () => { + // The widget is rendered. + // Hide the loader. + document.getElementById('loader').style.display = 'none'; + } + }, + // Will use popup for IDP Providers sign-in flow instead of the default, redirect. + signInFlow: 'popup', + signInSuccessUrl: '', + signInOptions: [ + // Leave the lines as is for the providers you want to offer your users. + firebase.auth.GoogleAuthProvider.PROVIDER_ID, + firebase.auth.FacebookAuthProvider.PROVIDER_ID, + firebase.auth.TwitterAuthProvider.PROVIDER_ID, + firebase.auth.GithubAuthProvider.PROVIDER_ID, + firebase.auth.EmailAuthProvider.PROVIDER_ID, + firebase.auth.PhoneAuthProvider.PROVIDER_ID + ], + // Terms of service url. + tosUrl: '', + // Privacy policy url. + privacyPolicyUrl: '' + }; + // [END auth_fui_config] +} + +function fuiConfigStart(uiConfig) { + var ui = fuiInit(); + // [START auth_fui_config_start] + ui.start('#firebaseui-auth-container', uiConfig); + // [END auth_fui_config_start] +} + +function fuiHandleAnonymous() { + var ui = fuiInit(); + // [START auth_fui_handle_anonymous] + // Temp variable to hold the anonymous user data if needed. + var data = null; + // Hold a reference to the anonymous current user. + var anonymousUser = firebase.auth().currentUser; + ui.start('#firebaseui-auth-container', { + // Whether to upgrade anonymous users should be explicitly provided. + // The user must already be signed in anonymously before FirebaseUI is + // rendered. + autoUpgradeAnonymousUsers: true, + signInSuccessUrl: '', + signInOptions: [ + firebase.auth.GoogleAuthProvider.PROVIDER_ID, + firebase.auth.FacebookAuthProvider.PROVIDER_ID, + firebase.auth.EmailAuthProvider.PROVIDER_ID, + firebase.auth.PhoneAuthProvider.PROVIDER_ID + ], + callbacks: { + // signInFailure callback must be provided to handle merge conflicts which + // occur when an existing credential is linked to an anonymous user. + signInFailure: (error) => { + // For merge conflicts, the error.code will be + // 'firebaseui/anonymous-upgrade-merge-conflict'. + if (error.code != 'firebaseui/anonymous-upgrade-merge-conflict') { + return Promise.resolve(); + } + // The credential the user tried to sign in with. + var cred = error.credential; + // Copy data from anonymous user to permanent user and delete anonymous + // user. + // ... + // Finish sign-in after data is copied. + return firebase.auth().signInWithCredential(cred).then(() => { + return; + }); + } + } + }); + // [END auth_fui_handle_anonymous] +} diff --git a/auth/github.js b/auth/github.js new file mode 100644 index 00000000..8ae9b39c --- /dev/null +++ b/auth/github.js @@ -0,0 +1,87 @@ +// These samples are intended for Web so this import would normally be +// done in HTML however using modules here is more convenient for +// ensuring sample correctness offline. +import firebase from "firebase/app"; +import "firebase/auth"; + +function githubProvider() { + // [START auth_github_provider_create] + var provider = new firebase.auth.GithubAuthProvider(); + // [END auth_github_provider_create] + + // [START auth_github_provider_scopes] + provider.addScope('repo'); + // [END auth_github_provider_scopes] + + // [START auth_github_provider_params] + provider.setCustomParameters({ + 'allow_signup': 'false' + }); + // [END auth_github_provider_params] +} + +function githubProviderCredential(token) { + // [START auth_github_provider_credential] + var credential = firebase.auth.GithubAuthProvider.credential(token); + // [END auth_github_provider_credential] +} + +function githubSignInPopup(provider) { + // [START auth_github_signin_popup] + firebase + .auth() + .signInWithPopup(provider) + .then((result) => { + /** @type {firebase.auth.OAuthCredential} */ + var credential = result.credential; + + // This gives you a GitHub Access Token. You can use it to access the GitHub API. + var token = credential.accessToken; + + // The signed-in user info. + var user = result.user; + // IdP data available in result.additionalUserInfo.profile. + // ... + }).catch((error) => { + // Handle Errors here. + var errorCode = error.code; + var errorMessage = error.message; + // The email of the user's account used. + var email = error.email; + // The firebase.auth.AuthCredential type that was used. + var credential = error.credential; + // ... + }); + // [END auth_github_signin_popup] +} + +function githubSignInRedirectResult() { + // [START auth_github_signin_redirect_result] + firebase.auth() + .getRedirectResult() + .then((result) => { + if (result.credential) { + /** @type {firebase.auth.OAuthCredential} */ + var credential = result.credential; + + // This gives you a GitHub Access Token. You can use it to access the GitHub API. + var token = credential.accessToken; + // ... + } + + // The signed-in user info. + var user = result.user; + // IdP data available in result.additionalUserInfo.profile. + // ... + }).catch((error) => { + // Handle Errors here. + var errorCode = error.code; + var errorMessage = error.message; + // The email of the user's account used. + var email = error.email; + // The firebase.auth.AuthCredential type that was used. + var credential = error.credential; + // ... + }); + // [END auth_github_signin_redirect_result] +} diff --git a/auth/google-signin.js b/auth/google-signin.js new file mode 100644 index 00000000..06a488e4 --- /dev/null +++ b/auth/google-signin.js @@ -0,0 +1,153 @@ +// These samples are intended for Web so this import would normally be +// done in HTML however using modules here is more convenient for +// ensuring sample correctness offline. +import firebase from "firebase/app"; +import "firebase/auth"; + +// Docs: https://source.corp.google.com/piper///depot/google3/third_party/devsite/firebase/en/docs/auth/web/google-signin.md + +function googleProvider() { + // [START auth_google_provider_create] + var provider = new firebase.auth.GoogleAuthProvider(); + // [END auth_google_provider_create] + + // [START auth_google_provider_scopes] + provider.addScope('https://www.googleapis.com/auth/contacts.readonly'); + // [END auth_google_provider_scopes] + + // [START auth_google_provider_params] + provider.setCustomParameters({ + 'login_hint': 'user@example.com' + }); + // [END auth_google_provider_params] +} + +function googleSignInPopup(provider) { + // [START auth_google_signin_popup] + firebase.auth() + .signInWithPopup(provider) + .then((result) => { + /** @type {firebase.auth.OAuthCredential} */ + var credential = result.credential; + + // This gives you a Google Access Token. You can use it to access the Google API. + var token = credential.accessToken; + // The signed-in user info. + var user = result.user; + // IdP data available in result.additionalUserInfo.profile. + // ... + }).catch((error) => { + // Handle Errors here. + var errorCode = error.code; + var errorMessage = error.message; + // The email of the user's account used. + var email = error.email; + // The firebase.auth.AuthCredential type that was used. + var credential = error.credential; + // ... + }); + // [END auth_google_signin_popup] +} + +function googleSignInRedirectResult() { + // [START auth_google_signin_redirect_result] + firebase.auth() + .getRedirectResult() + .then((result) => { + if (result.credential) { + /** @type {firebase.auth.OAuthCredential} */ + var credential = result.credential; + + // This gives you a Google Access Token. You can use it to access the Google API. + var token = credential.accessToken; + // ... + } + // The signed-in user info. + var user = result.user; + // IdP data available in result.additionalUserInfo.profile. + // ... + }).catch((error) => { + // Handle Errors here. + var errorCode = error.code; + var errorMessage = error.message; + // The email of the user's account used. + var email = error.email; + // The firebase.auth.AuthCredential type that was used. + var credential = error.credential; + // ... + }); + // [END auth_google_signin_redirect_result] +} + +function googleBuildAndSignIn(id_token) { + // [START auth_google_build_signin] + // Build Firebase credential with the Google ID token. + var credential = firebase.auth.GoogleAuthProvider.credential(id_token); + + // Sign in with credential from the Google user. + firebase.auth().signInWithCredential(credential).catch((error) => { + // Handle Errors here. + var errorCode = error.code; + var errorMessage = error.message; + // The email of the user's account used. + var email = error.email; + // The firebase.auth.AuthCredential type that was used. + var credential = error.credential; + // ... + }); + // [END auth_google_build_signin] +} + +// [START auth_google_callback] +function onSignIn(googleUser) { + console.log('Google Auth Response', googleUser); + // We need to register an Observer on Firebase Auth to make sure auth is initialized. + var unsubscribe = firebase.auth().onAuthStateChanged((firebaseUser) => { + unsubscribe(); + // Check if we are already signed-in Firebase with the correct user. + if (!isUserEqual(googleUser, firebaseUser)) { + // Build Firebase credential with the Google ID token. + var credential = firebase.auth.GoogleAuthProvider.credential( + googleUser.getAuthResponse().id_token); + + // Sign in with credential from the Google user. + // [START auth_google_signin_credential] + firebase.auth().signInWithCredential(credential).catch((error) => { + // Handle Errors here. + var errorCode = error.code; + var errorMessage = error.message; + // The email of the user's account used. + var email = error.email; + // The firebase.auth.AuthCredential type that was used. + var credential = error.credential; + // ... + }); + // [END auth_google_signin_credential] + } else { + console.log('User already signed-in Firebase.'); + } + }); +} +// [END auth_google_callback] + +// [START auth_google_checksameuser] +function isUserEqual(googleUser, firebaseUser) { + if (firebaseUser) { + var providerData = firebaseUser.providerData; + for (var i = 0; i < providerData.length; i++) { + if (providerData[i].providerId === firebase.auth.GoogleAuthProvider.PROVIDER_ID && + providerData[i].uid === googleUser.getBasicProfile().getId()) { + // We don't need to reauth the Firebase connection. + return true; + } + } + } + return false; +} +// [END auth_google_checksameuser] + +function googleProviderCredential(idToken) { + // [START auth_google_provider_credential] + var credential = firebase.auth.GoogleAuthProvider.credential(idToken); + // [END auth_google_provider_credential] +} diff --git a/auth/index.js b/auth/index.js new file mode 100644 index 00000000..35546aa3 --- /dev/null +++ b/auth/index.js @@ -0,0 +1,114 @@ +// These samples are intended for Web so this import would normally be +// done in HTML however using modules here is more convenient for +// ensuring sample correctness offline. +import firebase from "firebase/app"; +import "firebase/auth"; + +// ========================================================================================== +// Docs: Snippets in this file are "general purpose" and are used on more than one docs page +// ========================================================================================== + +function makeGoogleCredential(googleUser) { + // [START auth_make_google_credential] + var credential = firebase.auth.GoogleAuthProvider.credential( + googleUser.getAuthResponse().id_token); + // [END auth_make_google_credential] +} + +function makeFacebookCredential(response) { + // [START auth_make_facebook_credential] + var credential = firebase.auth.FacebookAuthProvider.credential( + response.authResponse.accessToken); + // [END auth_make_facebook_credential] +} + +function makeEmailCredential(email, password) { + // [START auth_make_email_credential] + var credential = firebase.auth.EmailAuthProvider.credential(email, password); + // [END auth_make_email_credential] +} + +function signOut() { + // [START auth_sign_out] + firebase.auth().signOut().then(() => { + // Sign-out successful. + }).catch((error) => { + // An error happened. + }); + // [END auth_sign_out] +} + +function authStateListener() { + // [START auth_state_listener] + firebase.auth().onAuthStateChanged((user) => { + if (user) { + // User is signed in, see docs for a list of available properties + // https://firebase.google.com/docs/reference/js/v8/firebase.User + var uid = user.uid; + // ... + } else { + // User is signed out + // ... + } + }); + // [END auth_state_listener] +} + +function currentUser() { + // [START auth_current_user] + const user = firebase.auth().currentUser; + + if (user) { + // User is signed in, see docs for a list of available properties + // https://firebase.google.com/docs/reference/js/v8/firebase.User + // ... + } else { + // No user is signed in. + } + // [END auth_current_user] +} + +function setLanguageCode() { + // [START auth_set_language_code] + firebase.auth().languageCode = 'it'; + // To apply the default browser preference instead of explicitly setting it. + // firebase.auth().useDeviceLanguage(); + // [END auth_set_language_code] +} + +function authWithCredential(credential) { + // [START auth_signin_credential] + // Sign in with the credential from the user. + firebase.auth() + .signInWithCredential(credential) + .then((result) => { + // Signed in + // ... + }) + .catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.email; + // ... + }); + // [END auth_signin_credential] +} + +function signInRedirect(provider) { + // [START auth_signin_redirect] + firebase.auth().signInWithRedirect(provider); + // [END auth_signin_redirect] +} + +function initializeWithCustomDomain() { + // [START auth_init_custom_domain] + firebase.initializeApp({ + apiKey: '...', + // By default, authDomain is '[YOUR_APP].firebaseapp.com'. + // You may replace it with a custom domain. + authDomain: '[YOUR_CUSTOM_DOMAIN]' + }); + // [END auth_init_custom_domain] +} diff --git a/auth/initialization.txt b/auth/initialization.txt new file mode 100644 index 00000000..bccb0571 --- /dev/null +++ b/auth/initialization.txt @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/auth/link-multiple-accounts.js b/auth/link-multiple-accounts.js index b4026d85..8eed77b8 100644 --- a/auth/link-multiple-accounts.js +++ b/auth/link-multiple-accounts.js @@ -1,31 +1,33 @@ // These samples are intended for Web so this import would normally be // done in HTML however using modules here is more convenient for // ensuring sample correctness offline. -var firebase = require('firebase'); -var auth = firebase.auth(); +import firebase from "firebase/app"; +import "firebase/auth"; -var MyUserDataRepo = function() {}; +const auth = firebase.auth(); + +const MyUserDataRepo = function() {}; MyUserDataRepo.prototype.merge = function(data1, data2) { // TODO(you): How you implement this is specific to your application! return { ...data1, ...data2, - } -} + }; +}; MyUserDataRepo.prototype.set = function(user, data) { // TODO(you): How you implement this is specific to your application! -} +}; MyUserDataRepo.prototype.delete = function(user) { // TODO(you): How you implement this is specific to your application! -} +}; MyUserDataRepo.prototype.get = function(user) { // TODO(you): How you implement this is specific to your application! return {}; -} +}; function getProviders() { // [START auth_get_providers] @@ -36,33 +38,25 @@ function getProviders() { // [END auth_get_providers] } -function simpleLink() { - // This is just a dummy variable for sample purposes, the other - // snippets demonstrate how to get a real credential. - var credential = new firebase.auth.AuthCredential(); - +function simpleLink(credential) { // [START auth_simple_link] auth.currentUser.linkWithCredential(credential) - .then(function(usercred) { + .then((usercred) => { var user = usercred.user; console.log("Account linking success", user); - }).catch(function(error) { + }).catch((error) => { console.log("Account linking error", error); }); // [END auth_simple_link] } -function anonymousLink() { - // This is just a dummy variable for sample purposes, the other - // snippets demonstrate how to get a real credential. - var credential = new firebase.auth.AuthCredential(); - +function anonymousLink(credential) { // [START auth_anonymous_link] auth.currentUser.linkWithCredential(credential) - .then(function(usercred) { + .then((usercred) => { var user = usercred.user; console.log("Anonymous account successfully upgraded", user); - }).catch(function(error) { + }).catch((error) => { console.log("Error upgrading anonymous account", error); }); // [END auth_anonymous_link] @@ -72,12 +66,12 @@ function linkWithPopup() { var provider = new firebase.auth.GoogleAuthProvider(); // [START auth_link_with_popup] - auth.currentUser.linkWithPopup(provider).then(function(result) { + auth.currentUser.linkWithPopup(provider).then((result) => { // Accounts successfully linked. var credential = result.credential; var user = result.user; // ... - }).catch(function(error) { + }).catch((error) => { // Handle Errors here. // ... }); @@ -94,25 +88,21 @@ function linkWithRedirect() { // [END auth_link_with_redirect] // [START auth_get_redirect_result] - auth.getRedirectResult().then(function(result) { + auth.getRedirectResult().then((result) => { if (result.credential) { // Accounts successfully linked. var credential = result.credential; var user = result.user; // ... } - }).catch(function(error) { + }).catch((error) => { // Handle Errors here. // ... }); // [END auth_get_redirect_result] } -function mergeAccounts() { - // This is just a dummy variable for sample purposes, the other - // snippets demonstrate how to get a real credential. - var newCredential = new firebase.auth.AuthCredential(); - +function mergeAccounts(newCredential) { // [START auth_merge_accounts] // The implementation of how you store your user data depends on your application var repo = new MyUserDataRepo(); @@ -128,7 +118,7 @@ function mergeAccounts() { repo.delete(prevUser); // Sign in user with the account you want to link to - auth.signInWithCredential(newCredential).then(function(result) { + auth.signInWithCredential(newCredential).then((result) => { console.log("Sign In Success", result); var currentUser = result.user; var currentUserData = repo.get(currentUser); @@ -138,19 +128,18 @@ function mergeAccounts() { var mergedData = repo.merge(prevUserData, currentUserData); return prevUser.linkWithCredential(result.credential) - .then(function(linkResult) { + .then((linkResult) => { // Sign in with the newly linked credential return auth.signInWithCredential(linkResult.credential); }) - .then(function(signInResult) { + .then((signInResult) => { // Save the merged data to the new user repo.set(signInResult.user, mergedData); }); - }).catch(function(error) { + }).catch((error) => { // If there are errors we want to undo the data merge/deletion console.log("Sign In Error", error); repo.set(prevUser, prevUserData); - repo.set(currentUser, currentUserData); }); // [END auth_merge_accounts] } @@ -164,16 +153,72 @@ function makeEmailCredential() { // [END auth_make_email_credential] } -function unlink() { +function unlink(providerId) { var user = auth.currentUser; // [START auth_unlink_provider] - user.unlink(providerId).then(function() { + user.unlink(providerId).then(() => { // Auth provider unlinked from account // ... - }).catch(function(error) { + }).catch((error) => { // An error happened // ... }); // [END auth_unlink_provider] -} \ No newline at end of file +} + +function accountExistsPopup(facebookProvider, goToApp, promptUserForPassword, promptUserForSignInMethod, getProviderForProviderId) { + // [START account_exists_popup] + // User tries to sign in with Facebook. + auth.signInWithPopup(facebookProvider).catch((error) => { + // User's email already exists. + if (error.code === 'auth/account-exists-with-different-credential') { + // The pending Facebook credential. + const pendingCred = error.credential; + // The provider account's email address. + const email = error.email; + + // Present the user with a list of providers they might have + // used to create the original account. + // Then, ask the user to sign in with the existing provider. + const method = promptUserForSignInMethod(); + + if (method === 'password') { + // TODO: Ask the user for their password. + // In real scenario, you should handle this asynchronously. + const password = promptUserForPassword(); + auth.signInWithEmailAndPassword(email, password).then((result) => { + return result.user.linkWithCredential(pendingCred); + }).then(() => { + // Facebook account successfully linked to the existing user. + goToApp(); + }); + return; + } + + // All other cases are external providers. + // Construct provider object for that provider. + // TODO: Implement getProviderForProviderId. + const provider = getProviderForProviderId(method); + // At this point, you should let the user know that they already have an + // account with a different provider, and validate they want to sign in + // with the new provider. + // Note: Browsers usually block popups triggered asynchronously, so in + // real app, you should ask the user to click on a "Continue" button + // that will trigger signInWithPopup(). + auth.signInWithPopup(provider).then((result) => { + // Note: Identity Platform doesn't control the provider's sign-in + // flow, so it's possible for the user to sign in with an account + // with a different email from the first one. + + // Link the Facebook credential. We have access to the pending + // credential, so we can directly call the link method. + result.user.linkWithCredential(pendingCred).then((userCred) => { + // Success. + goToApp(); + }); + }); + } +}); +// [END account_exists_popup] +} diff --git a/auth/manage.js b/auth/manage.js new file mode 100644 index 00000000..0e1f0e7b --- /dev/null +++ b/auth/manage.js @@ -0,0 +1,151 @@ +// These samples are intended for Web so this import would normally be +// done in HTML however using modules here is more convenient for +// ensuring sample correctness offline. +import firebase from "firebase/app"; +import "firebase/auth"; + +function getUserProfile() { + // [START auth_get_user_profile] + const user = firebase.auth().currentUser; + if (user !== null) { + // The user object has basic properties such as display name, email, etc. + const displayName = user.displayName; + const email = user.email; + const photoURL = user.photoURL; + const emailVerified = user.emailVerified; + + // The user's ID, unique to the Firebase project. Do NOT use + // this value to authenticate with your backend server, if + // you have one. Use User.getIdToken() instead. + const uid = user.uid; + } + // [END auth_get_user_profile] +} + +function getUserProfileProvider() { + // [START auth_get_user_profile_provider] + const user = firebase.auth().currentUser; + + if (user !== null) { + user.providerData.forEach((profile) => { + console.log("Sign-in provider: " + profile.providerId); + console.log(" Provider-specific UID: " + profile.uid); + console.log(" Name: " + profile.displayName); + console.log(" Email: " + profile.email); + console.log(" Photo URL: " + profile.photoURL); + }); + } + // [END auth_get_user_profile_provider] +} + +function updateUserProfile() { + // [START auth_update_user_profile] + const user = firebase.auth().currentUser; + + user.updateProfile({ + displayName: "Jane Q. User", + photoURL: "https://example.com/jane-q-user/profile.jpg" + }).then(() => { + // Update successful + // ... + }).catch((error) => { + // An error occurred + // ... + }); + // [END auth_update_user_profile] +} + +function updateUserEmail() { + // [START auth_update_user_email] + const user = firebase.auth().currentUser; + + user.updateEmail("user@example.com").then(() => { + // Update successful + // ... + }).catch((error) => { + // An error occurred + // ... + }); + // [END auth_update_user_email] +} + +function sendEmailVerification() { + // [START send_email_verification] + const user = firebase.auth().currentUser; + + user.sendEmailVerification().then(() => { + // Email sent. + }).catch((error) => { + // An error ocurred + // ... + }); + // [END send_email_verification] +} + +function updatePassword() { + function getASecureRandomPassword() { + return "correcthorsebatterystaple"; + } + + // [START auth_update_password] + const user = firebase.auth().currentUser; + const newPassword = getASecureRandomPassword(); + + user.updatePassword(newPassword).then(() => { + // Update successful. + }).catch((error) => { + // An error ocurred + // ... + }); + // [END auth_update_password] +} + +function sendPasswordReset() { + // [START auth_send_password_reset] + const auth = firebase.auth(); + const emailAddress = "user@example.com"; + + auth.sendPasswordResetEmail(emailAddress).then(() => { + // Email sent. + }).catch((error) => { + // An error ocurred + // ... + }); + // [END auth_send_password_reset] +} + +function deleteUser() { + // [START auth_delete_user] + const user = firebase.auth().currentUser; + + user.delete().then(() => { + // User deleted. + }).catch((error) => { + // An error ocurred + // ... + }); + // [END auth_delete_user] +} + +function reauthenticateWithCredential() { + /** + * @returns {object} + */ + function promptForCredentials() { + return {}; + } + + // [START auth_reauth_with_credential] + const user = firebase.auth().currentUser; + + // TODO(you): prompt the user to re-provide their sign-in credentials + const credential = promptForCredentials(); + + user.reauthenticateWithCredential(credential).then(() => { + // User re-authenticated. + }).catch((error) => { + // An error occurred + // ... + }); + // [END auth_reauth_with_credential] +} diff --git a/auth/microsoft-oauth.js b/auth/microsoft-oauth.js new file mode 100644 index 00000000..23289353 --- /dev/null +++ b/auth/microsoft-oauth.js @@ -0,0 +1,121 @@ +// These samples are intended for Web so this import would normally be +// done in HTML however using modules here is more convenient for +// ensuring sample correctness offline. +import firebase from "firebase/app"; +import "firebase/auth"; + +// Docs: https://source.corp.google.com/piper///depot/google3/third_party/devsite/firebase/en/docs/auth/web/microsoft-oauth.md + +function microsoftProvider() { + // [START auth_msft_provider_create] + var provider = new firebase.auth.OAuthProvider('microsoft.com'); + // [END auth_msft_provider_create] + + // [START auth_msft_provider_scopes] + provider.addScope('mail.read'); + provider.addScope('calendars.read'); + // [END auth_msft_provider_scopes] + + // [START auth_msft_provider_params] + provider.setCustomParameters({ + // Force re-consent. + prompt: 'consent', + // Target specific email with login hint. + login_hint: 'user@firstadd.onmicrosoft.com' + }); + // [END auth_msft_provider_params] + + // [START auth_msft_provider_params_tenant] + provider.setCustomParameters({ + // Optional "tenant" parameter in case you are using an Azure AD tenant. + // eg. '8eaef023-2b34-4da1-9baa-8bc8c9d6a490' or 'contoso.onmicrosoft.com' + // or "common" for tenant-independent tokens. + // The default value is "common". + tenant: 'TENANT_ID' + }); + // [END auth_msft_provider_params_tenant] +} + +function msftSignInPopup(provider) { + // [START auth_msft_signin_popup] + firebase.auth().signInWithPopup(provider) + .then((result) => { + // IdP data available in result.additionalUserInfo.profile. + // ... + + /** @type {firebase.auth.OAuthCredential} */ + var credential = result.credential; + + // OAuth access and id tokens can also be retrieved: + var accessToken = credential.accessToken; + var idToken = credential.idToken; + }) + .catch((error) => { + // Handle error. + }); + // [END auth_msft_signin_popup] +} + +function msftSignInRedirect(provider) { + // [START auth_msft_signin_redirect] + firebase.auth().signInWithRedirect(provider); + // [END auth_msft_signin_redirect] +} + +function msftSignInRedirectResult() { + // [START auth_msft_signin_redirect_result] + firebase.auth().getRedirectResult() + .then((result) => { + // IdP data available in result.additionalUserInfo.profile. + // ... + + /** @type {firebase.auth.OAuthCredential} */ + var credential = result.credential; + + // OAuth access and id tokens can also be retrieved: + var accessToken = credential.accessToken; + var idToken = credential.idToken; + }) + .catch((error) => { + // Handle error. + }); + // [END auth_msft_signin_redirect_result] +} + +function msftLinkWithPopup() { + // [START auth_msft_link_popup] + var provider = new firebase.auth.OAuthProvider('microsoft.com'); + firebase.auth().currentUser.linkWithPopup(provider) + .then((result) => { + // Microsoft credential is linked to the current user. + // IdP data available in result.additionalUserInfo.profile. + // OAuth access token can also be retrieved: + // result.credential.accessToken + // OAuth ID token can also be retrieved: + // result.credential.idToken + }) + .catch((error) => { + // Handle error. + }); + // [END auth_msft_link_popup] +} + +function msftReauthPopup() { + // [START auth_msft_reauth_popup] + var provider = new firebase.auth.OAuthProvider('microsoft.com'); + firebase.auth().currentUser.reauthenticateWithPopup(provider) + .then((result) => { + // User is re-authenticated with fresh tokens minted and + // should be able to perform sensitive operations like account + // deletion and email or password update. + // IdP data available in result.additionalUserInfo.profile. + // OAuth access token can also be retrieved: + // result.credential.accessToken + // OAuth ID token can also be retrieved: + // result.credential.idToken + }) + .catch((error) => { + // Handle error. + }); + // [END auth_msft_reauth_popup] +} diff --git a/auth/multi-tenancy.js b/auth/multi-tenancy.js new file mode 100644 index 00000000..8b31e145 --- /dev/null +++ b/auth/multi-tenancy.js @@ -0,0 +1,312 @@ +// These samples are intended for Web so this import would normally be +// done in HTML however using modules here is more convenient for +// ensuring sample correctness offline. +import firebase from "firebase/app"; +import "firebase/auth"; + +function setTenant() { + // [START multitenant_set_tenant] + const tenantId = "TENANT_ID1"; + firebase.auth().tenantId = tenantId; + // [END multitenant_set_tenant] +} + +function switchTenantSingleAuth() { + // [START multitenant_switch_tenant] + // One Auth instance + // Switch to tenant1 + firebase.auth().tenantId = "TENANT_ID1"; + // Switch to tenant2 + firebase.auth().tenantId = "TENANT_ID2"; + // Switch back to project level IdPs + firebase.auth().tenantId = null; + // [END multitenant_switch_tenant] +} + +function switchTenantMultiAuth(config) { + // [START multitenant_switch_tenant_multiinstance] + // Multiple Auth instances + firebase.initializeApp(config, 'app1_for_tenantId1'); + firebase.initializeApp(config, 'app2_for_tenantId2'); + + const auth1 = firebase.app('app1').auth(); + const auth2 = firebase.app('app2').auth(); + + auth1.tenantId = "TENANT_ID1"; + auth2.tenantId = "TENANT_ID2"; + // [END multitenant_switch_tenant_multiinstance] +} + +function passwordSignInWithTenantDemo(email, password) { + // [START multitenant_signin_password_demo] + // Switch to TENANT_ID1 + firebase.auth().tenantId = 'TENANT_ID1'; + + // Sign in with tenant + firebase.auth().signInWithEmailAndPassword(email, password) + .then((result) => { + const user = result.user; + // user.tenantId is set to 'TENANT_ID1'. + // Switch to 'TENANT_ID2'. + firebase.auth().tenantId = 'TENANT_ID2'; + // firebase.auth().currentUser still point to the user. + // firebase.auth().currentUser.tenantId is 'TENANT_ID1'. + }); + + // You could also get the current user from Auth state observer. + firebase.auth().onAuthStateChanged((user) => { + if (user) { + // User is signed in. + // user.tenantId is set to 'TENANT_ID1'. + } else { + // No user is signed in. + } + }); + // [END multitenant_signin_password_demo] +} + +function signUpWithTenant(email, password) { + // [START multitenant_signup_password] + firebase.auth().tenantId = 'TENANT_ID'; + + firebase.auth().createUserWithEmailAndPassword(email, password) + .then((result) => { + // result.user.tenantId is 'TENANT_ID'. + }).catch((error) => { + // Handle error. + }); + // [END multitenant_signup_password] +} + + +function passwordSignInWithTenant(email, password) { + // [START multitenant_signin_password] + firebase.auth().tenantId = 'TENANT_ID'; + + firebase.auth().signInWithEmailAndPassword(email, password) + .then((result) => { + // result.user.tenantId is 'TENANT_ID'. + }).catch((error) => { + // Handle error. + }); + // [END multitenant_signin_password] +} + +function samlSignInPopupTenant(provider) { + // [START multitenant_signin_saml_popup] + // Switch to TENANT_ID1. + firebase.auth().tenantId = 'TENANT_ID1'; + + // Sign-in with popup. + firebase.auth().signInWithPopup(provider) + .then((result) => { + // User is signed in. + // tenant ID is available in result.user.tenantId. + // Identity provider data is available in result.additionalUserInfo.profile. + }) + .catch((error) => { + // Handle error. + }); + // [END multitenant_signin_saml_popup] +} + +function samlSignInRedirectTenant(provider) { + // [START multitenant_signin_saml_redirect] + // Switch to TENANT_ID1. + firebase.auth().tenantId = 'TENANT_ID1'; + + // Sign-in with redirect. + firebase.auth().signInWithRedirect(provider); + + // After the user completes sign-in and returns to the app, you can get + // the sign-in result by calling getRedirectResult. However, if they sign out + // and sign in again with an IdP, no tenant is used. + firebase.auth().getRedirectResult() + .then((result) => { + // User is signed in. + // The tenant ID available in result.user.tenantId. + // Identity provider data is available in result.additionalUserInfo.profile. + }) + .catch((error) => { + // Handle error. + }); + // [END multitenant_signin_saml_redirect] +} + +function sendSignInLinkToEmailTenant(email, actionCodeSettings) { + // [START multitenant_send_emaillink] + // Switch to TENANT_ID1 + firebase.auth().tenantId = 'TENANT_ID1'; + + firebase.auth().sendSignInLinkToEmail(email, actionCodeSettings) + .then(() => { + // The link was successfully sent. Inform the user. + // Save the email locally so you don't need to ask the user for it again + // if they open the link on the same device. + window.localStorage.setItem('emailForSignIn', email); + }) + .catch((error) => { + // Some error occurred, you can inspect the code: error.code + }); + // [END multitenant_send_emaillink] +} + +function signInWithEmailLinkTenant() { + // [START multitenant_signin_emaillink] + if (firebase.auth().isSignInWithEmailLink(window.location.href)) { + const actionCodeUrl = firebase.auth.ActionCodeURL.parseLink(window.location.href); + if (actionCodeUrl.tenantId) { + firebase.auth().tenantId = actionCodeUrl.tenantId; + } + let email = window.localStorage.getItem('emailForSignIn'); + if (!email) { + // User opened the link on a different device. To prevent session fixation + // attacks, ask the user to provide the associated email again. For example: + email = window.prompt('Please provide your email for confirmation'); + } + firebase.auth().signInWithEmailLink(email, window.location.href) + .then((result) => { + // User is signed in. + // tenant ID available in result.user.tenantId. + }); + } + // [END multitenant_signin_emaillink] +} + +function createCustomTokenTenant(admin, uid) { + // [START multitenant_create_custom_token] + // Ensure you're using a tenant-aware auth instance + const tenantManager = admin.auth().tenantManager(); + const tenantAuth = tenantManager.authForTenant('TENANT_ID1'); + + // Create a custom token in the usual manner + tenantAuth.createCustomToken(uid) + .then((customToken) => { + // Send token back to client + }) + .catch((error) => { + console.log('Error creating custom token:', error); + }); + // [END multitenant_create_custom_token] +} + +function signInWithCustomTokenTenant(token) { + // [START multitenant_signin_custom_token] + firebase.auth().tenantId = 'TENANT_ID1'; + + firebase.auth().signInWithCustomToken(token) + .catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // ... + }); + // [END multitenant_signin_custom_token] +} + +function linkAccountTenant(provider, email, password) { + // [START multitenant_account_linking] + // Switch to TENANT_ID1 + firebase.auth().tenantId = 'TENANT_ID1'; + + // Sign-in with popup + firebase.auth().signInWithPopup(provider) + .then((result) => { + // Existing user with e.g. SAML provider. + const user = result.user; + const emailCredential = + firebase.auth.EmailAuthProvider.credential(email, password); + return user.linkWithCredential(emailCredential); + }) + .then((linkResult) => { + // The user can sign in with both SAML and email/password now. + }); + // [END multitenant_account_linking] +} + +function accountExistsPopupTenant(samlProvider, googleProvider, goToApp) { + // [START multitenant_account_exists_popup] + // Step 1. + // User tries to sign in to the SAML provider in that tenant. + firebase.auth().tenantId = 'TENANT_ID'; + firebase.auth().signInWithPopup(samlProvider) + .catch((error) => { + // An error happened. + if (error.code === 'auth/account-exists-with-different-credential') { + // Step 2. + // User's email already exists. + // The pending SAML credential. + const pendingCred = error.credential; + // The credential's tenantId if needed: error.tenantId + // The provider account's email address. + const email = error.email; + // Get sign-in methods for this email. + firebase.auth().fetchSignInMethodsForEmail(email) + .then((methods) => { + // Step 3. + // Ask the user to sign in with existing Google account. + if (methods[0] == 'google.com') { + firebase.auth().signInWithPopup(googleProvider) + .then((result) => { + // Step 4 + // Link the SAML AuthCredential to the existing user. + result.user.linkWithCredential(pendingCred) + .then((linkResult) => { + // SAML account successfully linked to the existing + // user. + goToApp(); + }); + }); + } + }); + } + }); + // [END multitenant_account_exists_popup] +} + +function accountExistsRedirectTenant(samlProvider, googleProvider, goToApp) { + // [START multitenant_account_exists_redirect] + // Step 1. + // User tries to sign in to SAML provider. + firebase.auth().tenantId = 'TENANT_ID'; + firebase.auth().signInWithRedirect(samlProvider); + var pendingCred; + // Redirect back from SAML IDP. auth.tenantId is null after redirecting. + firebase.auth().getRedirectResult().catch((error) => { + if (error.code === 'auth/account-exists-with-different-credential') { + // Step 2. + // User's email already exists. + const tenantId = error.tenantId; + // The pending SAML credential. + pendingCred = error.credential; + // The provider account's email address. + const email = error.email; + // Need to set the tenant ID again as the page was reloaded and the + // previous setting was reset. + firebase.auth().tenantId = tenantId; + // Get sign-in methods for this email. + firebase.auth().fetchSignInMethodsForEmail(email) + .then((methods) => { + // Step 3. + // Ask the user to sign in with existing Google account. + if (methods[0] == 'google.com') { + firebase.auth().signInWithRedirect(googleProvider); + } + }); + } + }); + + // Redirect back from Google. auth.tenantId is null after redirecting. + firebase.auth().getRedirectResult().then((result) => { + // Step 4 + // Link the SAML AuthCredential to the existing user. + // result.user.tenantId is 'TENANT_ID'. + result.user.linkWithCredential(pendingCred) + .then((linkResult) => { + // SAML account successfully linked to the existing + // user. + goToApp(); + }); + }); + // [END multitenant_account_exists_redirect] +} \ No newline at end of file diff --git a/auth/oidc.js b/auth/oidc.js new file mode 100644 index 00000000..e3a3e01e --- /dev/null +++ b/auth/oidc.js @@ -0,0 +1,68 @@ +// These samples are intended for Web so this import would normally be +// done in HTML however using modules here is more convenient for +// ensuring sample correctness offline. +import firebase from "firebase/app"; +import "firebase/auth"; + +function oidcProvider() { + // [START auth_oidc_provider_create] + const provider = new firebase.auth.OAuthProvider('oidc.myProvider'); + // [END auth_oidc_provider_create] +} + +function oidcSignInPopup(provider) { + // [START auth_oidc_signin_popup] + firebase.auth().signInWithPopup(provider) + .then((result) => { + // User is signed in. + // result.credential is a firebase.auth().OAuthCredential object. + // result.credential.providerId is equal to 'oidc.myProvider'. + // result.credential.idToken is the OIDC provider's ID token. + }) + .catch((error) => { + // Handle error. + }); + // [END auth_oidc_signin_popup] +} + +function oidcSignInRedirect(provider) { + // [START auth_oidc_signin_redirect] + firebase.auth().signInWithRedirect(provider).catch((error) => { + // Handle error. + }); + // [END auth_oidc_signin_redirect] +} + +function oidcSignInRedirectResult(provider) { + // [START auth_oidc_signin_redirect_result] + // On return. + firebase.auth().getRedirectResult() + .then((result) => { + // User is signed in. + // result.credential is a firebase.auth().OAuthCredential object. + // result.credential.providerId is equal to 'oidc.myProvider'. + // result.credential.idToken is the OIDC provider's ID token. + }) + .catch((error) => { + // Handle / display error. + // ... + }); + // [END auth_oidc_signin_redirect_result] +} + +function oidcDirectSignIn(provider, oidcIdToken) { + // [START auth_oidc_direct_sign_in] + const credential = provider.credential(oidcIdToken, null); + + firebase.auth().signInWithCredential(credential) + .then((result) => { + // User is signed in. + // User now has a odic.myProvider UserInfo in providerData. + }) + .catch((error) => { + // Handle / display error. + // ... + }); + // [END auth_oidc_direct_sign_in] +} + diff --git a/auth/package.json b/auth/package.json index 3dd4e43d..b4b13f01 100644 --- a/auth/package.json +++ b/auth/package.json @@ -1,11 +1,13 @@ { "name": "auth", "version": "1.0.0", - "description": "", - "scripts": {}, - "author": "", + "scripts": { + "compile": "cp ../tsconfig.json.template ./tsconfig.json && tsc" + }, "license": "Apache 2.0", "dependencies": { - "firebase": "^7.12.0" + "firebase": "^8.10.0", + "firebase-admin": "^12.0.0", + "firebaseui": "^5.0.0" } } diff --git a/auth/phone-auth.js b/auth/phone-auth.js new file mode 100644 index 00000000..1b9549a9 --- /dev/null +++ b/auth/phone-auth.js @@ -0,0 +1,110 @@ +// These samples are intended for Web so this import would normally be +// done in HTML however using modules here is more convenient for +// ensuring sample correctness offline. +import firebase from "firebase/app"; +import "firebase/auth"; + +// Mask the global 'window' for this snippet file +const window = { + recaptchaVerifier: undefined +}; + +function recaptchaVerifierInvisible() { + function onSignInSubmit() { + // TODO(you): Implement + } + + // [START auth_phone_recaptcha_verifier_invisible] + window.recaptchaVerifier = new firebase.auth.RecaptchaVerifier('sign-in-button', { + 'size': 'invisible', + 'callback': (response) => { + // reCAPTCHA solved, allow signInWithPhoneNumber. + onSignInSubmit(); + } + }); + // [END auth_phone_recaptcha_verifier_invisible] +} + +function recaptchaVerifierVisible() { + // [START auth_phone_recaptcha_verifier_visible] + window.recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container', { + 'size': 'normal', + 'callback': (response) => { + // reCAPTCHA solved, allow signInWithPhoneNumber. + // ... + }, + 'expired-callback': () => { + // Response expired. Ask user to solve reCAPTCHA again. + // ... + } + }); + // [END auth_phone_recaptcha_verifier_visible] +} + +function recaptchaVerifierSimple() { + // [START auth_phone_recaptcha_verifier_simple] + window.recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container'); + // [END auth_phone_recaptcha_verifier_simple] +} + +function recaptchaRender() { + /** @type {firebase.auth.RecaptchaVerifier} */ + const recaptchaVerifier = window.recaptchaVerifier; + + // [START auth_phone_recaptcha_render] + recaptchaVerifier.render().then((widgetId) => { + window.recaptchaWidgetId = widgetId; + }); + // [END auth_phone_recaptcha_render] +} + +function phoneSignIn() { + function getPhoneNumberFromUserInput() { + return "+15558675309"; + } + + // [START auth_phone_signin] + const phoneNumber = getPhoneNumberFromUserInput(); + const appVerifier = window.recaptchaVerifier; + firebase.auth().signInWithPhoneNumber(phoneNumber, appVerifier) + .then((confirmationResult) => { + // SMS sent. Prompt user to type the code from the message, then sign the + // user in with confirmationResult.confirm(code). + window.confirmationResult = confirmationResult; + // ... + }).catch((error) => { + // Error; SMS not sent + // ... + }); + // [END auth_phone_signin] +} + +function verifyCode() { + function getCodeFromUserInput() { + return "1234"; + } + + /** @type {firebase.auth.ConfirmationResult} */ + const confirmationResult = undefined; + + // [START auth_phone_verify_code] + const code = getCodeFromUserInput(); + confirmationResult.confirm(code).then((result) => { + // User signed in successfully. + const user = result.user; + // ... + }).catch((error) => { + // User couldn't sign in (bad verification code?) + // ... + }); + // [END auth_phone_verify_code] +} + +function getRecaptchaResponse() { + const recaptchaWidgetId = "..."; + const grecaptcha = {}; + + // [START auth_get_recaptcha_response] + const recaptchaResponse = grecaptcha.getResponse(recaptchaWidgetId); + // [END auth_get_recaptcha_response] +} diff --git a/auth/saml.js b/auth/saml.js new file mode 100644 index 00000000..e3a99cde --- /dev/null +++ b/auth/saml.js @@ -0,0 +1,53 @@ +// These samples are intended for Web so this import would normally be +// done in HTML however using modules here is more convenient for +// ensuring sample correctness offline. +import firebase from "firebase/app"; +import "firebase/auth"; + +function samlProvider() { + // [START auth_saml_provider_create] + const provider = new firebase.auth.SAMLAuthProvider('saml.myProvider'); + // [END auth_saml_provider_create] +} + +function samlSignInPopup(provider) { + // [START auth_saml_signin_popup] + firebase.auth().signInWithPopup(provider) + .then((result) => { + // User is signed in. + // Identity provider data available in result.additionalUserInfo.profile, + // or from the user's ID token obtained from result.user.getIdToken() + // as an object in the firebase.sign_in_attributes custom claim + // This is also available from result.user.getIdTokenResult() + // idTokenResult.claims.firebase.sign_in_attributes. + }) + .catch((error) => { + // Handle / display error. + // ... + }); + // [END auth_saml_signin_popup] +} + +function samlSignInRedirect(provider) { + // [START auth_saml_signin_redirect] + firebase.auth().signInWithRedirect(provider); + // [END auth_saml_signin_redirect] +} + +function samlSignInRedirectResult(provider) { + // [START auth_saml_signin_redirect_result] + firebase.auth().getRedirectResult() + .then((result) => { + // User is signed in. + // Provider data available in result.additionalUserInfo.profile, + // or from the user's ID token obtained from result.user.getIdToken() + // as an object in the firebase.sign_in_attributes custom claim + // This is also available from result.user.getIdTokenResult() + // idTokenResult.claims.firebase.sign_in_attributes. + }).catch((error) => { + // Handle / display error. + // ... + }); + // [END auth_saml_signin_redirect_result] +} + diff --git a/auth/service-worker-sessions.js b/auth/service-worker-sessions.js new file mode 100644 index 00000000..05791463 --- /dev/null +++ b/auth/service-worker-sessions.js @@ -0,0 +1,207 @@ +// These samples are intended for Web so this import would normally be +// done in HTML however using modules here is more convenient for +// ensuring sample correctness offline. +import firebase from "firebase/app"; +import "firebase/auth"; + +// Docs: https://source.corp.google.com/piper///depot/google3/third_party/devsite/firebase/en/docs/auth/web/service-worker-sessions.md + +function svcGetIdToken() { + // [START auth_svc_get_idtoken] + firebase.auth().currentUser.getIdToken() + .then((idToken) => { + // idToken can be passed back to server. + }) + .catch((error) => { + // Error occurred. + }); + // [END auth_svc_get_idtoken] +} + +function svcSubscribe(config) { + // [START auth_svc_subscribe] + // Initialize the Firebase app in the service worker script. + firebase.initializeApp(config); + + /** + * Returns a promise that resolves with an ID token if available. + * @return {!Promise} The promise that resolves with an ID token if + * available. Otherwise, the promise resolves with null. + */ + const getIdToken = () => { + return new Promise((resolve, reject) => { + const unsubscribe = firebase.auth().onAuthStateChanged((user) => { + unsubscribe(); + if (user) { + user.getIdToken().then((idToken) => { + resolve(idToken); + }, (error) => { + resolve(null); + }); + } else { + resolve(null); + } + }); + }); + }; + // [END auth_svc_subscribe] +} + +function svcIntercept() { + // See above + function getIdToken() { + return Promise.resolve("id-token"); + } + + // [START auth_svc_intercept] + const getOriginFromUrl = (url) => { + // https://stackoverflow.com/questions/1420881/how-to-extract-base-url-from-a-string-in-javascript + const pathArray = url.split('/'); + const protocol = pathArray[0]; + const host = pathArray[2]; + return protocol + '//' + host; + }; + + // Get underlying body if available. Works for text and json bodies. + const getBodyContent = (req) => { + return Promise.resolve().then(() => { + if (req.method !== 'GET') { + if (req.headers.get('Content-Type').indexOf('json') !== -1) { + return req.json() + .then((json) => { + return JSON.stringify(json); + }); + } else { + return req.text(); + } + } + }).catch((error) => { + // Ignore error. + }); + }; + + self.addEventListener('fetch', (event) => { + /** @type {FetchEvent} */ + const evt = event; + + const requestProcessor = (idToken) => { + let req = evt.request; + let processRequestPromise = Promise.resolve(); + // For same origin https requests, append idToken to header. + if (self.location.origin == getOriginFromUrl(evt.request.url) && + (self.location.protocol == 'https:' || + self.location.hostname == 'localhost') && + idToken) { + // Clone headers as request headers are immutable. + const headers = new Headers(); + req.headers.forEach((val, key) => { + headers.append(key, val); + }); + // Add ID token to header. + headers.append('Authorization', 'Bearer ' + idToken); + processRequestPromise = getBodyContent(req).then((body) => { + try { + req = new Request(req.url, { + method: req.method, + headers: headers, + mode: 'same-origin', + credentials: req.credentials, + cache: req.cache, + redirect: req.redirect, + referrer: req.referrer, + body, + // bodyUsed: req.bodyUsed, + // context: req.context + }); + } catch (e) { + // This will fail for CORS requests. We just continue with the + // fetch caching logic below and do not pass the ID token. + } + }); + } + return processRequestPromise.then(() => { + return fetch(req); + }); + }; + // Fetch the resource after checking for the ID token. + // This can also be integrated with existing logic to serve cached files + // in offline mode. + evt.respondWith(getIdToken().then(requestProcessor, requestProcessor)); + }); + // [END auth_svc_intercept] +} + +function svcListenActivate(clients) { + // [START auth_svc_listen_activate] + self.addEventListener('activate', (event) => { + event.waitUntil(clients.claim()); + }); + // [END auth_svc_listen_activate] +} + +function svcRegister() { + // [START auth_svc_register] + // Install servicerWorker if supported on sign-in/sign-up page. + if ('serviceWorker' in navigator) { + navigator.serviceWorker.register('/service-worker.js', {scope: '/'}); + } + // [END auth_svc_register] +} + +function svcSignInEmail(email, password) { + // [START auth_svc_sign_in_email] + // Sign in screen. + firebase.auth().signInWithEmailAndPassword(email, password) + .then((result) => { + // Redirect to profile page after sign-in. The service worker will detect + // this and append the ID token to the header. + window.location.assign('/profile'); + }) + .catch((error) => { + // Error occurred. + }); + // [END auth_svc_sign_in_email] +} + +function svcRedirectAdmin() { + const app = { use: (a) => {} }; + + // [START auth_svc_admin] + // Server side code. + const admin = require('firebase-admin'); + + // The Firebase Admin SDK is used here to verify the ID token. + admin.initializeApp(); + + function getIdToken(req) { + // Parse the injected ID token from the request header. + const authorizationHeader = req.headers.authorization || ''; + const components = authorizationHeader.split(' '); + return components.length > 1 ? components[1] : ''; + } + + function checkIfSignedIn(url) { + return (req, res, next) => { + if (req.url == url) { + const idToken = getIdToken(req); + // Verify the ID token using the Firebase Admin SDK. + // User already logged in. Redirect to profile page. + admin.auth().verifyIdToken(idToken).then((decodedClaims) => { + // User is authenticated, user claims can be retrieved from + // decodedClaims. + // In this sample code, authenticated users are always redirected to + // the profile page. + res.redirect('/profile'); + }).catch((error) => { + next(); + }); + } else { + next(); + } + }; + } + + // If a user is signed in, redirect to profile page. + app.use(checkIfSignedIn('/')); + // [END auth_svc_admin] +} diff --git a/auth/twitter.js b/auth/twitter.js new file mode 100644 index 00000000..68ed6f35 --- /dev/null +++ b/auth/twitter.js @@ -0,0 +1,87 @@ +// These samples are intended for Web so this import would normally be +// done in HTML however using modules here is more convenient for +// ensuring sample correctness offline. +import firebase from "firebase/app"; +import "firebase/auth"; + +function twitterProvider() { + // [START auth_twitter_provider_create] + var provider = new firebase.auth.TwitterAuthProvider(); + // [END auth_twitter_provider_create] + + // [START auth_twitter_provider_params] + provider.setCustomParameters({ + 'lang': 'es' + }); + // [END auth_twitter_provider_params] +} + +function twitterSignInPopup(provider) { + // [START auth_twitter_signin_popup] + firebase + .auth() + .signInWithPopup(provider) + .then((result) => { + /** @type {firebase.auth.OAuthCredential} */ + var credential = result.credential; + + // This gives you a the Twitter OAuth 1.0 Access Token and Secret. + // You can use these server side with your app's credentials to access the Twitter API. + var token = credential.accessToken; + var secret = credential.secret; + + // The signed-in user info. + var user = result.user; + // IdP data available in result.additionalUserInfo.profile. + // ... + }).catch((error) => { + // Handle Errors here. + var errorCode = error.code; + var errorMessage = error.message; + // The email of the user's account used. + var email = error.email; + // The firebase.auth.AuthCredential type that was used. + var credential = error.credential; + // ... + }); + // [END auth_twitter_signin_popup] +} + +function twitterSignInRedirectResult() { + // [START auth_twitter_signin_redirect_result] + firebase.auth() + .getRedirectResult() + .then((result) => { + if (result.credential) { + /** @type {firebase.auth.OAuthCredential} */ + var credential = result.credential; + + // This gives you a the Twitter OAuth 1.0 Access Token and Secret. + // You can use these server side with your app's credentials to access the Twitter API. + var token = credential.accessToken; + var secret = credential.secret; + // ... + } + + // The signed-in user info. + var user = result.user; + // IdP data available in result.additionalUserInfo.profile. + // ... + }).catch((error) => { + // Handle Errors here. + var errorCode = error.code; + var errorMessage = error.message; + // The email of the user's account used. + var email = error.email; + // The firebase.auth.AuthCredential type that was used. + var credential = error.credential; + // ... + }); + // [END auth_twitter_signin_redirect_result] +} + +function twitterProviderCredential(accessToken, secret) { + // [START auth_twitter_provider_credential] + var credential = firebase.auth.TwitterAuthProvider.credential(accessToken, secret); + // [END auth_twitter_provider_credential] +} \ No newline at end of file diff --git a/auth/yahoo-oauth.js b/auth/yahoo-oauth.js new file mode 100644 index 00000000..92529bf0 --- /dev/null +++ b/auth/yahoo-oauth.js @@ -0,0 +1,114 @@ +// These samples are intended for Web so this import would normally be +// done in HTML however using modules here is more convenient for +// ensuring sample correctness offline. +import firebase from "firebase/app"; +import "firebase/auth"; + +// Docs: https://source.corp.google.com/piper///depot/google3/third_party/devsite/firebase/en/docs/auth/web/yahoo-oauth.md + +function yahooProvider() { + // [START auth_yahoo_provider_create] + var provider = new firebase.auth.OAuthProvider('yahoo.com'); + // [END auth_yahoo_provider_create] + + // [START auth_yahoo_provider_scopes] + // Request access to Yahoo Mail API. + provider.addScope('mail-r'); + // Request read/write access to user contacts. + // This must be preconfigured in the app's API permissions. + provider.addScope('sdct-w'); + // [END auth_yahoo_provider_scopes] + + // [START auth_yahoo_provider_params] + provider.setCustomParameters({ + // Prompt user to re-authenticate to Yahoo. + prompt: 'login', + // Localize to French. + language: 'fr' + }); + // [END auth_yahoo_provider_params] +} + +function yahooSignInPopup(provider) { + // [START auth_yahoo_signin_popup] + firebase.auth().signInWithPopup(provider) + .then((result) => { + // IdP data available in result.additionalUserInfo.profile + // ... + + /** @type {firebase.auth.OAuthCredential} */ + const credential = result.credential; + + // Yahoo OAuth access token and ID token can be retrieved by calling: + var accessToken = credential.accessToken; + var idToken = credential.idToken; + }) + .catch((error) => { + // Handle error. + }); + // [END auth_yahoo_signin_popup] +} + +function yahooSignInRedirect(provider) { + // [START auth_yahoo_signin_redirect] + firebase.auth().signInWithRedirect(provider); + // [END auth_yahoo_signin_redirect] +} + +function yahooSigninRedirectResult() { + // [START auth_yahoo_signin_redirect_result] + firebase.auth().getRedirectResult() + .then((result) => { + // IdP data available in result.additionalUserInfo.profile + // ... + + /** @type {firebase.auth.OAuthCredential} */ + const credential = result.credential; + + // Yahoo OAuth access token and ID token can be retrieved by calling: + var accessToken = credential.accessToken; + var idToken = credential.idToken; + }) + .catch((error) => { + // Handle error. + }); + // [END auth_yahoo_signin_redirect_result] +} + +function yahooLinkPopup() { + // [START auth_yahoo_link_popup] + var provider = new firebase.auth.OAuthProvider('yahoo.com'); + firebase.auth().currentUser.linkWithPopup(provider) + .then((result) => { + // Yahoo credential is linked to the current user. + // IdP data available in result.additionalUserInfo.profile. + // Yahoo OAuth access token can be retrieved by calling: + // result.credential.accessToken + // Yahoo OAuth ID token can be retrieved by calling: + // result.credential.idToken + }) + .catch((error) => { + // Handle error. + }); + // [END auth_yahoo_link_popup] +} + +function yahooReauthPopup() { + // [START auth_yahoo_reauth_popup] + var provider = new firebase.auth.OAuthProvider('yahoo.com'); + firebase.auth().currentUser.reauthenticateWithPopup(provider) + .then((result) => { + // User is re-authenticated with fresh tokens minted and + // should be able to perform sensitive operations like account + // deletion and email or password update. + // IdP data available in result.additionalUserInfo.profile. + // Yahoo OAuth access token can be retrieved by calling: + // result.credential.accessToken + // Yahoo OAuth ID token can be retrieved by calling: + // result.credential.idToken + }) + .catch((error) => { + // Handle error. + }); + // [END auth_yahoo_reauth_popup] +} diff --git a/database-next/emulator-suite.js b/database-next/emulator-suite.js new file mode 100644 index 00000000..f94d3c89 --- /dev/null +++ b/database-next/emulator-suite.js @@ -0,0 +1,24 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +function onDocumentReady() { + // [START rtdb_emulator_connect] + const { getDatabase, connectDatabaseEmulator } = require("firebase/database"); + + const db = getDatabase(); + if (location.hostname === "localhost") { + // Point to the RTDB emulator running on localhost. + connectDatabaseEmulator(db, "127.0.0.1", 9000); + } + // [END rtdb_emulator_connect] +} + +function flushRealtimeDatabase() { + // [START rtdb_emulator_flush] + const { getDatabase, ref, set } = require("firebase/database"); + + // With a database Reference, write null to clear the database. + const db = getDatabase(); + set(ref(db), null); + // [END rtdb_emulator_flush] +} diff --git a/database-next/index.js b/database-next/index.js new file mode 100644 index 00000000..b0a3400e --- /dev/null +++ b/database-next/index.js @@ -0,0 +1,10 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +function getReference() { + // [START rtdb_get_reference] + const { getDatabase } = require("firebase/database"); + + const database = getDatabase(); + // [END rtdb_get_reference] +} diff --git a/database-next/lists-of-data.js b/database-next/lists-of-data.js new file mode 100644 index 00000000..53afbc08 --- /dev/null +++ b/database-next/lists-of-data.js @@ -0,0 +1,93 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +function socialPush() { + // [START rtdb_social_push] + const { getDatabase, ref, push, set } = require("firebase/database"); + + // Create a new post reference with an auto-generated id + const db = getDatabase(); + const postListRef = ref(db, 'posts'); + const newPostRef = push(postListRef); + set(newPostRef, { + // ... + }); + // [END rtdb_social_push] +} + +function socialListenChildren() { + const postElement = document.querySelector("#post"); + const postId = "1234"; + function addCommentElement(el, key, text, author) {} + function setCommentValues(el, key, text, author) {}; + function deleteComment(el, key) {}; + + // [START rtdb_social_listen_children] + const { getDatabase, ref, onChildAdded, onChildChanged, onChildRemoved } = require("firebase/database"); + + const db = getDatabase(); + const commentsRef = ref(db, 'post-comments/' + postId); + onChildAdded(commentsRef, (data) => { + addCommentElement(postElement, data.key, data.val().text, data.val().author); + }); + + onChildChanged(commentsRef, (data) => { + setCommentValues(postElement, data.key, data.val().text, data.val().author); + }); + + onChildRemoved(commentsRef, (data) => { + deleteComment(postElement, data.key); + }); + // [END rtdb_social_listen_children] +} + +function socialListenValue() { + + // [START rtdb_social_listen_value] + const { getDatabase, ref, onValue } = require("firebase/database"); + + const db = getDatabase(); + const dbRef = ref(db, '/a/b/c'); + + onValue(dbRef, (snapshot) => { + snapshot.forEach((childSnapshot) => { + const childKey = childSnapshot.key; + const childData = childSnapshot.val(); + // ... + }); + }, { + onlyOnce: true + }); + // [END rtdb_social_listen_value] +} + +function socialMostStarred() { + // [START rtdb_social_most_starred] + const { getDatabase, ref, query, orderByChild } = require("firebase/database"); + const { getAuth } = require("firebase/auth"); + + const db = getDatabase(); + const auth = getAuth(); + + const myUserId = auth.currentUser.uid; + const topUserPostsRef = query(ref(db, 'user-posts/' + myUserId), orderByChild('starCount')); + // [END rtdb_social_most_starred] +} + +function socialMostViewed() { + // [START rtdb_social_most_viewed] + const { getDatabase, ref, query, orderByChild } = require("firebase/database"); + + const db = getDatabase(); + const mostViewedPosts = query(ref(db, 'posts'), orderByChild('metrics/views')); + // [END rtdb_social_most_viewed] +} + +function socialRecent() { + // [START rtdb_social_recent] + const { getDatabase, ref, query, limitToLast } = require("firebase/database"); + + const db = getDatabase(); + const recentPostsRef = query(ref(db, 'posts'), limitToLast(100)); + // [END rtdb_social_recent] +} diff --git a/database-next/offline.js b/database-next/offline.js new file mode 100644 index 00000000..3dd9e22b --- /dev/null +++ b/database-next/offline.js @@ -0,0 +1,113 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +function onDisconnectSimple() { + // [START rtdb_ondisconnect_simple] + const { getDatabase, ref, onDisconnect } = require("firebase/database"); + + const db = getDatabase(); + const presenceRef = ref(db, "disconnectmessage"); + // Write a string when this client loses connection + onDisconnect(presenceRef).set("I disconnected!"); + // [END rtdb_ondisconnect_simple] +} + +function onDisconnectCallback() { + const { getDatabase, ref, onDisconnect } = require("firebase/database"); + + const db = getDatabase(); + const presenceRef = ref(db, "disconnectmessage"); + + // [START rtdb_ondisconnect_callback] + onDisconnect(presenceRef).remove().catch((err) => { + if (err) { + console.error("could not establish onDisconnect event", err); + } + }); + // [END rtdb_ondisconnect_callback] +} + +function onDisconnectCancel() { + const { getDatabase, ref, onDisconnect } = require("firebase/database"); + + const db = getDatabase(); + const presenceRef = ref(db, "disconnectmessage"); + + // [START rtdb_ondisconnect_cancel] + const onDisconnectRef = onDisconnect(presenceRef); + onDisconnectRef.set("I disconnected"); + // some time later when we change our minds + onDisconnectRef.cancel(); + // [END rtdb_ondisconnect_cancel] +} + +function detectConnectionState() { + // [START rtdb_detect_connection_state] + const { getDatabase, ref, onValue } = require("firebase/database"); + + const db = getDatabase(); + const connectedRef = ref(db, ".info/connected"); + onValue(connectedRef, (snap) => { + if (snap.val() === true) { + console.log("connected"); + } else { + console.log("not connected"); + } + }); + // [END rtdb_detect_connection_state] +} + +function setServerTimestamp() { + // [START rtdb_set_server_timestamp] + const { getDatabase, ref, onDisconnect, serverTimestamp } = require("firebase/database"); + + const db = getDatabase(); + const userLastOnlineRef = ref(db, "users/joe/lastOnline"); + onDisconnect(userLastOnlineRef).set(serverTimestamp()); + // [END rtdb_set_server_timestamp] +} + +function estimateClockSkew() { + // [START rtdb_estimate_clock_skew] + const { getDatabase, ref, onValue } = require("firebase/database"); + + const db = getDatabase(); + const offsetRef = ref(db, ".info/serverTimeOffset"); + onValue(offsetRef, (snap) => { + const offset = snap.val(); + const estimatedServerTimeMs = new Date().getTime() + offset; + }); + // [END rtdb_estimate_clock_skew] +} + +function samplePresenceApp() { + // [START rtdb_sample_presence_app] + const { getDatabase, ref, onValue, push, onDisconnect, set, serverTimestamp } = require("firebase/database"); + + // Since I can connect from multiple devices or browser tabs, we store each connection instance separately + // any time that connectionsRef's value is null (i.e. has no children) I am offline + const db = getDatabase(); + const myConnectionsRef = ref(db, 'users/joe/connections'); + + // stores the timestamp of my last disconnect (the last time I was seen online) + const lastOnlineRef = ref(db, 'users/joe/lastOnline'); + + const connectedRef = ref(db, '.info/connected'); + onValue(connectedRef, (snap) => { + if (snap.val() === true) { + // We're connected (or reconnected)! Do anything here that should happen only if online (or on reconnect) + const con = push(myConnectionsRef); + + // When I disconnect, remove this device + onDisconnect(con).remove(); + + // Add this device to my connections list + // this value could contain info about the device or a timestamp too + set(con, true); + + // When I disconnect, update the last time I was seen online + onDisconnect(lastOnlineRef).set(serverTimestamp()); + } + }); + // [END rtdb_sample_presence_app] +} diff --git a/database-next/package.json b/database-next/package.json new file mode 100644 index 00000000..eb16e2d5 --- /dev/null +++ b/database-next/package.json @@ -0,0 +1,11 @@ +{ + "name": "database-next", + "version": "1.0.0", + "scripts": { + "compile": "cp ../tsconfig.json.template ./tsconfig.json && tsc" + }, + "license": "Apache-2.0", + "dependencies": { + "firebase": "^10.0.0" + } +} diff --git a/database-next/read-and-write.js b/database-next/read-and-write.js new file mode 100644 index 00000000..c06c66b4 --- /dev/null +++ b/database-next/read-and-write.js @@ -0,0 +1,189 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +function writeUserData_wrapped() { + // [START rtdb_write_new_user] + const { getDatabase, ref, set } = require("firebase/database"); + + function writeUserData(userId, name, email, imageUrl) { + const db = getDatabase(); + set(ref(db, 'users/' + userId), { + username: name, + email: email, + profile_picture : imageUrl + }); + } + // [END rtdb_write_new_user] +} + + +function writeUserDataWithCompletion(userId, name, email, imageUrl) { + // [START rtdb_write_new_user_completion] + const { getDatabase, ref, set } = require("firebase/database"); + + const db = getDatabase(); + set(ref(db, 'users/' + userId), { + username: name, + email: email, + profile_picture : imageUrl + }) + .then(() => { + // Data saved successfully! + }) + .catch((error) => { + // The write failed... + }); + // [END rtdb_write_new_user_completion] +} + +function socialListenStarCount() { + const postElement = document.querySelector('#post'); + const postId = "1234"; + function updateStarCount(a, b) { + // ... + } + + // [START rtdb_social_listen_star_count] + const { getDatabase, ref, onValue } = require("firebase/database"); + + const db = getDatabase(); + const starCountRef = ref(db, 'posts/' + postId + '/starCount'); + onValue(starCountRef, (snapshot) => { + const data = snapshot.val(); + updateStarCount(postElement, data); + }); + // [END rtdb_social_listen_star_count] +} + +function socialSingleValueRead() { + // [START rtdb_social_single_value_read] + const { getDatabase, ref, onValue } = require("firebase/database"); + const { getAuth } = require("firebase/auth"); + + const db = getDatabase(); + const auth = getAuth(); + + const userId = auth.currentUser.uid; + return onValue(ref(db, '/users/' + userId), (snapshot) => { + const username = (snapshot.val() && snapshot.val().username) || 'Anonymous'; + // ... + }, { + onlyOnce: true + }); + // [END rtdb_social_single_value_read] +} + +function writeNewPost_wrapped() { + // [START rtdb_social_write_fan_out] + const { getDatabase, ref, child, push, update } = require("firebase/database"); + + function writeNewPost(uid, username, picture, title, body) { + const db = getDatabase(); + + // A post entry. + const postData = { + author: username, + uid: uid, + body: body, + title: title, + starCount: 0, + authorPic: picture + }; + + // Get a key for a new Post. + const newPostKey = push(child(ref(db), 'posts')).key; + + // Write the new post's data simultaneously in the posts list and the user's post list. + const updates = {}; + updates['/posts/' + newPostKey] = postData; + updates['/user-posts/' + uid + '/' + newPostKey] = postData; + + return update(ref(db), updates); + } + // [END rtdb_social_write_fan_out] +} + +function socialCompletionCallback() { + const userId = "123"; + const email = "test@example.com"; + const imageUrl = "https://example.com/image.png"; + + // [START rtdb_social_completion_callback] + const { getDatabase, ref, set } = require("firebase/database"); + + const db = getDatabase(); + set(ref(db, 'users/' + userId), { + username: name, + email: email, + profile_picture : imageUrl + }) + .then(() => { + // Data saved successfully! + }) + .catch((error) => { + // The write failed... + }); + // [END rtdb_social_completion_callback] +} + +function toggleStar_wrapped() { + // [START rtdb_social_star_transaction] + const { getDatabase, ref, runTransaction } = require("firebase/database"); + + function toggleStar(uid) { + const db = getDatabase(); + const postRef = ref(db, '/posts/foo-bar-123'); + + runTransaction(postRef, (post) => { + if (post) { + if (post.stars && post.stars[uid]) { + post.starCount--; + post.stars[uid] = null; + } else { + post.starCount++; + if (!post.stars) { + post.stars = {}; + } + post.stars[uid] = true; + } + } + return post; + }); + } + // [END rtdb_social_star_transaction] +} + +/** + * @param {string} uid + * @param {string} key + */ +// [START rtdb_social_star_increment] +function addStar(uid, key) { + const { getDatabase, increment, ref, update } = require("firebase/database"); + const dbRef = ref(getDatabase()); + + const updates = {}; + updates[`posts/${key}/stars/${uid}`] = true; + updates[`posts/${key}/starCount`] = increment(1); + updates[`user-posts/${key}/stars/${uid}`] = true; + updates[`user-posts/${key}/starCount`] = increment(1); + update(dbRef, updates); +} +// [END rtdb_social_star_increment] + +function readOnceWithGet(userId) { + // [START rtdb_read_once_get] + const { getDatabase, ref, child, get } = require("firebase/database"); + + const dbRef = ref(getDatabase()); + get(child(dbRef, `users/${userId}`)).then((snapshot) => { + if (snapshot.exists()) { + console.log(snapshot.val()); + } else { + console.log("No data available"); + } + }).catch((error) => { + console.error(error); + }); + // [END rtdb_read_once_get] +} diff --git a/database-next/sharding.js b/database-next/sharding.js new file mode 100644 index 00000000..509503ef --- /dev/null +++ b/database-next/sharding.js @@ -0,0 +1,31 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +import { initializeApp } from "firebase/app"; + +const firebaseApp = initializeApp({ + apiKey: '### FIREBASE API KEY ###', + appId: '### FIREBASE APP ID ###', + projectId: '### FIREBASE PROJECT ID ###' +}); + +function multipleInstances() { + // [START rtdb_multiple_instances] + const { initializeApp } = require("firebase/app"); + const { getDatabase } = require("firebase/database"); + + const app1 = initializeApp({ + databaseURL: "https://testapp-1234-1.firebaseio.com" + }); + + const app2 = initializeApp({ + databaseURL: "https://testapp-1234-2.firebaseio.com" + }, 'app2'); + + // Get the default database instance for an app1 + const database1 = getDatabase(app1); + + // Get a database instance for app2 + const database2 = getDatabase(app2); + // [END rtdb_multiple_instances] +} diff --git a/database/emulator-suite.js b/database/emulator-suite.js index f2ad3366..8da23e25 100644 --- a/database/emulator-suite.js +++ b/database/emulator-suite.js @@ -1,24 +1,22 @@ -function onDocumentReady(firebase) { +// These samples are intended for Web so this import would normally be +// done in HTML however using modules here is more convenient for +// ensuring sample correctness offline. +import firebase from "firebase/app"; +import "firebase/database"; - //[START rtdb_emulator_connect] +function onDocumentReady() { + // [START rtdb_emulator_connect] + var db = firebase.database(); if (location.hostname === "localhost") { - - var firebaseConfig = { - // Point to the RTDB emulator running on localhost. - // In almost all cases the ns (namespace) is your project ID. - databaseURL: "http://localhost:9000?ns=YOUR_DATABASE_NAMESPACE" - } - - var myApp = firebase.initializeApp(firebaseConfig); - var db = myApp.database(); + // Point to the RTDB emulator running on localhost. + db.useEmulator("127.0.0.1", 9000); } // [END rtdb_emulator_connect] } -function flushRealtimeDatabase(firebase) { - - //[START rtdb_emulator_flush] +function flushRealtimeDatabase() { + // [START rtdb_emulator_flush] // With a database Reference, write null to clear the database. firebase.database().ref().set(null); // [END rtdb_emulator_flush] diff --git a/database/index.js b/database/index.js new file mode 100644 index 00000000..eb13416e --- /dev/null +++ b/database/index.js @@ -0,0 +1,11 @@ +// These samples are intended for Web so this import would normally be +// done in HTML however using modules here is more convenient for +// ensuring sample correctness offline. +import firebase from "firebase/app"; +import "firebase/database"; + +function getReference() { + // [START rtdb_get_reference] + var database = firebase.database(); + // [END rtdb_get_reference] +} diff --git a/database/lists-of-data.js b/database/lists-of-data.js new file mode 100644 index 00000000..8010d10b --- /dev/null +++ b/database/lists-of-data.js @@ -0,0 +1,72 @@ +// These samples are intended for Web so this import would normally be +// done in HTML however using modules here is more convenient for +// ensuring sample correctness offline. +import firebase from "firebase/app"; +import "firebase/database"; + +function socialPush() { + // [START rtdb_social_push] + // Create a new post reference with an auto-generated id + var postListRef = firebase.database().ref('posts'); + var newPostRef = postListRef.push(); + newPostRef.set({ + // ... + }); + // [END rtdb_social_push] +} + +function socialListenChildren() { + const postElement = document.querySelector("#post"); + const postId = "1234"; + function addCommentElement(el, key, text, author) {} + function setCommentValues(el, key, text, author) {}; + function deleteComment(el, key) {}; + + // [START rtdb_social_listen_children] + var commentsRef = firebase.database().ref('post-comments/' + postId); + commentsRef.on('child_added', (data) => { + addCommentElement(postElement, data.key, data.val().text, data.val().author); + }); + + commentsRef.on('child_changed', (data) => { + setCommentValues(postElement, data.key, data.val().text, data.val().author); + }); + + commentsRef.on('child_removed', (data) => { + deleteComment(postElement, data.key); + }); + // [END rtdb_social_listen_children] +} + +function socialListenValue() { + const ref = firebase.database().ref('/a/b/c'); + + // [START rtdb_social_listen_value] + ref.once('value', (snapshot) => { + snapshot.forEach((childSnapshot) => { + var childKey = childSnapshot.key; + var childData = childSnapshot.val(); + // ... + }); + }); + // [END rtdb_social_listen_value] +} + +function socialMostStarred() { + // [START rtdb_social_most_starred] + var myUserId = firebase.auth().currentUser.uid; + var topUserPostsRef = firebase.database().ref('user-posts/' + myUserId).orderByChild('starCount'); + // [END rtdb_social_most_starred] +} + +function socialMostViewed() { + // [START rtdb_social_most_viewed] + var mostViewedPosts = firebase.database().ref('posts').orderByChild('metrics/views'); + // [END rtdb_social_most_viewed] +} + +function socialRecent() { + // [START rtdb_social_recent] + var recentPostsRef = firebase.database().ref('posts').limitToLast(100); + // [END rtdb_social_recent] +} diff --git a/database/offline.js b/database/offline.js new file mode 100644 index 00000000..66df4825 --- /dev/null +++ b/database/offline.js @@ -0,0 +1,95 @@ +// These samples are intended for Web so this import would normally be +// done in HTML however using modules here is more convenient for +// ensuring sample correctness offline. +import firebase from "firebase/app"; +import "firebase/database"; + +function onDisconnectSimple() { + // [START rtdb_ondisconnect_simple] + var presenceRef = firebase.database().ref("disconnectmessage"); + // Write a string when this client loses connection + presenceRef.onDisconnect().set("I disconnected!"); + // [END rtdb_ondisconnect_simple] +} + +function onDisconnectCallback() { + var presenceRef = firebase.database().ref("disconnectmessage"); + + // [START rtdb_ondisconnect_callback] + presenceRef.onDisconnect().remove((err) => { + if (err) { + console.error("could not establish onDisconnect event", err); + } + }); + // [END rtdb_ondisconnect_callback] +} + +function onDisconnectCancel() { + var presenceRef = firebase.database().ref("disconnectmessage"); + + // [START rtdb_ondisconnect_cancel] + var onDisconnectRef = presenceRef.onDisconnect(); + onDisconnectRef.set("I disconnected"); + // some time later when we change our minds + onDisconnectRef.cancel(); + // [END rtdb_ondisconnect_cancel] +} + +function detectConnectionState() { + // [START rtdb_detect_connection_state] + var connectedRef = firebase.database().ref(".info/connected"); + connectedRef.on("value", (snap) => { + if (snap.val() === true) { + console.log("connected"); + } else { + console.log("not connected"); + } + }); + // [END rtdb_detect_connection_state] +} + +function setServerTimestamp() { + // [START rtdb_set_server_timestamp] + var userLastOnlineRef = firebase.database().ref("users/joe/lastOnline"); + userLastOnlineRef.onDisconnect().set(firebase.database.ServerValue.TIMESTAMP); + // [END rtdb_set_server_timestamp] +} + +function estimateClockSkew() { + // [START rtdb_estimate_clock_skew] + var offsetRef = firebase.database().ref(".info/serverTimeOffset"); + offsetRef.on("value", (snap) => { + var offset = snap.val(); + var estimatedServerTimeMs = new Date().getTime() + offset; + }); + // [END rtdb_estimate_clock_skew] +} + +function samplePresenceApp() { + // [START rtdb_sample_presence_app] + // Since I can connect from multiple devices or browser tabs, we store each connection instance separately + // any time that connectionsRef's value is null (i.e. has no children) I am offline + var myConnectionsRef = firebase.database().ref('users/joe/connections'); + + // stores the timestamp of my last disconnect (the last time I was seen online) + var lastOnlineRef = firebase.database().ref('users/joe/lastOnline'); + + var connectedRef = firebase.database().ref('.info/connected'); + connectedRef.on('value', (snap) => { + if (snap.val() === true) { + // We're connected (or reconnected)! Do anything here that should happen only if online (or on reconnect) + var con = myConnectionsRef.push(); + + // When I disconnect, remove this device + con.onDisconnect().remove(); + + // Add this device to my connections list + // this value could contain info about the device or a timestamp too + con.set(true); + + // When I disconnect, update the last time I was seen online + lastOnlineRef.onDisconnect().set(firebase.database.ServerValue.TIMESTAMP); + } + }); + // [END rtdb_sample_presence_app] +} diff --git a/database/package.json b/database/package.json new file mode 100644 index 00000000..ea21b973 --- /dev/null +++ b/database/package.json @@ -0,0 +1,11 @@ +{ + "name": "database", + "version": "1.0.0", + "scripts": { + "compile": "cp ../tsconfig.json.template ./tsconfig.json && tsc" + }, + "license": "Apache 2.0", + "dependencies": { + "firebase": "^8.10.0" + } +} diff --git a/database/read-and-write.js b/database/read-and-write.js index 31df7504..bcae4ccc 100644 --- a/database/read-and-write.js +++ b/database/read-and-write.js @@ -1,3 +1,9 @@ +// These samples are intended for Web so this import would normally be +// done in HTML however using modules here is more convenient for +// ensuring sample correctness offline. +import firebase from "firebase/app"; +import "firebase/database"; + // [START rtdb_write_new_user] function writeUserData(userId, name, email, imageUrl) { firebase.database().ref('users/' + userId).set({ @@ -14,12 +20,136 @@ function writeUserDataWithCompletion(userId, name, email, imageUrl) { username: name, email: email, profile_picture : imageUrl - }, function(error) { + }, (error) => { if (error) { // The write failed... } else { // Data saved successfully! } }); - // [START rtdb_write_new_user_completion] + // [END rtdb_write_new_user_completion] +} + +function socialListenStarCount() { + const postElement = document.querySelector('#post'); + const postId = "1234"; + function updateStarCount(a, b) { + // ... + } + + // [START rtdb_social_listen_star_count] + var starCountRef = firebase.database().ref('posts/' + postId + '/starCount'); + starCountRef.on('value', (snapshot) => { + const data = snapshot.val(); + updateStarCount(postElement, data); + }); + // [END rtdb_social_listen_star_count] +} + +function socialSingleValueRead() { + // [START rtdb_social_single_value_read] + var userId = firebase.auth().currentUser.uid; + return firebase.database().ref('/users/' + userId).once('value').then((snapshot) => { + var username = (snapshot.val() && snapshot.val().username) || 'Anonymous'; + // ... + }); + // [END rtdb_social_single_value_read] +} + +// [START rtdb_social_write_fan_out] +function writeNewPost(uid, username, picture, title, body) { + // A post entry. + var postData = { + author: username, + uid: uid, + body: body, + title: title, + starCount: 0, + authorPic: picture + }; + + // Get a key for a new Post. + var newPostKey = firebase.database().ref().child('posts').push().key; + + // Write the new post's data simultaneously in the posts list and the user's post list. + var updates = {}; + updates['/posts/' + newPostKey] = postData; + updates['/user-posts/' + uid + '/' + newPostKey] = postData; + + return firebase.database().ref().update(updates); +} +// [END rtdb_social_write_fan_out] + +function socialCompletionCallback() { + const userId = "123"; + const email = "test@example.com"; + const imageUrl = "https://example.com/image.png"; + + // [START rtdb_social_completion_callback] + firebase.database().ref('users/' + userId).set({ + username: name, + email: email, + profile_picture : imageUrl + }, (error) => { + if (error) { + // The write failed... + } else { + // Data saved successfully! + } + }); + // [END rtdb_social_completion_callback] +} + +/** + * @param {firebase.database.Reference} postRef + * @param {string} uid + */ +// [START rtdb_social_star_transaction] +function toggleStar(postRef, uid) { + postRef.transaction((post) => { + if (post) { + if (post.stars && post.stars[uid]) { + post.starCount--; + post.stars[uid] = null; + } else { + post.starCount++; + if (!post.stars) { + post.stars = {}; + } + post.stars[uid] = true; + } + } + return post; + }); +} +// [END rtdb_social_star_transaction] + +/** + * @param {string} uid + * @param {string} key + */ +// [START rtdb_social_star_increment] +function addStar(uid, key) { + const updates = {}; + updates[`posts/${key}/stars/${uid}`] = true; + updates[`posts/${key}/starCount`] = firebase.database.ServerValue.increment(1); + updates[`user-posts/${key}/stars/${uid}`] = true; + updates[`user-posts/${key}/starCount`] = firebase.database.ServerValue.increment(1); + firebase.database().ref().update(updates); +} +// [END rtdb_social_star_increment] + +function readOnceWithGet(userId) { + // [START rtdb_read_once_get] + const dbRef = firebase.database().ref(); + dbRef.child("users").child(userId).get().then((snapshot) => { + if (snapshot.exists()) { + console.log(snapshot.val()); + } else { + console.log("No data available"); + } + }).catch((error) => { + console.error(error); + }); + // [END rtdb_read_once_get] } diff --git a/database/sharding.js b/database/sharding.js new file mode 100644 index 00000000..7a828689 --- /dev/null +++ b/database/sharding.js @@ -0,0 +1,23 @@ +// These samples are intended for Web so this import would normally be +// done in HTML however using modules here is more convenient for +// ensuring sample correctness offline. +import firebase from "firebase/app"; +import "firebase/database"; + +function multipleInstances() { + // [START rtdb_multiple_instances] + const app1 = firebase.initializeApp({ + databaseURL: "https://testapp-1234-1.firebaseio.com" + }); + + const app2 = firebase.initializeApp({ + databaseURL: "https://testapp-1234-2.firebaseio.com" + }, 'app2'); + + // Get the default database instance for an app1 + var database1 = firebase.database(); + + // Get a database instance for app2 + var database2 = firebase.database(app2); + // [END rtdb_multiple_instances] +} diff --git a/firebaseapp-next/firebaseapp.js b/firebaseapp-next/firebaseapp.js new file mode 100644 index 00000000..54aa3137 --- /dev/null +++ b/firebaseapp-next/firebaseapp.js @@ -0,0 +1,85 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +function multpleFirebaseApps() { + // [START firebase_options] + const { initializeApp } = require("firebase/app"); + + // The following fields are REQUIRED: + // - Project ID + // - App ID + // - API Key + const secondaryAppConfig = { + projectId: "", + appId: "", + apiKey: "", + // databaseURL: "...", + // storageBucket: "...", + }; + // [END firebase_options] + + // [START firebase_secondary] + // Initialize another app with a different config + const secondaryApp = initializeApp(secondaryAppConfig, "secondary"); + // Access services, such as the Realtime Database + // getDatabase(secondaryApp) + // [END firebase_secondary] +} + +function defaultInitOptions() { + const firebaseConfig = { + // ... + }; + + // [START app_default_init_options] + const { initializeApp } = require("firebase/app"); + const { getStorage } = require("firebase/storage"); + const { getFirestore } = require("firebase/firestore"); + + // Initialize Firebase with a "default" Firebase project + const defaultProject = initializeApp(firebaseConfig); + + console.log(defaultProject.name); // "[DEFAULT]" + + // Option 1: Access Firebase services via the defaultProject variable + let defaultStorage = getStorage(defaultProject); + let defaultFirestore = getFirestore(defaultProject); + + // Option 2: Access Firebase services using shorthand notation + defaultStorage = getStorage(); + defaultFirestore = getFirestore(); + // [END app_default_init_options] +} + +function multiProjectInitOptions() { + const firebaseConfig = { + // ... + }; + + const otherProjectFirebaseConfig = { + // ... + }; + + // [START app_multi_project_init_options] + const { initializeApp, getApp } = require("firebase/app"); + const { getStorage } = require("firebase/storage"); + const { getFirestore } = require("firebase/firestore"); + + // Initialize Firebase with a default Firebase project + initializeApp(firebaseConfig); + + // Initialize Firebase with a second Firebase project + const otherProject = initializeApp(otherProjectFirebaseConfig, "other"); + + console.log(getApp().name); // "[DEFAULT]" + console.log(otherProject.name); // "otherProject" + + // Use the shorthand notation to access the default project's Firebase services + const defaultStorage = getStorage(); + const defaultFirestore = getFirestore(); + + // Use the otherProject variable to access the second project's Firebase services + const otherStorage = getStorage(otherProject); + const otherFirestore = getFirestore(otherProject); + // [END app_multi_project_init_options] +} diff --git a/firebaseapp-next/package.json b/firebaseapp-next/package.json new file mode 100644 index 00000000..13cc74cd --- /dev/null +++ b/firebaseapp-next/package.json @@ -0,0 +1,11 @@ +{ + "name": "firebaseapp-next", + "version": "1.0.0", + "scripts": { + "compile": "cp ../tsconfig.json.template ./tsconfig.json && tsc" + }, + "license": "Apache-2.0", + "dependencies": { + "firebase": "^10.0.0" + } +} diff --git a/firebaseapp/firebaseapp.js b/firebaseapp/firebaseapp.js index 2932104b..e030583f 100644 --- a/firebaseapp/firebaseapp.js +++ b/firebaseapp/firebaseapp.js @@ -1,4 +1,4 @@ -var firebase = require('@firebase/app'); +import firebase from 'firebase/app'; function multpleFirebaseApps() { // [START firebase_options] @@ -6,7 +6,7 @@ function multpleFirebaseApps() { // - Project ID // - App ID // - API Key - var secondaryAppConfig = { + const secondaryAppConfig = { projectId: "", appId: "", apiKey: "", @@ -17,8 +17,58 @@ function multpleFirebaseApps() { // [START firebase_secondary] // Initialize another app with a different config - var secondaryApp = firebase.initializeApp(secondaryAppConfig, "secondary"); + const secondaryApp = firebase.initializeApp(secondaryAppConfig, "secondary"); // Access services, such as the Realtime Database // secondaryApp.database(); // [END firebase_secondary] -} \ No newline at end of file +} + +function defaultInitOptions() { + const firebaseConfig = { + // ... + }; + + // [START app_default_init_options] + // Initialize Firebase with a "default" Firebase project + const defaultProject = firebase.initializeApp(firebaseConfig); + + console.log(defaultProject.name); // "[DEFAULT]" + + // Option 1: Access Firebase services via the defaultProject variable + let defaultStorage = defaultProject.storage(); + let defaultFirestore = defaultProject.firestore(); + + // Option 2: Access Firebase services using shorthand notation + defaultStorage = firebase.storage(); + defaultFirestore = firebase.firestore(); + // [END app_default_init_options] +} + +function multiProjectInitOptions() { + const firebaseConfig = { + // ... + }; + + const otherProjectFirebaseConfig = { + // ... + }; + + // [START app_multi_project_init_options] + // Initialize Firebase with a default Firebase project + firebase.initializeApp(firebaseConfig); + + // Initialize Firebase with a second Firebase project + const otherProject = firebase.initializeApp(otherProjectFirebaseConfig, "other"); + + console.log(firebase.app().name); // "[DEFAULT]" + console.log(otherProject.name); // "otherProject" + + // Use the shorthand notation to access the default project's Firebase services + const defaultStorage = firebase.storage(); + const defaultFirestore = firebase.firestore(); + + // Use the otherProject variable to access the second project's Firebase services + const otherStorage = otherProject.storage(); + const otherFirestore = otherProject.firestore(); + // [END app_multi_project_init_options] +} diff --git a/firebaseapp/package.json b/firebaseapp/package.json index 92552bbe..5226e2a0 100644 --- a/firebaseapp/package.json +++ b/firebaseapp/package.json @@ -1,14 +1,11 @@ { "name": "firebaseapp", "version": "1.0.0", - "description": "", - "main": "firebaseapp.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "compile": "cp ../tsconfig.json.template ./tsconfig.json && tsc" }, - "author": "", "license": "Apache-2.0", "dependencies": { - "firebase": "^7.9.2" + "firebase": "^8.10.0" } } diff --git a/firebaseserverapp-next/firebaseserverapp.js b/firebaseserverapp-next/firebaseserverapp.js new file mode 100644 index 00000000..d0ab338c --- /dev/null +++ b/firebaseserverapp-next/firebaseserverapp.js @@ -0,0 +1,27 @@ +// @ts-nocheck +// [START serverapp_auth] +import { initializeServerApp } from 'firebase/app'; +import { getAuth } from 'firebase/auth'; +import { headers } from 'next/headers'; +import { redirect } from 'next/navigation'; + +export default function MyServerComponent() { + + // Get relevant request headers (in Next.JS) + const authIdToken = headers().get('Authorization')?.split('Bearer ')[1]; + + // Initialize the FirebaseServerApp instance. + const serverApp = initializeServerApp(firebaseConfig, { authIdToken }); + + // Initialize Firebase Authentication using the FirebaseServerApp instance. + const auth = getAuth(serverApp); + + if (auth.currentUser) { + redirect('/profile'); + } + + // ... +} +// [END serverapp_auth] + +const firebaseConfig = {}; diff --git a/firebaseserverapp-next/package.json b/firebaseserverapp-next/package.json new file mode 100644 index 00000000..0836b0b2 --- /dev/null +++ b/firebaseserverapp-next/package.json @@ -0,0 +1,12 @@ +{ + "name": "firebaseserverapp-next", + "version": "1.0.0", + "scripts": { + "compile": "cp ../tsconfig.json.template ./tsconfig.json && tsc" + }, + "license": "Apache-2.0", + "dependencies": { + "firebase": "^10.0.0", + "next": "^14.1.3" + } +} diff --git a/firestore-next/emulator-suite.js b/firestore-next/emulator-suite.js new file mode 100644 index 00000000..edc900c1 --- /dev/null +++ b/firestore-next/emulator-suite.js @@ -0,0 +1,12 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +function onDocumentReady() { + // [START fs_emulator_connect] + const { getFirestore, connectFirestoreEmulator } = require("firebase/firestore"); + + // firebaseApps previously initialized using initializeApp() + const db = getFirestore(); + connectFirestoreEmulator(db, '127.0.0.1', 8080); + // [END fs_emulator_connect] +} diff --git a/firestore-next/package.json b/firestore-next/package.json new file mode 100644 index 00000000..ef3b8708 --- /dev/null +++ b/firestore-next/package.json @@ -0,0 +1,18 @@ +{ + "name": "firestore-next", + "version": "1.0.0", + "scripts": { + "compile": "cp ../tsconfig.json.template ./tsconfig.json && tsc" + }, + "license": "Apache-2.0", + "dependencies": { + "firebase": "^10.0.0", + "geofire-common": "^5.1.0" + }, + "devDependencies": { + "@types/chai": "^4.2.12", + "@types/mocha": "^8.0.3", + "chai": "^4.2.0", + "mocha": "^8.1.3" + } +} diff --git a/firestore-next/test.firestore.js b/firestore-next/test.firestore.js new file mode 100644 index 00000000..969c1654 --- /dev/null +++ b/firestore-next/test.firestore.js @@ -0,0 +1,1239 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +const { expect } = require('chai'); + +// [START city_custom_object] +class City { + constructor (name, state, country ) { + this.name = name; + this.state = state; + this.country = country; + } + toString() { + return this.name + ', ' + this.state + ', ' + this.country; + } +} + +// Firestore data converter +const cityConverter = { + toFirestore: (city) => { + return { + name: city.name, + state: city.state, + country: city.country + }; + }, + fromFirestore: (snapshot, options) => { + const data = snapshot.data(options); + return new City(data.name, data.state, data.country); + } +}; +// [END city_custom_object] + +describe("firestore", () => { + const { Firestore } = require("firebase/firestore"); + + /** @type {Firestore} */ + let db; + let app; + + before(() => { + const { initializeApp } = require("firebase/app"); + const { getFirestore } = require("firebase/firestore"); + + const config = { + apiKey: "AIzaSyCM61mMr_iZnP1DzjT1PMB5vDGxfyWNM64", + authDomain: "firestore-snippets.firebaseapp.com", + projectId: "firestore-snippets" + }; + app = initializeApp(config); + db = getFirestore(app); + }); + + it("should be able to set the cache size", () => { + // [START fs_setup_cache] + const { initializeFirestore, CACHE_SIZE_UNLIMITED } = require("firebase/firestore"); + + const firestoreDb = initializeFirestore(app, { + cacheSizeBytes: CACHE_SIZE_UNLIMITED + }); + // [END fs_setup_cache] + }); + + it("should be initializable with persistence", () => { + const { initializeApp } = require("firebase/app"); + const { getFirestore } = require("firebase/firestore"); + + const app = initializeApp({ + apiKey: '### FIREBASE API KEY ###', + authDomain: '### FIREBASE AUTH DOMAIN ###', + projectId: '### FIREBASE PROJECT ID ###', + } ,"persisted_app"); + + const db = getFirestore(app); + + // [START initialize_persistence] + const { enableIndexedDbPersistence } = require("firebase/firestore"); + + enableIndexedDbPersistence(db) + .catch((err) => { + if (err.code == 'failed-precondition') { + // Multiple tabs open, persistence can only be enabled + // in one tab at a a time. + // ... + } else if (err.code == 'unimplemented') { + // The current browser does not support all of the + // features required to enable persistence + // ... + } + }); + // Subsequent queries will use persistence, if it was enabled successfully + // [END initialize_persistence] + }); + + it("should be able to enable/disable network", async () => { + // [START disable_network] + const { disableNetwork } = require("firebase/firestore"); + + await disableNetwork(db); + console.log("Network disabled!"); + // Do offline actions + // [START_EXCLUDE] + console.log("Network disabled!"); + // [END_EXCLUDE] + // [END disable_network] + + // [START enable_network] + const { enableNetwork } = require("firebase/firestore"); + + await enableNetwork(db); + // Do online actions + // [START_EXCLUDE] + console.log("Network enabled!"); + // [END_EXCLUDE] + // [END enable_network] + + }); + + it("should reply with .fromCache fields", () => { + // [START use_from_cache] + const { collection, onSnapshot, where, query } = require("firebase/firestore"); + + const q = query(collection(db, "cities"), where("state", "==", "CA")); + onSnapshot(q, { includeMetadataChanges: true }, (snapshot) => { + snapshot.docChanges().forEach((change) => { + if (change.type === "added") { + console.log("New city: ", change.doc.data()); + } + + const source = snapshot.metadata.fromCache ? "local cache" : "server"; + console.log("Data came from " + source); + }); + }); + // [END use_from_cache] + }); + + describe("collection('users')", () => { + it("should add data to a collection", async () => { + // [START add_ada_lovelace] + const { collection, addDoc } = require("firebase/firestore"); + + try { + const docRef = await addDoc(collection(db, "users"), { + first: "Ada", + last: "Lovelace", + born: 1815 + }); + console.log("Document written with ID: ", docRef.id); + } catch (e) { + console.error("Error adding document: ", e); + } + // [END add_ada_lovelace] + }); + + it("should get all users", async () => { + // [START get_all_users] + const { collection, getDocs } = require("firebase/firestore"); + + const querySnapshot = await getDocs(collection(db, "users")); + querySnapshot.forEach((doc) => { + console.log(`${doc.id} => ${doc.data()}`); + }); + // [END get_all_users] + }); + + it("should add data to a collection with new fields", async () => { + // [START add_alan_turing] + // Add a second document with a generated ID. + const { addDoc, collection } = require("firebase/firestore"); + + try { + const docRef = await addDoc(collection(db, "users"), { + first: "Alan", + middle: "Mathison", + last: "Turing", + born: 1912 + }); + + console.log("Document written with ID: ", docRef.id); + } catch (e) { + console.error("Error adding document: ", e); + } + // [END add_alan_turing] + }); + + it("should loop through a watched collection", (done) => { + // [START listen_for_users] + const { collection, where, query, onSnapshot } = require("firebase/firestore"); + + const q = query(collection(db, "users"), where("born", "<", 1900)); + const unsubscribe = onSnapshot(q, (snapshot) => { + console.log("Current users born before 1900:"); + snapshot.forEach((userSnapshot) => { + console.log(userSnapshot.data()); + }); + }); + // [END listen_for_users] + + setTimeout(() => { + unsubscribe(); + done(); + }, 1500); + }); + + it("should reference a specific document", () => { + // [START doc_reference] + const { doc } = require("firebase/firestore"); + + const alovelaceDocumentRef = doc(db, 'users', 'alovelace'); + // [END doc_reference] + }); + + it("should reference a specific collection", () => { + // [START collection_reference] + const { collection } = require("firebase/firestore"); + + const usersCollectionRef = collection(db, 'users'); + // [END collection_reference] + }); + + it("should reference a specific document (alternative)", () => { + // [START doc_reference_alternative] + const { doc } = require("firebase/firestore"); + + const alovelaceDocumentRef = doc(db, 'users/alovelace'); + // [END doc_reference_alternative] + }); + + it("should reference a document in a subcollection", () => { + // [START subcollection_reference] + const { doc } = require("firebase/firestore"); + + const messageRef = doc(db, "rooms", "roomA", "messages", "message1"); + // [END subcollection_reference] + }); + + it("should set a document", async () => { + // [START set_document] + const { doc, setDoc } = require("firebase/firestore"); + + // Add a new document in collection "cities" + await setDoc(doc(db, "cities", "LA"), { + name: "Los Angeles", + state: "CA", + country: "USA" + }); + // [END set_document] + }); + + it("should set document with a custom object converter", async () => { + // [START set_custom_object] + const { doc, setDoc } = require("firebase/firestore"); + + // Set with cityConverter + const ref = doc(db, "cities", "LA").withConverter(cityConverter); + await setDoc(ref, new City("Los Angeles", "CA", "USA")); + // [END set_custom_object] + }); + + it("should get document with a custom object converter", async () => { + // [START get_custom_object] + const { doc, getDoc} = require("firebase/firestore"); + + const ref = doc(db, "cities", "LA").withConverter(cityConverter); + const docSnap = await getDoc(ref); + if (docSnap.exists()) { + // Convert to City object + const city = docSnap.data(); + // Use a City instance method + console.log(city.toString()); + } else { + console.log("No such document!"); + } + // [END get_custom_object] + }); + + it("should support batch writes", async () => { + // [START write_batch] + const { writeBatch, doc } = require("firebase/firestore"); + + // Get a new write batch + const batch = writeBatch(db); + + // Set the value of 'NYC' + const nycRef = doc(db, "cities", "NYC"); + batch.set(nycRef, {name: "New York City"}); + + // Update the population of 'SF' + const sfRef = doc(db, "cities", "SF"); + batch.update(sfRef, {"population": 1000000}); + + // Delete the city 'LA' + const laRef = doc(db, "cities", "LA"); + batch.delete(laRef); + + // Commit the batch + await batch.commit(); + // [END write_batch] + }); + + it("should set a document with every datatype #UNVERIFIED", async () => { + // [START data_types] + const { doc, setDoc, Timestamp } = require("firebase/firestore"); + + const docData = { + stringExample: "Hello world!", + booleanExample: true, + numberExample: 3.14159265, + dateExample: Timestamp.fromDate(new Date("December 10, 1815")), + arrayExample: [5, true, "hello"], + nullExample: null, + objectExample: { + a: 5, + b: { + nested: "foo" + } + } + }; + await setDoc(doc(db, "data", "one"), docData); + // [END data_types] + }); + + it("should allow set with merge", async () => { + // [START set_with_merge] + const { doc, setDoc } = require("firebase/firestore"); + + const cityRef = doc(db, 'cities', 'BJ'); + setDoc(cityRef, { capital: true }, { merge: true }); + // [END set_with_merge] + }); + + it("should update a document's nested fields #UNVERIFIED", async () => { + // [START update_document_nested] + const { doc, setDoc, updateDoc } = require("firebase/firestore"); + + // Create an initial document to update. + const frankDocRef = doc(db, "users", "frank"); + await setDoc(frankDocRef, { + name: "Frank", + favorites: { food: "Pizza", color: "Blue", subject: "recess" }, + age: 12 + }); + + // To update age and favorite color: + await updateDoc(frankDocRef, { + "age": 13, + "favorites.color": "Red" + }); + // [END update_document_nested] + }); + + it("should delete a collection", () => { + // [START delete_collection] + /** + * Delete a collection, in batches of batchSize. Note that this does + * not recursively delete subcollections of documents in the collection + */ + const { collection, query, orderBy, limit, getDocs, writeBatch } = require("firebase/firestore"); + + function deleteCollection(db, collectionRef, batchSize) { + const q = query(collectionRef, orderBy('__name__'), limit(batchSize)); + + return new Promise((resolve) => { + deleteQueryBatch(db, q, batchSize, resolve); + }); + } + + async function deleteQueryBatch(db, query, batchSize, resolve) { + const snapshot = await getDocs(query); + + // When there are no documents left, we are done + let numDeleted = 0; + if (snapshot.size > 0) { + // Delete documents in a batch + const batch = writeBatch(db); + snapshot.docs.forEach((doc) => { + batch.delete(doc.ref); + }); + + await batch.commit(); + numDeleted = snapshot.size; + } + + if (numDeleted < batchSize) { + resolve(); + return; + } + + // Recurse on the next process tick, to avoid + // exploding the stack. + setTimeout(() => { + deleteQueryBatch(db, query, batchSize, resolve); + }, 0); + } + // [END delete_collection] + + return deleteCollection(db, collection(db, "users"), 2); + }).timeout(2000); + }); + + describe("collection('cities')", () => { + it("should set documents", async () => { + // [START example_data] + const { collection, doc, setDoc } = require("firebase/firestore"); + + const citiesRef = collection(db, "cities"); + + await setDoc(doc(citiesRef, "SF"), { + name: "San Francisco", state: "CA", country: "USA", + capital: false, population: 860000, + regions: ["west_coast", "norcal"] }); + await setDoc(doc(citiesRef, "LA"), { + name: "Los Angeles", state: "CA", country: "USA", + capital: false, population: 3900000, + regions: ["west_coast", "socal"] }); + await setDoc(doc(citiesRef, "DC"), { + name: "Washington, D.C.", state: null, country: "USA", + capital: true, population: 680000, + regions: ["east_coast"] }); + await setDoc(doc(citiesRef, "TOK"), { + name: "Tokyo", state: null, country: "Japan", + capital: true, population: 9000000, + regions: ["kanto", "honshu"] }); + await setDoc(doc(citiesRef, "BJ"), { + name: "Beijing", state: null, country: "China", + capital: true, population: 21500000, + regions: ["jingjinji", "hebei"] }); + // [END example_data] + }); + it("should set a document", async () => { + const data = {}; + + // [START cities_document_set] + const { doc, setDoc } = require("firebase/firestore"); + + await setDoc(doc(db, "cities", "new-city-id"), data); + // [END cities_document_set] + }); + + it("should add a document", async () => { + // [START add_document] + const { collection, addDoc } = require("firebase/firestore"); + + // Add a new document with a generated id. + const docRef = await addDoc(collection(db, "cities"), { + name: "Tokyo", + country: "Japan" + }); + console.log("Document written with ID: ", docRef.id); + // [END add_document] + }); + + it("should add an empty a document", async () => { + const data = {}; + // [START new_document] + const { collection, doc, setDoc } = require("firebase/firestore"); + + // Add a new document with a generated id + const newCityRef = doc(collection(db, "cities")); + + // later... + await setDoc(newCityRef, data); + // [END new_document] + }); + + it("should update a document", async () => { + const data = {}; + // [START update_document] + const { doc, updateDoc } = require("firebase/firestore"); + + const washingtonRef = doc(db, "cities", "DC"); + + // Set the "capital" field of the city 'DC' + await updateDoc(washingtonRef, { + capital: true + }); + // [END update_document] + }); + + it("should update an array field in a document", async () => { + // [START update_document_array] + const { doc, updateDoc, arrayUnion, arrayRemove } = require("firebase/firestore"); + + const washingtonRef = doc(db, "cities", "DC"); + + // Atomically add a new region to the "regions" array field. + await updateDoc(washingtonRef, { + regions: arrayUnion("greater_virginia") + }); + + // Atomically remove a region from the "regions" array field. + await updateDoc(washingtonRef, { + regions: arrayRemove("east_coast") + }); + // [END update_document_array] + }); + + it("should update a document using numeric transforms", async () => { + // [START update_document_increment] + const { doc, updateDoc, increment } = require("firebase/firestore"); + + const washingtonRef = doc(db, "cities", "DC"); + + // Atomically increment the population of the city by 50. + await updateDoc(washingtonRef, { + population: increment(50) + }); + // [END update_document_increment] + }); + + it("should delete a document", async () => { + // [START delete_document] + const { doc, deleteDoc } = require("firebase/firestore"); + + await deleteDoc(doc(db, "cities", "DC")); + // [END delete_document] + }); + + it("should handle transactions", async () => { + const { doc, setDoc } = require("firebase/firestore"); + + const sfDocRef = doc(db, "cities", "SF"); + await setDoc(sfDocRef, { population: 0 }); + + // [START transaction] + const { runTransaction } = require("firebase/firestore"); + + try { + await runTransaction(db, async (transaction) => { + const sfDoc = await transaction.get(sfDocRef); + if (!sfDoc.exists()) { + throw "Document does not exist!"; + } + + const newPopulation = sfDoc.data().population + 1; + transaction.update(sfDocRef, { population: newPopulation }); + }); + console.log("Transaction successfully committed!"); + } catch (e) { + console.log("Transaction failed: ", e); + } + // [END transaction] + }); + + it("should handle transaction which bubble out data", async () => { + // [START transaction_promise] + const { doc, runTransaction } = require("firebase/firestore"); + + // Create a reference to the SF doc. + const sfDocRef = doc(db, "cities", "SF"); + + try { + const newPopulation = await runTransaction(db, async (transaction) => { + const sfDoc = await transaction.get(sfDocRef); + if (!sfDoc.exists()) { + throw "Document does not exist!"; + } + + const newPop = sfDoc.data().population + 1; + if (newPop <= 1000000) { + transaction.update(sfDocRef, { population: newPop }); + return newPop; + } else { + return Promise.reject("Sorry! Population is too big"); + } + }); + + console.log("Population increased to ", newPopulation); + } catch (e) { + // This will be a "population is too big" error. + console.error(e); + } + // [END transaction_promise] + }); + + it("should get a single document", async () => { + // [START get_document] + const { doc, getDoc } = require("firebase/firestore"); + + const docRef = doc(db, "cities", "SF"); + const docSnap = await getDoc(docRef); + + if (docSnap.exists()) { + console.log("Document data:", docSnap.data()); + } else { + // docSnap.data() will be undefined in this case + console.log("No such document!"); + } + // [END get_document] + }); + + it("should get a document with options", async () => { + // [START get_document_options] + const { doc, getDocFromCache } = require("firebase/firestore"); + + const docRef = doc(db, "cities", "SF"); + + // Get a document, forcing the SDK to fetch from the offline cache. + try { + const doc = await getDocFromCache(docRef); + + // Document was found in the cache. If no cached document exists, + // an error will be returned to the 'catch' block below. + console.log("Cached document data:", doc.data()); + } catch (e) { + console.log("Error getting cached document:", e); + } + // [END get_document_options] + }); + + it("should listen on a single document", (done) => { + // [START listen_document] + const { doc, onSnapshot } = require("firebase/firestore"); + + const unsub = onSnapshot(doc(db, "cities", "SF"), (doc) => { + console.log("Current data: ", doc.data()); + }); + // [END listen_document] + + setTimeout(() => { + unsub(); + done(); + }, 3000); + }).timeout(5000); + + it("should listen on a single document with metadata", (done) => { + // [START listen_document_local] + const { doc, onSnapshot } = require("firebase/firestore"); + + const unsub = onSnapshot(doc(db, "cities", "SF"), (doc) => { + const source = doc.metadata.hasPendingWrites ? "Local" : "Server"; + console.log(source, " data: ", doc.data()); + }); + // [END listen_document_local] + + setTimeout(() => { + unsub(); + done(); + }, 3000); + }).timeout(5000); + + it("should listen on a single document with options #UNVERIFIED", (done) => { + // [START listen_with_metadata] + const { doc, onSnapshot } = require("firebase/firestore"); + + const unsub = onSnapshot( + doc(db, "cities", "SF"), + { includeMetadataChanges: true }, + (doc) => { + // ... + }); + // [END listen_with_metadata] + + setTimeout(() => { + unsub(); + done(); + }, 3000); + }).timeout(5000); + + it("should get multiple documents from a collection", async () => { + // [START get_multiple] + const { collection, query, where, getDocs } = require("firebase/firestore"); + + const q = query(collection(db, "cities"), where("capital", "==", true)); + + const querySnapshot = await getDocs(q); + querySnapshot.forEach((doc) => { + // doc.data() is never undefined for query doc snapshots + console.log(doc.id, " => ", doc.data()); + }); + // [END get_multiple] + }).timeout(5000); + + it("should get all documents from a collection", async () => { + // [START get_multiple_all] + const { collection, getDocs } = require("firebase/firestore"); + + const querySnapshot = await getDocs(collection(db, "cities")); + querySnapshot.forEach((doc) => { + // doc.data() is never undefined for query doc snapshots + console.log(doc.id, " => ", doc.data()); + }); + // [END get_multiple_all] + }); + + it("should get all documents from a subcollection", async () => { + // [START firestore_query_subcollection] + const { collection, getDocs } = require("firebase/firestore"); + // Query a reference to a subcollection + const querySnapshot = await getDocs(collection(db, "cities", "SF", "landmarks")); + querySnapshot.forEach((doc) => { + // doc.data() is never undefined for query doc snapshots + console.log(doc.id, " => ", doc.data()); + }); + // [END firestore_query_subcollection] + }); + + it("should listen on multiple documents #UNVERIFIED", (done) => { + // [START listen_multiple] + const { collection, query, where, onSnapshot } = require("firebase/firestore"); + + const q = query(collection(db, "cities"), where("state", "==", "CA")); + const unsubscribe = onSnapshot(q, (querySnapshot) => { + const cities = []; + querySnapshot.forEach((doc) => { + cities.push(doc.data().name); + }); + console.log("Current cities in CA: ", cities.join(", ")); + }); + // [END listen_multiple] + setTimeout(() => { + unsubscribe(); + done(); + }, 2500); + }).timeout(5000); + + it("should view changes between snapshots #UNVERIFIED", (done) => { + // [START listen_diffs] + const { collection, query, where, onSnapshot } = require("firebase/firestore"); + + const q = query(collection(db, "cities"), where("state", "==", "CA")); + const unsubscribe = onSnapshot(q, (snapshot) => { + snapshot.docChanges().forEach((change) => { + if (change.type === "added") { + console.log("New city: ", change.doc.data()); + } + if (change.type === "modified") { + console.log("Modified city: ", change.doc.data()); + } + if (change.type === "removed") { + console.log("Removed city: ", change.doc.data()); + } + }); + }); + // [END listen_diffs] + setTimeout(() => { + unsubscribe(); + done(); + }, 2500); + }).timeout(5000); + + it("should unsubscribe a listener", () => { + // [START detach_listener] + const { collection, onSnapshot } = require("firebase/firestore"); + + const unsubscribe = onSnapshot(collection(db, "cities"), () => { + // Respond to data + // ... + }); + + // Later ... + + // Stop listening to changes + unsubscribe(); + // [END detach_listener] + }); + + it("should handle listener errors", () => { + // [START handle_listen_errors] + const { collection, onSnapshot } = require("firebase/firestore"); + + const unsubscribe = onSnapshot( + collection(db, "cities"), + (snapshot) => { + // ... + }, + (error) => { + // ... + }); + // [END handle_listen_errors] + unsubscribe(); + }); + + it("should update a document with server timestamp", async () => { + async function update() { + // [START update_with_server_timestamp] + const { updateDoc, serverTimestamp } = require("firebase/firestore"); + + const docRef = doc(db, 'objects', 'some-id'); + + // Update the timestamp field with the value from the server + const updateTimestamp = await updateDoc(docRef, { + timestamp: serverTimestamp() + }); + // [END update_with_server_timestamp] + + return updateTimestamp; + } + + const { doc, setDoc } = require("firebase/firestore"); + + await setDoc(doc(db, 'objects', 'some-id'), {}); + await update(); + console.log('Document updated with server timestamp'); + }); + + it("should use options to control server timestamp resolution", async () => { + // [START server_timestamp_resolution_options] + const { doc, updateDoc, serverTimestamp, onSnapshot } = require("firebase/firestore"); + // Perform an update followed by an immediate read without + // waiting for the update to complete. Due to the snapshot + // options we will get two results: one with an estimate + // timestamp and one with the resolved server timestamp. + const docRef = doc(db, 'objects', 'some-id'); + updateDoc(docRef, { + timestamp: serverTimestamp() + }); + + onSnapshot(docRef, (snapshot) => { + const data = snapshot.data({ + // Options: 'estimate', 'previous', or 'none' + serverTimestamps: "estimate" + }); + console.log( + 'Timestamp: ' + data.timestamp + + ', pending: ' + snapshot.metadata.hasPendingWrites); + }); + // [END server_timestamp_resolution_options] + }); + + it("should delete a document field", async () => { + async function update() { + // [START update_delete_field] + const { doc, updateDoc, deleteField } = require("firebase/firestore"); + + const cityRef = doc(db, 'cities', 'BJ'); + + // Remove the 'capital' field from the document + await updateDoc(cityRef, { + capital: deleteField() + }); + // [END update_delete_field] + } + + const { doc, setDoc } = require("firebase/firestore"); + + await setDoc(doc(db, 'cities', 'BJ'), { capital: true }); + await update(); + }); + + describe("queries", () => { + it("should handle simple where", () => { + // [START simple_queries] + // Create a reference to the cities collection + const { collection, query, where } = require("firebase/firestore"); + const citiesRef = collection(db, "cities"); + + // Create a query against the collection. + const q = query(citiesRef, where("state", "==", "CA")); + // [END simple_queries] + }); + + it("should handle another simple where", () => { + // [START simple_queries_again] + const { collection, query, where } = require("firebase/firestore"); + const citiesRef = collection(db, "cities"); + + const q = query(citiesRef, where("capital", "==", true)); + // [END simple_queries_again] + }); + + it("should handle other wheres", () => { + const { collection, query, where } = require("firebase/firestore"); + const citiesRef = collection(db, "cities"); + + // [START example_filters] + const stateQuery = query(citiesRef, where("state", "==", "CA")); + const populationQuery = query(citiesRef, where("population", "<", 100000)); + const nameQuery = query(citiesRef, where("name", ">=", "San Francisco")); + // [END example_filters] + + // [START simple_query_not_equal] + const notCapitalQuery = query(citiesRef, where("capital", "!=", false)); + // [END simple_query_not_equal] + }); + + it("should handle array-contains where", () => { + const { collection } = require("firebase/firestore"); + const citiesRef = collection(db, "cities"); + + // [START array_contains_filter] + const { query, where } = require("firebase/firestore"); + const q = query(citiesRef, where("regions", "array-contains", "west_coast")); + // [END array_contains_filter] + }); + + it("should handle an array contains any where", () => { + const { collection } = require("firebase/firestore"); + const citiesRef = collection(db, "cities"); + + // [START array_contains_any_filter] + const { query, where } = require("firebase/firestore"); + + const q = query(citiesRef, + where('regions', 'array-contains-any', ['west_coast', 'east_coast'])); + // [END array_contains_any_filter] + }); + + it("should handle an in where", () => { + const { collection } = require("firebase/firestore"); + const citiesRef = collection(db, "cities"); + + function inFilter() { + // [START in_filter] + const { query, where } = require("firebase/firestore"); + + const q = query(citiesRef, where('country', 'in', ['USA', 'Japan'])); + // [END in_filter] + } + + function notInFilter() { + // [START not_in_filter] + const { query, where } = require("firebase/firestore"); + + const q = query(citiesRef, where('country', 'not-in', ['USA', 'Japan'])); + // [END not_in_filter] + } + + function inFilterWithArray() { + // [START in_filter_with_array] + const { query, where } = require("firebase/firestore"); + + const q = query(citiesRef, where('regions', 'in', [['west_coast'], ['east_coast']])); + // [END in_filter_with_array] + } + }); + + it("should handle compound queries", () => { + const { collection } = require("firebase/firestore"); + const citiesRef = collection(db, "cities"); + + // [START chain_filters] + const { query, where } = require("firebase/firestore"); + + const q1 = query(citiesRef, where("state", "==", "CO"), where("name", "==", "Denver")); + const q2 = query(citiesRef, where("state", "==", "CA"), where("population", "<", 1000000)); + // [END chain_filters] + }); + + it("should handle range filters on one field", () => { + const { collection } = require("firebase/firestore"); + const citiesRef = collection(db, "cities"); + + // [START valid_range_filters] + const { query, where } = require("firebase/firestore"); + + const q1 = query(citiesRef, where("state", ">=", "CA"), where("state", "<=", "IN")); + const q2 = query(citiesRef, where("state", "==", "CA"), where("population", ">", 1000000)); + // [END valid_range_filters] + }); + + it("should not handle range filters on multiple field", () => { + const { collection } = require("firebase/firestore"); + const citiesRef = collection(db, "cities"); + + expect(() => { + // [START invalid_range_filters] + const { query, where } = require("firebase/firestore"); + + const q = query(citiesRef, where("state", ">=", "CA"), where("population", ">", 100000)); + // [END invalid_range_filters] + }).to.throw; + }); + + it("should order and limit", () => { + const { collection } = require("firebase/firestore"); + const citiesRef = collection(db, "cities"); + + // [START order_and_limit] + const { query, orderBy, limit } = require("firebase/firestore"); + + const q = query(citiesRef, orderBy("name"), limit(3)); + // [END order_and_limit] + }); + + it("should order descending", () => { + const { collection } = require("firebase/firestore"); + const citiesRef = collection(db, "cities"); + + // [START order_and_limit_desc] + const { query, orderBy, limit } = require("firebase/firestore"); + + const q = query(citiesRef, orderBy("name", "desc"), limit(3)); + // [END order_and_limit_desc] + }); + + it("should order descending by other field", () => { + const { collection } = require("firebase/firestore"); + const citiesRef = collection(db, "cities"); + + // [START order_multiple] + const { query, orderBy } = require("firebase/firestore"); + + const q = query(citiesRef, orderBy("state"), orderBy("population", "desc")); + // [END order_multiple] + }); + + it("should where and order by with limit", () => { + const { collection } = require("firebase/firestore"); + const citiesRef = collection(db, "cities"); + + // [START filter_and_order] + const { query, where, orderBy, limit } = require("firebase/firestore"); + + const q = query(citiesRef, where("population", ">", 100000), orderBy("population"), limit(2)); + // [END filter_and_order] + }); + + it("should where and order on same field", () => { + const { collection } = require("firebase/firestore"); + const citiesRef = collection(db, "cities"); + + // [START valid_filter_and_order] + const { query, where, orderBy } = require("firebase/firestore"); + + const q = query(citiesRef, where("population", ">", 100000), orderBy("population")); + // [END valid_filter_and_order] + }); + + it("should not where and order on same field", () => { + const { collection } = require("firebase/firestore"); + const citiesRef = collection(db, "cities"); + + expect(() => { + // [START invalid_filter_and_order] + const { query, where, orderBy } = require("firebase/firestore"); + + const q = query(citiesRef, where("population", ">", 100000), orderBy("country")); + // [END invalid_filter_and_order] + }).to.throw; + }); + + it("should handle startAt", () => { + const { collection } = require("firebase/firestore"); + const citiesRef = collection(db, "cities"); + + // [START order_and_start] + const { query, orderBy, startAt } = require("firebase/firestore"); + + const q = query(citiesRef, orderBy("population"), startAt(1000000)); + // [END order_and_start] + }); + + it("should handle endAt", () => { + const { collection } = require("firebase/firestore"); + const citiesRef = collection(db, "cities"); + + // [START order_and_end] + const { query, orderBy, endAt } = require("firebase/firestore"); + + const q = query(citiesRef, orderBy("population"), endAt(1000000)); + // [END order_and_end] + }); + + it("should handle startAt(doc) ", async () => { + // [START start_doc] + const { collection, doc, getDoc, query, orderBy, startAt } = require("firebase/firestore"); + const citiesRef = collection(db, "cities"); + + const docSnap = await getDoc(doc(citiesRef, "SF")); + + // Get all cities with a population bigger than San Francisco + const biggerThanSf = query(citiesRef, orderBy("population"), startAt(docSnap)); + // ... + // [END start_doc] + }); + + it("should handle multiple orderBy", () => { + // [START start_multiple_orderby] + // Will return all Springfields + const { collection, query, orderBy, startAt } = require("firebase/firestore"); + const q1 = query(collection(db, "cities"), + orderBy("name"), + orderBy("state"), + startAt("Springfield")); + + // Will return "Springfield, Missouri" and "Springfield, Wisconsin" + const q2 = query(collection(db, "cities"), + orderBy("name"), + orderBy("state"), + startAt("Springfield", "Missouri")); + // [END start_multiple_orderby] + }); + + it("should paginate", async () => { + // [START paginate] + const { collection, query, orderBy, startAfter, limit, getDocs } = require("firebase/firestore"); + + // Query the first page of docs + const first = query(collection(db, "cities"), orderBy("population"), limit(25)); + const documentSnapshots = await getDocs(first); + + // Get the last visible document + const lastVisible = documentSnapshots.docs[documentSnapshots.docs.length-1]; + console.log("last", lastVisible); + + // Construct a new query starting at this document, + // get the next 25 cities. + const next = query(collection(db, "cities"), + orderBy("population"), + startAfter(lastVisible), + limit(25)); + // [END paginate] + }); + }); + + describe('collectionGroup(landmarks)', () => { + it("should setup example data", async () => { + // [START fs_collection_group_query_data_setup] + const { collection, addDoc } = require("firebase/firestore"); + + const citiesRef = collection(db, 'cities'); + + await Promise.all([ + addDoc(collection(citiesRef, 'SF', 'landmarks'), { + name: 'Golden Gate Bridge', + type: 'bridge' + }), + addDoc(collection(citiesRef, 'SF', 'landmarks'), { + name: 'Legion of Honor', + type: 'museum' + }), + addDoc(collection(citiesRef, 'LA', 'landmarks'), { + name: 'Griffith Park', + type: 'park' + }), + addDoc(collection(citiesRef, 'LA', 'landmarks'), { + name: 'The Getty', + type: 'museum' + }), + addDoc(collection(citiesRef, 'DC', 'landmarks'), { + name: 'Lincoln Memorial', + type: 'memorial' + }), + addDoc(collection(citiesRef, 'DC', 'landmarks'), { + name: 'National Air and Space Museum', + type: 'museum' + }), + addDoc(collection(citiesRef, 'TOK', 'landmarks'), { + name: 'Ueno Park', + type: 'park' + }), + addDoc(collection(citiesRef, 'TOK', 'landmarks'), { + name: 'National Museum of Nature and Science', + type: 'museum' + }), + addDoc(collection(citiesRef, 'BJ', 'landmarks'), { + name: 'Jingshan Park', + type: 'park' + }), + addDoc(collection(citiesRef, 'BJ', 'landmarks'), { + name: 'Beijing Ancient Observatory', + type: 'museum' + }) + ]); + // [END fs_collection_group_query_data_setup] + }); + + it("should query a collection group", async () => { + // [START fs_collection_group_query] + const { collectionGroup, query, where, getDocs } = require("firebase/firestore"); + + const museums = query(collectionGroup(db, 'landmarks'), where('type', '==', 'museum')); + const querySnapshot = await getDocs(museums); + querySnapshot.forEach((doc) => { + console.log(doc.id, ' => ', doc.data()); + }); + // [END fs_collection_group_query] + }); + }); + }); + + describe("aggregate queries", () => { + it("should fetch the count of documents in a collection", async () => { + const { collection, getCountFromServer } = require("firebase/firestore"); + // [START count_aggregate_collection] + const coll = collection(db, "cities"); + const snapshot = await getCountFromServer(coll); + console.log('count: ', snapshot.data().count); + // [END count_aggregate_collection] + }); + + it("should fetch the count of documents in a query", async () => { + const { collection, getCountFromServer, where, query } = require("firebase/firestore"); + // [START count_aggregate_query] + const coll = collection(db, "cities"); + const q = query(coll, where("state", "==", "CA")); + const snapshot = await getCountFromServer(q); + console.log('count: ', snapshot.data().count); + // [END count_aggregate_query] + }); + }); + + // TODO: Break out into separate file + describe("solution-aggregation", () => { + it("should update a restaurant in a transaction #UNVERIFIED", async () => { + // [START add_rating_transaction] + const { collection, doc, runTransaction } = require("firebase/firestore"); + + async function addRating(restaurantRef, rating) { + // Create a reference for a new rating, for use inside the transaction + const ratingRef = doc(collection(restaurantRef, 'ratings')); + + // In a transaction, add the new rating and update the aggregate totals + await runTransaction(db, async (transaction) => { + const res = await transaction.get(restaurantRef); + if (!res.exists()) { + throw "Document does not exist!"; + } + + // Compute new number of ratings + const newNumRatings = res.data().numRatings + 1; + + // Compute new average rating + const oldRatingTotal = res.data().avgRating * res.data().numRatings; + const newAvgRating = (oldRatingTotal + rating) / newNumRatings; + + // Commit to Firestore + transaction.update(restaurantRef, { + numRatings: newNumRatings, + avgRating: newAvgRating + }); + transaction.set(ratingRef, { rating: rating }); + }); + } + // [END add_rating_transaction] + + // Create document and add a rating + const { setDoc } = require("firebase/firestore"); + const ref = doc(db, 'restaurants', 'arinell-pizza'); + await setDoc(ref, { + name: 'Arinell Pizza', + avgRating: 4.63, + numRatings: 683 + }); + await addRating(ref, 5.0); + }); + }); +}); diff --git a/firestore-next/test.solution-aggregation.js b/firestore-next/test.solution-aggregation.js new file mode 100644 index 00000000..7106a0c9 --- /dev/null +++ b/firestore-next/test.solution-aggregation.js @@ -0,0 +1,43 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +// [START sample_doc] +const arinellDoc = { + name: 'Arinell Pizza', + avgRating: 4.65, + numRatings: 683 +}; +// [END sample_doc] + +describe("firestore-solution-arrays", () => { + const { Firestore } = require("firebase/firestore"); + + /** @type {Firestore} */ + let db; + + before(async () => { + const { initializeApp } = require("firebase/app"); + const { getFirestore, collection, doc, setDoc } = require("firebase/firestore"); + + const config = { + apiKey: "AIzaSyArvVh6VSdXicubcvIyuB-GZs8ua0m0DTI", + authDomain: "firestorequickstarts.firebaseapp.com", + projectId: "firestorequickstarts", + }; + const app = initializeApp(config, "solution-arrays"); + db = getFirestore(app); + + await setDoc(doc(db, "restaurants", "arinell-pizza"), arinellDoc); + }); + + describe("solution-arrays", () => { + it("should get a collection of ratings", async () => { + // [START get_collection_ratings] + const { collection, getDocs } = require("firebase/firestore"); + + const ratingsRef = collection(db, "restaurants", "arinell-pizza", "ratings"); + const ratingsDocs = await getDocs(ratingsRef); + // [END get_collection_ratings] + }); + }); +}); diff --git a/firestore-next/test.solution-arrays.js b/firestore-next/test.solution-arrays.js new file mode 100644 index 00000000..3746a8d1 --- /dev/null +++ b/firestore-next/test.solution-arrays.js @@ -0,0 +1,102 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +const postsWithArray = [ + // [START post_with_array] + // Sample document in the 'posts' collection. + { + title: "My great post", + categories: [ + "technology", + "opinion", + "cats" + ] + } + // [END post_with_array] +]; + +const postsWithMap = [ + // [START post_with_map] + // Sample document in the 'posts' collection + { + title: "My great post", + categories: { + "technology": true, + "opinion": true, + "cats": true + } + } + // [END post_with_map] +]; + +const postsWithMapAdvanced = [ + // [START post_with_map_advanced] + // The value of each entry in 'categories' is a unix timestamp + { + title: "My great post", + categories: { + technology: 1502144665, + opinion: 1502144665, + cats: 1502144665 + } + } + // [END post_with_map_advanced] +]; + +describe("firestore-solution-arrays", () => { + const { Firestore } = require("firebase/firestore"); + + /** @type {Firestore} */ + let db; + + before(() => { + const { initializeApp } = require("firebase/app"); + const { getFirestore } = require("firebase/firestore"); + + const config = { + apiKey: "AIzaSyArvVh6VSdXicubcvIyuB-GZs8ua0m0DTI", + authDomain: "firestorequickstarts.firebaseapp.com", + projectId: "firestorequickstarts", + }; + const app = initializeApp(config, "solution-arrays"); + db = getFirestore(app); + }); + + describe("solution-arrays", () => { + it("should query in a category", async () => { + // [START query_in_category] + const { collection, getDocs, query, where } = require("firebase/firestore"); + + // Find all documents in the 'posts' collection that are + // in the 'cats' category. + const q = query(collection(db, "posts"), where("categories.cats", "==", true)); + const docs = await getDocs(q); + // ... + // [END query_in_category] + }); + + it("should query in a category by timestamp", () => { + function queryOne() { + // [START query_in_category_timestamp_invalid] + const { collection, query, where, orderBy, Firestore } = require("firebase/firestore"); + + const q = query(collection(db, "posts"), + where("categories.cats", "==", true), + orderBy("timestamp")); + // [END query_in_category_timestamp_invalid] + } + + + function queryTwo() { + // [START query_in_category_timestamp] + const { collection, query, where, orderBy } = require("firebase/firestore"); + + const q = query(collection(db, "posts"), + where("categories.cats", ">", 0), + orderBy("categories.cats")); + // [END query_in_category_timestamp] + } + + }); + }); +}); diff --git a/firestore-next/test.solution-bundles.js b/firestore-next/test.solution-bundles.js new file mode 100644 index 00000000..94bf7ff2 --- /dev/null +++ b/firestore-next/test.solution-bundles.js @@ -0,0 +1,50 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +const { Firestore } = require('firebase/firestore'); + +/** + * @type Firestore + */ +var db; + +// [START fs_bundle_load] +const { loadBundle, namedQuery, getDocsFromCache } = require("firebase/firestore"); + +async function fetchFromBundle() { + // Fetch the bundle from Firebase Hosting, if the CDN cache is hit the 'X-Cache' + // response header will be set to 'HIT' + const resp = await fetch('/createBundle'); + + // Load the bundle contents into the Firestore SDK + await loadBundle(db, resp.body); + + // Query the results from the cache + const query = await namedQuery(db, 'latest-stories-query'); + const storiesSnap = await getDocsFromCache(query); + + // Use the results + // ... +} +// [END fs_bundle_load] + +describe("firestore-solution-bundles", () => { + before(() => { + const { initializeApp } = require("firebase/app"); + const { getFirestore} = require("firebase/firestore"); + + const config = { + apiKey: "AIzaSyArvVh6VSdXicubcvIyuB-GZs8ua0m0DTI", + authDomain: "firestorequickstarts.firebaseapp.com", + projectId: "firestorequickstarts", + }; + const app = initializeApp(config, "solution-bundles"); + db = getFirestore(app); + }); + + describe("solution-bundles", () => { + it("should fetch a bundle", (done) => { + fetchFromBundle().finally(done); + }); + }); +}); diff --git a/firestore-next/test.solution-counters.js b/firestore-next/test.solution-counters.js new file mode 100644 index 00000000..da854797 --- /dev/null +++ b/firestore-next/test.solution-counters.js @@ -0,0 +1,99 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +const { Firestore } = require('firebase/firestore'); + +/** @type {Firestore} */ +let db; + +// [START create_counter] +function createCounter(ref, num_shards) { + const { doc, writeBatch } = require("firebase/firestore"); + + const batch = writeBatch(db); + + // Initialize the counter document + batch.set(ref, { num_shards: num_shards }); + + // Initialize each shard with count=0 + for (let i = 0; i < num_shards; i++) { + const shardRef = doc(ref, 'shards', i.toString()); + batch.set(shardRef, { count: 0 }); + } + + // Commit the write batch + return batch.commit(); +} +// [END create_counter] + +// [START increment_counter] +function incrementCounter(db, ref, num_shards) { + const { doc, updateDoc, increment } = require("firebase/firestore"); + + // Select a shard of the counter at random + const shardId = Math.floor(Math.random() * num_shards).toString(); + const shardRef = doc(ref, 'shards', shardId); + + // Update count + return updateDoc(shardRef, "count", increment(1)); +} +// [END increment_counter] + +// [START get_count] +async function getCount(ref) { + const { collection, getDocs } = require("firebase/firestore"); + + // Sum the count of each shard in the subcollection + const snapshot = await getDocs(collection(ref, 'shards')); + + let totalCount = 0; + snapshot.forEach((doc) => { + totalCount += doc.data().count; + }); + + return totalCount; +} +// [END get_count] + +describe("firestore-solution-counters", () => { + before(() => { + const { initializeApp } = require("firebase/app"); + const { getFirestore } = require("firebase/firestore"); + + const config = { + apiKey: "AIzaSyArvVh6VSdXicubcvIyuB-GZs8ua0m0DTI", + authDomain: "firestorequickstarts.firebaseapp.com", + projectId: "firestorequickstarts", + }; + const app = initializeApp(config, "solution-arrays"); + db = getFirestore(app); + }); + + describe("solution-counters", () => { + it("should create a counter", () => { + // Create a counter with 10 shards + const { collection, doc } = require("firebase/firestore"); + + return createCounter(doc(collection(db, 'counters')), 10); + }); + + it("should increment a counter", async () => { + // Create a counter, then increment it + const { collection, doc } = require("firebase/firestore"); + + const ref = doc(collection(db, 'counters')); + await createCounter(ref, 10); + await incrementCounter(db, ref, 10); + }); + + it("should get the count of a counter", async () => { + // Create a counter, increment it, then get the count + const { collection, doc } = require("firebase/firestore"); + + const ref = doc(collection(db, 'counters')); + await createCounter(ref, 10); + await incrementCounter(db, ref, 10); + await getCount(ref); + }); + }); +}); diff --git a/firestore-next/test.solution-geoqueries.js b/firestore-next/test.solution-geoqueries.js new file mode 100644 index 00000000..507c6178 --- /dev/null +++ b/firestore-next/test.solution-geoqueries.js @@ -0,0 +1,103 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +const { Firestore } = require('firebase/firestore'); + +const geofire = require('geofire-common'); + +/** @type {Firestore} */ +let db; + +async function addHash(done) { + // [START fs_geo_add_hash] + const { doc, updateDoc } = require('firebase/firestore'); + + // Compute the GeoHash for a lat/lng point + const lat = 51.5074; + const lng = 0.1278; + const hash = geofire.geohashForLocation([lat, lng]); + + // Add the hash and the lat/lng to the document. We will use the hash + // for queries and the lat/lng for distance comparisons. + const londonRef = doc(db, 'cities', 'LON'); + await updateDoc(londonRef, { + geohash: hash, + lat: lat, + lng: lng + }); + // [END fs_geo_add_hash] + done(); +} + +async function queryHashes(done) { + // [START fs_geo_query_hashes] + const { collection, query, orderBy, startAt, endAt, getDocs } = require('firebase/firestore'); + + // Find cities within 50km of London + const center = [51.5074, 0.1278]; + const radiusInM = 50 * 1000; + + // Each item in 'bounds' represents a startAt/endAt pair. We have to issue + // a separate query for each pair. There can be up to 9 pairs of bounds + // depending on overlap, but in most cases there are 4. + const bounds = geofire.geohashQueryBounds(center, radiusInM); + const promises = []; + for (const b of bounds) { + const q = query( + collection(db, 'cities'), + orderBy('geohash'), + startAt(b[0]), + endAt(b[1])); + + promises.push(getDocs(q)); + } + + // Collect all the query results together into a single list + const snapshots = await Promise.all(promises); + + const matchingDocs = []; + for (const snap of snapshots) { + for (const doc of snap.docs) { + const lat = doc.get('lat'); + const lng = doc.get('lng'); + + // We have to filter out a few false positives due to GeoHash + // accuracy, but most will match + const distanceInKm = geofire.distanceBetween([lat, lng], center); + const distanceInM = distanceInKm * 1000; + if (distanceInM <= radiusInM) { + matchingDocs.push(doc); + } + } + } + // [END fs_geo_query_hashes] + done(matchingDocs); +} + +describe("firestore-solution-geoqueries", () => { + before(() => { + const { initializeApp } = require("firebase/app"); + const { getFirestore} = require("firebase/firestore"); + + const config = { + apiKey: "AIzaSyArvVh6VSdXicubcvIyuB-GZs8ua0m0DTI", + authDomain: "firestorequickstarts.firebaseapp.com", + projectId: "firestorequickstarts", + }; + const app = initializeApp(config, "solution-geoqueries"); + db = getFirestore(app); + }); + + describe("solution-geoqueries", () => { + it("should add a hash to a doc", (done) => { + addHash(done); + }); + + it("should query hashes", (done) => { + queryHashes(done); + }); + }); +}); + + + diff --git a/firestore/emulator-suite.js b/firestore/emulator-suite.js index d50a58ca..03aef1e9 100644 --- a/firestore/emulator-suite.js +++ b/firestore/emulator-suite.js @@ -1,14 +1,12 @@ +import firebase from "firebase/app"; +import 'firebase/firestore'; -function onDocumentReady(firebaseApp) { - - //[START fs_emulator_connect] - // firebaseApp previously initialized using firebase.initializeApp(). - var db = firebaseApp.firestore(); +function onDocumentReady() { + // [START fs_emulator_connect] + // Firebase previously initialized using firebase.initializeApp(). + var db = firebase.firestore(); if (location.hostname === "localhost") { - db.settings({ - host: "localhost:8080", - ssl: false - }); + db.useEmulator("127.0.0.1", 8080); } // [END fs_emulator_connect] } diff --git a/firestore/index.html b/firestore/index.html index b3e00c45..3599ca13 100644 --- a/firestore/index.html +++ b/firestore/index.html @@ -7,13 +7,13 @@
- + - - + + diff --git a/firestore/package.json b/firestore/package.json new file mode 100644 index 00000000..73bbb88c --- /dev/null +++ b/firestore/package.json @@ -0,0 +1,18 @@ +{ + "name": "firestore", + "version": "1.0.0", + "scripts": { + "compile": "cp ../tsconfig.json.template ./tsconfig.json && tsc" + }, + "license": "Apache-2.0", + "dependencies": { + "firebase": "^8.10.0", + "geofire-common": "^5.1.0" + }, + "devDependencies": { + "@types/chai": "^4.2.12", + "@types/mocha": "^8.0.3", + "chai": "^4.2.0", + "mocha": "^8.1.3" + } +} diff --git a/firestore/test.firestore.js b/firestore/test.firestore.js index 1ea998a1..20032d36 100644 --- a/firestore/test.firestore.js +++ b/firestore/test.firestore.js @@ -1,3 +1,36 @@ +import firebase from 'firebase/app'; +import 'firebase/firestore'; + +const { expect } = require('chai'); + +// [START city_custom_object] +class City { + constructor (name, state, country ) { + this.name = name; + this.state = state; + this.country = country; + } + toString() { + return this.name + ', ' + this.state + ', ' + this.country; + } +} + +// Firestore data converter +var cityConverter = { + toFirestore: function(city) { + return { + name: city.name, + state: city.state, + country: city.country + }; + }, + fromFirestore: function(snapshot, options){ + const data = snapshot.data(options); + return new City(data.name, data.state, data.country); + } +}; +// [END city_custom_object] + describe("firestore", () => { var db; before(() => { @@ -8,7 +41,7 @@ describe("firestore", () => { }; var app = firebase.initializeApp(config); db = firebase.firestore(app); - //firebase.firestore.setLogLevel("debug"); + // firebase.firestore.setLogLevel("debug"); }); it("should be able to set the cache size", () => { @@ -23,12 +56,12 @@ describe("firestore", () => { firebase.initializeApp({ apiKey: '### FIREBASE API KEY ###', authDomain: '### FIREBASE AUTH DOMAIN ###', - projectId: '### CLOUD FIRESTORE PROJECT ID ###', + projectId: '### FIREBASE PROJECT ID ###', } ,"persisted_app"); // [START initialize_persistence] firebase.firestore().enablePersistence() - .catch(function(err) { + .catch((err) => { if (err.code == 'failed-precondition') { // Multiple tabs open, persistence can only be enabled // in one tab at a a time. @@ -47,7 +80,7 @@ describe("firestore", () => { var disable = // [START disable_network] firebase.firestore().disableNetwork() - .then(function() { + .then(() => { // Do offline actions // [START_EXCLUDE] console.log("Network disabled!"); @@ -58,7 +91,7 @@ describe("firestore", () => { var enable = // [START enable_network] firebase.firestore().enableNetwork() - .then(function() { + .then(() => { // Do online actions // [START_EXCLUDE] console.log("Network enabled!"); @@ -72,8 +105,8 @@ describe("firestore", () => { it("should reply with .fromCache fields", () => { // [START use_from_cache] db.collection("cities").where("state", "==", "CA") - .onSnapshot({ includeMetadataChanges: true }, function(snapshot) { - snapshot.docChanges().forEach(function(change) { + .onSnapshot({ includeMetadataChanges: true }, (snapshot) => { + snapshot.docChanges().forEach((change) => { if (change.type === "added") { console.log("New city: ", change.doc.data()); } @@ -87,24 +120,25 @@ describe("firestore", () => { describe("collection('users')", () => { it("should add data to a collection", () => { - return output = + var output = // [START add_ada_lovelace] db.collection("users").add({ first: "Ada", last: "Lovelace", born: 1815 }) - .then(function(docRef) { + .then((docRef) => { console.log("Document written with ID: ", docRef.id); }) - .catch(function(error) { + .catch((error) => { console.error("Error adding document: ", error); }); // [END add_ada_lovelace] + return output; }); it("should get all users", () => { - return output = + var output = // [START get_all_users] db.collection("users").get().then((querySnapshot) => { querySnapshot.forEach((doc) => { @@ -112,10 +146,11 @@ describe("firestore", () => { }); }); // [END get_all_users] + return output; }); it("should add data to a collection with new fields", () => { - return output = + var output = // [START add_alan_turing] // Add a second document with a generated ID. db.collection("users").add({ @@ -124,13 +159,14 @@ describe("firestore", () => { last: "Turing", born: 1912 }) - .then(function(docRef) { + .then((docRef) => { console.log("Document written with ID: ", docRef.id); }) - .catch(function(error) { + .catch((error) => { console.error("Error adding document: ", error); }); // [END add_alan_turing] + return output; }); it("should loop through a watched collection", (done) => { @@ -140,10 +176,10 @@ describe("firestore", () => { // [START listen_for_users] db.collection("users") .where("born", "<", 1900) - .onSnapshot(function(snapshot) { + .onSnapshot((snapshot) => { console.log("Current users born before 1900:"); - snapshot.forEach(function (userSnapshot) { - console.log(userSnapshot.data()) + snapshot.forEach((userSnapshot) => { + console.log(userSnapshot.data()); }); }); // [END listen_for_users] @@ -170,7 +206,7 @@ describe("firestore", () => { // [START doc_reference_alternative] var alovelaceDocumentRef = db.doc('users/alovelace'); // [END doc_reference_alternative] - }) + }); it("should reference a document in a subcollection", () => { // [START subcollection_reference] @@ -180,7 +216,7 @@ describe("firestore", () => { }); it("should set a document", () => { - return output = + var output = // [START set_document] // Add a new document in collection "cities" db.collection("cities").doc("LA").set({ @@ -188,69 +224,45 @@ describe("firestore", () => { state: "CA", country: "USA" }) - .then(function() { + .then(() => { console.log("Document successfully written!"); }) - .catch(function(error) { + .catch((error) => { console.error("Error writing document: ", error); }); // [END set_document] + return output; }); it("should set document with a custom object converter", () => { - // [START city_custom_object] - class City { - constructor (name, state, country ) { - this.name = name; - this.state = state; - this.country = country; - } - toString() { - return this.name + ', ' + this.state + ', ' + this.country; - } - } - - // Firestore data converter - cityConverter = { - toFirestore: function(city) { - return { - name: city.name, - state: city.state, - country: city.country - } - }, - fromFirestore: function(snapshot, options){ - const data = snapshot.data(options); - return new City(data.name, data.state, data.country) - } - } - // [END city_custom_object] - return output = + var output = // [START set_custom_object] // Set with cityConverter db.collection("cities").doc("LA") .withConverter(cityConverter) .set(new City("Los Angeles", "CA", "USA")); // [END set_custom_object] + return output; }); it("should get document with a custom object converter", () => { - return output = + var output = // [START get_custom_object] db.collection("cities").doc("LA") .withConverter(cityConverter) - .get().then(function(doc) { + .get().then((doc) => { if (doc.exists){ // Convert to City object - city = doc.data(); + var city = doc.data(); // Use a City instance method console.log(city.toString()); } else { - console.log("No such document!") - }}).catch(function(error) { - console.log("Error getting document:", error) + console.log("No such document!"); + }}).catch((error) => { + console.log("Error getting document:", error); }); // [END get_custom_object] + return output; }); it("should support batch writes", (done) => { @@ -271,7 +283,7 @@ describe("firestore", () => { batch.delete(laRef); // Commit the batch - batch.commit().then(function () { + batch.commit().then(() => { // [START_EXCLUDE] done(); // [END_EXCLUDE] @@ -295,7 +307,7 @@ describe("firestore", () => { } } }; - db.collection("data").doc("one").set(docData).then(function() { + db.collection("data").doc("one").set(docData).then(() => { console.log("Document successfully written!"); }); // [END data_types] @@ -327,7 +339,7 @@ describe("firestore", () => { "age": 13, "favorites.color": "Red" }) - .then(function() { + .then(() => { console.log("Document successfully updated!"); }); // [END update_document_nested] @@ -342,7 +354,7 @@ describe("firestore", () => { function deleteCollection(db, collectionRef, batchSize) { var query = collectionRef.orderBy('__name__').limit(batchSize); - return new Promise(function(resolve, reject) { + return new Promise((resolve, reject) => { deleteQueryBatch(db, query, batchSize, resolve, reject); }); } @@ -357,14 +369,14 @@ describe("firestore", () => { // Delete documents in a batch var batch = db.batch(); - snapshot.docs.forEach(function(doc) { + snapshot.docs.forEach((doc) => { batch.delete(doc.ref); }); - return batch.commit().then(function() { + return batch.commit().then(() => { return snapshot.size; }); - }).then(function(numDeleted) { + }).then((numDeleted) => { if (numDeleted < batchSize) { resolve(); return; @@ -372,7 +384,7 @@ describe("firestore", () => { // Recurse on the next process tick, to avoid // exploding the stack. - setTimeout(function() { + setTimeout(() => { deleteQueryBatch(db, query, batchSize, resolve, reject); }, 0); }) @@ -413,28 +425,29 @@ describe("firestore", () => { }); it("should set a document", () => { var data = {}; - - return output = + var output = // [START cities_document_set] db.collection("cities").doc("new-city-id").set(data); // [END cities_document_set] + return output; }); it("should add a document", () => { - return output = + var output = // [START add_document] // Add a new document with a generated id. db.collection("cities").add({ name: "Tokyo", country: "Japan" }) - .then(function(docRef) { + .then((docRef) => { console.log("Document written with ID: ", docRef.id); }) - .catch(function(error) { + .catch((error) => { console.error("Error adding document: ", error); }); // [END add_document] + return output; }); it("should add an empty a document #UNVERIFIED", () => { @@ -457,10 +470,10 @@ describe("firestore", () => { return washingtonRef.update({ capital: true }) - .then(function() { + .then(() => { console.log("Document successfully updated!"); }) - .catch(function(error) { + .catch((error) => { // The document probably doesn't exist. console.error("Error updating document: ", error); }); @@ -492,17 +505,18 @@ describe("firestore", () => { population: firebase.firestore.FieldValue.increment(50) }); // [END update_document_increment] - }) + }); it("should delete a document", () => { - return output = + var output = // [START delete_document] - db.collection("cities").doc("DC").delete().then(function() { + db.collection("cities").doc("DC").delete().then(() => { console.log("Document successfully deleted!"); - }).catch(function(error) { + }).catch((error) => { console.error("Error removing document: ", error); }); // [END delete_document] + return output; }); it("should handle transactions #FIXME #UNVERIFIED", () => { @@ -514,9 +528,9 @@ describe("firestore", () => { // Uncomment to initialize the doc. // sfDocRef.set({ population: 0 }); - return db.runTransaction(function(transaction) { + return db.runTransaction((transaction) => { // This code may get re-run multiple times if there are conflicts. - return transaction.get(sfDocRef).then(function(sfDoc) { + return transaction.get(sfDocRef).then((sfDoc) => { if (!sfDoc.exists) { throw "Document does not exist!"; } @@ -527,9 +541,9 @@ describe("firestore", () => { var newPopulation = sfDoc.data().population + 1; transaction.update(sfDocRef, { population: newPopulation }); }); - }).then(function() { + }).then(() => { console.log("Transaction successfully committed!"); - }).catch(function(error) { + }).catch((error) => { console.log("Transaction failed: ", error); }); // [END transaction] @@ -541,8 +555,8 @@ describe("firestore", () => { // Create a reference to the SF doc. var sfDocRef = db.collection("cities").doc("SF"); - db.runTransaction(function(transaction) { - return transaction.get(sfDocRef).then(function(sfDoc) { + db.runTransaction((transaction) => { + return transaction.get(sfDocRef).then((sfDoc) => { if (!sfDoc.exists) { throw "Document does not exist!"; } @@ -555,9 +569,9 @@ describe("firestore", () => { return Promise.reject("Sorry! Population is too big."); } }); - }).then(function(newPopulation) { + }).then((newPopulation) => { console.log("Population increased to ", newPopulation); - }).catch(function(err) { + }).catch((err) => { // This will be an "population is too big" error. console.error(err); }); @@ -568,14 +582,14 @@ describe("firestore", () => { // [START get_document] var docRef = db.collection("cities").doc("SF"); - docRef.get().then(function(doc) { + docRef.get().then((doc) => { if (doc.exists) { console.log("Document data:", doc.data()); } else { // doc.data() will be undefined in this case console.log("No such document!"); } - }).catch(function(error) { + }).catch((error) => { console.log("Error getting document:", error); }); // [END get_document] @@ -586,18 +600,18 @@ describe("firestore", () => { var docRef = db.collection("cities").doc("SF"); // Valid options for source are 'server', 'cache', or - // 'default'. See https://firebase.google.com/docs/reference/js/firebase.firestore.GetOptions + // 'default'. See https://firebase.google.com/docs/reference/js/v8/firebase.firestore.GetOptions // for more information. var getOptions = { source: 'cache' }; // Get a document, forcing the SDK to fetch from the offline cache. - docRef.get(getOptions).then(function(doc) { + docRef.get(getOptions).then((doc) => { // Document was found in the cache. If no cached document exists, // an error will be returned to the 'catch' block below. console.log("Cached document data:", doc.data()); - }).catch(function(error) { + }).catch((error) => { console.log("Error getting cached document:", error); }); // [END get_document_options] @@ -607,12 +621,12 @@ describe("firestore", () => { var unsub = // [START listen_document] db.collection("cities").doc("SF") - .onSnapshot(function(doc) { + .onSnapshot((doc) => { console.log("Current data: ", doc.data()); }); // [END listen_document] - setTimeout(function() { + setTimeout(() => { unsub(); done(); }, 3000); @@ -622,13 +636,13 @@ describe("firestore", () => { var unsub = // [START listen_document_local] db.collection("cities").doc("SF") - .onSnapshot(function(doc) { + .onSnapshot((doc) => { var source = doc.metadata.hasPendingWrites ? "Local" : "Server"; console.log(source, " data: ", doc.data()); }); // [END listen_document_local] - setTimeout(function() { + setTimeout(() => { unsub(); done(); }, 3000); @@ -641,59 +655,61 @@ describe("firestore", () => { .onSnapshot({ // Listen for document metadata changes includeMetadataChanges: true - }, function(doc) { + }, (doc) => { // ... }); // [END listen_with_metadata] - setTimeout(function() { + setTimeout(() => { unsub(); done(); }, 3000); }).timeout(5000); it("should get multiple documents from a collection", () => { - return output = + var output = // [START get_multiple] db.collection("cities").where("capital", "==", true) .get() - .then(function(querySnapshot) { - querySnapshot.forEach(function(doc) { + .then((querySnapshot) => { + querySnapshot.forEach((doc) => { // doc.data() is never undefined for query doc snapshots console.log(doc.id, " => ", doc.data()); }); }) - .catch(function(error) { + .catch((error) => { console.log("Error getting documents: ", error); }); // [END get_multiple] + return output; }).timeout(5000); it("should get all documents from a collection", () => { - return output = + var output = // [START get_multiple_all] - db.collection("cities").get().then(function(querySnapshot) { - querySnapshot.forEach(function(doc) { + db.collection("cities").get().then((querySnapshot) => { + querySnapshot.forEach((doc) => { // doc.data() is never undefined for query doc snapshots console.log(doc.id, " => ", doc.data()); }); }); // [END get_multiple_all] - }) + return output; + }); it("should listen on multiple documents #UNVERIFIED", (done) => { var unsubscribe = // [START listen_multiple] db.collection("cities").where("state", "==", "CA") - .onSnapshot(function(querySnapshot) { + .onSnapshot((querySnapshot) => { var cities = []; - querySnapshot.forEach(function(doc) { + querySnapshot.forEach((doc) => { cities.push(doc.data().name); }); console.log("Current cities in CA: ", cities.join(", ")); }); // [END listen_multiple] - setTimeout(function() { + setTimeout(() => { unsubscribe(); done(); }, 2500); @@ -703,8 +719,8 @@ describe("firestore", () => { var unsubscribe = // [START listen_diffs] db.collection("cities").where("state", "==", "CA") - .onSnapshot(function(snapshot) { - snapshot.docChanges().forEach(function(change) { + .onSnapshot((snapshot) => { + snapshot.docChanges().forEach((change) => { if (change.type === "added") { console.log("New city: ", change.doc.data()); } @@ -717,7 +733,7 @@ describe("firestore", () => { }); }); // [END listen_diffs] - setTimeout(function() { + setTimeout(() => { unsubscribe(); done(); }, 2500); @@ -726,7 +742,7 @@ describe("firestore", () => { it("should unsubscribe a listener", () => { // [START detach_listener] var unsubscribe = db.collection("cities") - .onSnapshot(function (){ + .onSnapshot(() => { // Respond to data // ... }); @@ -742,10 +758,10 @@ describe("firestore", () => { var unsubscribe = // [START handle_listen_errors] db.collection("cities") - .onSnapshot(function(snapshot) { - //... - }, function(error) { - //... + .onSnapshot((snapshot) => { + // ... + }, (error) => { + // ... }); // [END handle_listen_errors] unsubscribe(); @@ -787,7 +803,7 @@ describe("firestore", () => { docRef.update({ timestamp: firebase.firestore.FieldValue.serverTimestamp() }); - docRef.onSnapshot(function(snapshot) { + docRef.onSnapshot((snapshot) => { var data = snapshot.data(options); console.log( 'Timestamp: ' + data.timestamp + @@ -840,16 +856,20 @@ describe("firestore", () => { it("should handle other wheres", () => { var citiesRef = db.collection("cities"); // [START example_filters] - citiesRef.where("state", "==", "CA") - citiesRef.where("population", "<", 100000) - citiesRef.where("name", ">=", "San Francisco") + const stateQuery = citiesRef.where("state", "==", "CA"); + const populationQuery = citiesRef.where("population", "<", 100000); + const nameQuery = citiesRef.where("name", ">=", "San Francisco"); // [END example_filters] + + // [START simple_query_not_equal] + citiesRef.where("capital", "!=", false); + // [END simple_query_not_equal] }); it("should handle array-contains where", () => { var citiesRef = db.collection("cities"); // [START array_contains_filter] - citiesRef.where("regions", "array-contains", "west_coast") + citiesRef.where("regions", "array-contains", "west_coast"); // [END array_contains_filter] }); @@ -867,25 +887,29 @@ describe("firestore", () => { citiesRef.where('country', 'in', ['USA', 'Japan']); // [END in_filter] + // [START not_in_filter] + citiesRef.where('country', 'not-in', ['USA', 'Japan']); + // [END not_in_filter] + // [START in_filter_with_array] - citiesRef.where('region', 'in', - [['west_coast', 'east_coast']]); + citiesRef.where('regions', 'in', + [['west_coast'], ['east_coast']]); // [END in_filter_with_array] }); it("should handle compound queries", () => { var citiesRef = db.collection("cities"); // [START chain_filters] - citiesRef.where("state", "==", "CO").where("name", "==", "Denver"); - citiesRef.where("state", "==", "CA").where("population", "<", 1000000); + const q1 = citiesRef.where("state", "==", "CO").where("name", "==", "Denver"); + const q2 = citiesRef.where("state", "==", "CA").where("population", "<", 1000000); // [END chain_filters] }); it("should handle range filters on one field", () => { var citiesRef = db.collection("cities"); // [START valid_range_filters] - citiesRef.where("state", ">=", "CA").where("state", "<=", "IN"); - citiesRef.where("state", "==", "CA").where("population", ">", 1000000); + const q1 = citiesRef.where("state", ">=", "CA").where("state", "<=", "IN"); + const q2 = citiesRef.where("state", "==", "CA").where("population", ">", 1000000); // [END valid_range_filters] }); @@ -895,41 +919,41 @@ describe("firestore", () => { // [START invalid_range_filters] citiesRef.where("state", ">=", "CA").where("population", ">", 100000); // [END invalid_range_filters] - }).to.throwException(); + }).to.throw(); }); it("should order and limit", () => { var citiesRef = db.collection("cities"); // [START order_and_limit] - citiesRef.orderBy("name").limit(3) + citiesRef.orderBy("name").limit(3); // [END order_and_limit] }); it("should order descending", () => { var citiesRef = db.collection("cities"); // [START order_and_limit_desc] - citiesRef.orderBy("name", "desc").limit(3) + citiesRef.orderBy("name", "desc").limit(3); // [END order_and_limit_desc] }); it("should order descending by other field", () => { var citiesRef = db.collection("cities"); // [START order_multiple] - citiesRef.orderBy("state").orderBy("population", "desc") + citiesRef.orderBy("state").orderBy("population", "desc"); // [END order_multiple] }); it("should where and order by with limit", () => { var citiesRef = db.collection("cities"); // [START filter_and_order] - citiesRef.where("population", ">", 100000).orderBy("population").limit(2) + citiesRef.where("population", ">", 100000).orderBy("population").limit(2); // [END filter_and_order] }); it("should where and order on same field", () => { var citiesRef = db.collection("cities"); // [START valid_filter_and_order] - citiesRef.where("population", ">", 100000).orderBy("population") + citiesRef.where("population", ">", 100000).orderBy("population"); // [END valid_filter_and_order] }); @@ -937,22 +961,22 @@ describe("firestore", () => { var citiesRef = db.collection("cities"); expect(() => { // [START invalid_filter_and_order] - citiesRef.where("population", ">", 100000).orderBy("country") + citiesRef.where("population", ">", 100000).orderBy("country"); // [END invalid_filter_and_order] - }).to.throwException(); + }).to.throw; }); it("should handle startAt", () => { var citiesRef = db.collection("cities"); // [START order_and_start] - citiesRef.orderBy("population").startAt(1000000) + citiesRef.orderBy("population").startAt(1000000); // [END order_and_start] }); it("should handle endAt", () => { var citiesRef = db.collection("cities"); // [START order_and_end] - citiesRef.orderBy("population").endAt(1000000) + citiesRef.orderBy("population").endAt(1000000); // [END order_and_end] }); @@ -960,7 +984,7 @@ describe("firestore", () => { // [START start_doc] var citiesRef = db.collection("cities"); - return citiesRef.doc("SF").get().then(function(doc) { + return citiesRef.doc("SF").get().then((doc) => { // Get all cities with a population bigger than San Francisco var biggerThanSf = citiesRef .orderBy("population") @@ -977,23 +1001,23 @@ describe("firestore", () => { db.collection("cities") .orderBy("name") .orderBy("state") - .startAt("Springfield") + .startAt("Springfield"); // Will return "Springfield, Missouri" and "Springfield, Wisconsin" db.collection("cities") .orderBy("name") .orderBy("state") - .startAt("Springfield", "Missouri") + .startAt("Springfield", "Missouri"); // [END start_multiple_orderby] }); - it("shoud paginate", () => { + it("should paginate", () => { // [START paginate] var first = db.collection("cities") .orderBy("population") .limit(25); - return first.get().then(function (documentSnapshots) { + return first.get().then((documentSnapshots) => { // Get the last visible document var lastVisible = documentSnapshots.docs[documentSnapshots.docs.length-1]; console.log("last", lastVisible); @@ -1063,8 +1087,8 @@ describe("firestore", () => { it("should query a collection group", () => { // [START fs_collection_group_query] var museums = db.collectionGroup('landmarks').where('type', '==', 'museum'); - museums.get().then(function (querySnapshot) { - querySnapshot.forEach(function (doc) { + museums.get().then((querySnapshot) => { + querySnapshot.forEach((doc) => { console.log(doc.id, ' => ', doc.data()); }); }); @@ -1082,8 +1106,8 @@ describe("firestore", () => { var ratingRef = restaurantRef.collection('ratings').doc(); // In a transaction, add the new rating and update the aggregate totals - return db.runTransaction(transaction => { - return transaction.get(restaurantRef).then(res => { + return db.runTransaction((transaction) => { + return transaction.get(restaurantRef).then((res) => { if (!res.exists) { throw "Document does not exist!"; } @@ -1101,7 +1125,7 @@ describe("firestore", () => { avgRating: newAvgRating }); transaction.set(ratingRef, { rating: rating }); - }) + }); }); } // [END add_rating_transaction] @@ -1112,8 +1136,8 @@ describe("firestore", () => { name: 'Arinell Pizza', avgRating: 4.63, numRatings: 683 - }).then(res => { - return addRating(ref, 5.0) + }).then((res) => { + return addRating(ref, 5.0); }); }); }); diff --git a/firestore/test.solution-aggregation.js b/firestore/test.solution-aggregation.js index 024a3a58..571498a9 100644 --- a/firestore/test.solution-aggregation.js +++ b/firestore/test.solution-aggregation.js @@ -1,9 +1,12 @@ +import firebase from 'firebase/app'; +import 'firebase/firestore'; + // [START sample_doc] var arinellDoc = { name: 'Arinell Pizza', avgRating: 4.65, numRatings: 683 -} +}; // [END sample_doc] describe("firestore-solution-arrays", () => { @@ -28,8 +31,8 @@ describe("firestore-solution-arrays", () => { db.collection("restaurants") .doc("arinell-pizza") .collection("ratings") - .get() + .get(); // [END get_collection_ratings] - }) + }); }); }); diff --git a/firestore/test.solution-arrays.js b/firestore/test.solution-arrays.js index d233b0c6..8a1ee65b 100644 --- a/firestore/test.solution-arrays.js +++ b/firestore/test.solution-arrays.js @@ -1,4 +1,7 @@ -let postsWithArray = [ +import firebase from 'firebase/app'; +import 'firebase/firestore'; + +const postsWithArray = [ // [START post_with_array] // Sample document in the 'posts' collection. { @@ -12,7 +15,7 @@ let postsWithArray = [ // [END post_with_array] ]; -let postsWithMap = [ +const postsWithMap = [ // [START post_with_map] // Sample document in the 'posts' collection { @@ -26,7 +29,7 @@ let postsWithMap = [ // [END post_with_map] ]; -let postsWithMapAdvanced = [ +const postsWithMapAdvanced = [ // [START post_with_map_advanced] // The value of each entry in 'categories' is a unix timestamp { @@ -38,7 +41,7 @@ let postsWithMapAdvanced = [ } } // [END post_with_map_advanced] -] +]; describe("firestore-solution-arrays", () => { var db; diff --git a/firestore/test.solution-bundles.js b/firestore/test.solution-bundles.js new file mode 100644 index 00000000..4bece2c8 --- /dev/null +++ b/firestore/test.solution-bundles.js @@ -0,0 +1,48 @@ +// [START fs_bundle_load] +// If you are using module bundlers. +import firebase from "firebase/app"; +import "firebase/firestore"; +import "firebase/firestore/bundle"; // This line enables bundle loading as a side effect. + +// [START_EXCLUDE] +/** + * @type firebase.firestore.Firestore + */ +var db; +// [END_EXCLUDE] + +async function fetchFromBundle() { + // Fetch the bundle from Firebase Hosting, if the CDN cache is hit the 'X-Cache' + // response header will be set to 'HIT' + const resp = await fetch('/createBundle'); + + // Load the bundle contents into the Firestore SDK + await db.loadBundle(resp.body); + + // Query the results from the cache + // Note: omitting "source: cache" will query the Firestore backend. + const query = await db.namedQuery('latest-stories-query'); + const storiesSnap = await query.get({ source: 'cache' }); + + // Use the results + // ... +} +// [END fs_bundle_load] + +describe("firestore-solution-bundles", () => { + before(() => { + var config = { + apiKey: "AIzaSyArvVh6VSdXicubcvIyuB-GZs8ua0m0DTI", + authDomain: "firestorequickstarts.firebaseapp.com", + projectId: "firestorequickstarts", + }; + var app = firebase.initializeApp(config, "solution-bundles"); + db = firebase.firestore(app); + }); + + describe("solution-bundles", () => { + it("should fetch a bundle", (done) => { + fetchFromBundle().finally(done); + }); + }); +}); diff --git a/firestore/test.solution-counters.js b/firestore/test.solution-counters.js index e831807e..c7c51cff 100644 --- a/firestore/test.solution-counters.js +++ b/firestore/test.solution-counters.js @@ -1,3 +1,6 @@ +import firebase from 'firebase/app'; +import 'firebase/firestore'; + var db; // [START create_counter] @@ -9,7 +12,7 @@ function createCounter(ref, num_shards) { // Initialize each shard with count=0 for (let i = 0; i < num_shards; i++) { - let shardRef = ref.collection('shards').doc(i.toString()); + const shardRef = ref.collection('shards').doc(i.toString()); batch.set(shardRef, { count: 0 }); } @@ -19,7 +22,7 @@ function createCounter(ref, num_shards) { // [END create_counter] // [START increment_counter] -function incrementCounter(db, ref, num_shards) { +function incrementCounter(ref, num_shards) { // Select a shard of the counter at random const shard_id = Math.floor(Math.random() * num_shards).toString(); const shard_ref = ref.collection('shards').doc(shard_id); @@ -32,9 +35,9 @@ function incrementCounter(db, ref, num_shards) { // [START get_count] function getCount(ref) { // Sum the count of each shard in the subcollection - return ref.collection('shards').get().then(snapshot => { + return ref.collection('shards').get().then((snapshot) => { let total_count = 0; - snapshot.forEach(doc => { + snapshot.forEach((doc) => { total_count += doc.data().count; }); @@ -62,17 +65,17 @@ describe("firestore-solution-counters", () => { it("should increment a counter", () => { // Create a counter, then increment it - let ref = db.collection('counters').doc(); + const ref = db.collection('counters').doc(); return createCounter(ref, 10).then(() => { - return incrementCounter(db, ref, 10); + return incrementCounter(ref, 10); }); }); it("should get the count of a counter", () => { // Create a counter, increment it, then get the count - let ref = db.collection('counters').doc(); + const ref = db.collection('counters').doc(); return createCounter(ref, 10).then(() => { - return incrementCounter(db, ref, 10); + return incrementCounter(ref, 10); }).then(() => { return getCount(ref); }); diff --git a/firestore/test.solution-geoqueries.js b/firestore/test.solution-geoqueries.js new file mode 100644 index 00000000..61cb13b5 --- /dev/null +++ b/firestore/test.solution-geoqueries.js @@ -0,0 +1,103 @@ +import firebase from 'firebase/app'; +import 'firebase/firestore'; + +const geofire = require('geofire-common'); + +/** + * @type firebase.firestore.Firestore + */ +var db; + +function addHash(done) { + // [START fs_geo_add_hash] + // Compute the GeoHash for a lat/lng point + const lat = 51.5074; + const lng = 0.1278; + const hash = geofire.geohashForLocation([lat, lng]); + + // Add the hash and the lat/lng to the document. We will use the hash + // for queries and the lat/lng for distance comparisons. + const londonRef = db.collection('cities').doc('LON'); + londonRef.update({ + geohash: hash, + lat: lat, + lng: lng + }).then(() => { + // [START_EXCLUDE] + done(); + // [END_EXCLUDE] + }); + // [END fs_geo_add_hash] +} + +function queryHashes(done) { + // [START fs_geo_query_hashes] + // Find cities within 50km of London + const center = [51.5074, 0.1278]; + const radiusInM = 50 * 1000; + + // Each item in 'bounds' represents a startAt/endAt pair. We have to issue + // a separate query for each pair. There can be up to 9 pairs of bounds + // depending on overlap, but in most cases there are 4. + const bounds = geofire.geohashQueryBounds(center, radiusInM); + const promises = []; + for (const b of bounds) { + const q = db.collection('cities') + .orderBy('geohash') + .startAt(b[0]) + .endAt(b[1]); + + promises.push(q.get()); + } + + // Collect all the query results together into a single list + Promise.all(promises).then((snapshots) => { + const matchingDocs = []; + + for (const snap of snapshots) { + for (const doc of snap.docs) { + const lat = doc.get('lat'); + const lng = doc.get('lng'); + + // We have to filter out a few false positives due to GeoHash + // accuracy, but most will match + const distanceInKm = geofire.distanceBetween([lat, lng], center); + const distanceInM = distanceInKm * 1000; + if (distanceInM <= radiusInM) { + matchingDocs.push(doc); + } + } + } + + return matchingDocs; + }).then((matchingDocs) => { + // Process the matching documents + // [START_EXCLUDE] + done(matchingDocs); + // [END_EXCLUDE] + }); + + // [END fs_geo_query_hashes] +} + +describe("firestore-solution-geoqueries", () => { + before(() => { + var config = { + apiKey: "AIzaSyArvVh6VSdXicubcvIyuB-GZs8ua0m0DTI", + authDomain: "firestorequickstarts.firebaseapp.com", + projectId: "firestorequickstarts", + }; + var app = firebase.initializeApp(config, "solution-geoqueries"); + db = firebase.firestore(app); + }); + + describe("solution-geoqueries", () => { + it("should add a hash to a doc", (done) => { + addHash(done); + }); + + it("should query hashes", (done) => { + queryHashes(done); + }); + }); +}); diff --git a/functions-next/.gitignore b/functions-next/.gitignore new file mode 100644 index 00000000..1521c8b7 --- /dev/null +++ b/functions-next/.gitignore @@ -0,0 +1 @@ +dist diff --git a/functions-next/callable.js b/functions-next/callable.js new file mode 100644 index 00000000..5ebc03d9 --- /dev/null +++ b/functions-next/callable.js @@ -0,0 +1,62 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +export function initialize() { + // [START fb_functions_initialize] + const { initializeApp } = require("firebase/app"); + const { getFunctions } = require("firebase/functions"); + + initializeApp({ + // Your Firebase Web SDK configuration + // [START_EXCLUDE] + projectId: "", + apiKey: "", + // [END_EXCLUDE] + }); + + const functions = getFunctions(); + // [END fb_functions_initialize] +} + +export function callAddMessage() { + const messageText = "Hello, World!"; + + // [START fb_functions_call_add_message] + const { getFunctions, httpsCallable } = require("firebase/functions"); + + const functions = getFunctions(); + const addMessage = httpsCallable(functions, 'addMessage'); + addMessage({ text: messageText }) + .then((result) => { + // Read result of the Cloud Function. + /** @type {any} */ + const data = result.data; + const sanitizedMessage = data.text; + }); + // [END fb_functions_call_add_message] +} + +export function callAddMessageError(firebaseApp) { + const messageText = "Hello, World!"; + + // [START fb_functions_call_add_message_error] + const { getFunctions, httpsCallable } = require("firebase/functions"); + + const functions = getFunctions(); + const addMessage = httpsCallable(functions, 'addMessage'); + addMessage({ text: messageText }) + .then((result) => { + // Read result of the Cloud Function. + /** @type {any} */ + const data = result.data; + const sanitizedMessage = data.text; + }) + .catch((error) => { + // Getting the Error details. + const code = error.code; + const message = error.message; + const details = error.details; + // ... + }); + // [END fb_functions_call_add_message_error] +} diff --git a/functions-next/emulator-suite.js b/functions-next/emulator-suite.js new file mode 100644 index 00000000..db7fd49d --- /dev/null +++ b/functions-next/emulator-suite.js @@ -0,0 +1,36 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +import { initializeApp } from "firebase/app"; + +initializeApp({ + projectId: '### PROJECT ID ###', + apiKey: '### FIREBASE API KEY ###', + authDomain: '### FIREBASE AUTH DOMAIN ###', +}); + +export function emulatorSettings() { + // [START fb_functions_emulator_connect] + const { getApp } = require("firebase/app"); + const { getFunctions, connectFunctionsEmulator } = require("firebase/functions"); + + const functions = getFunctions(getApp()); + connectFunctionsEmulator(functions, "127.0.0.1", 5001); + // [END fb_functions_emulator_connect] +} + +export async function callFunction() { + // [START fb_functions_callable_call] + const { getApp } = require("firebase/app"); + const { getFunctions, httpsCallable } = require("firebase/functions"); + + const functions = getFunctions(getApp()); + const addMessage = httpsCallable(functions, 'addMessage'); + + const result = await addMessage({ text: ''}); + /** @type {any} */ + const data = result.data; + const sanitizedMessage = data.text; + // ... + // [END fb_functions_callable_call] +} diff --git a/functions-next/package.json b/functions-next/package.json new file mode 100644 index 00000000..14f097ee --- /dev/null +++ b/functions-next/package.json @@ -0,0 +1,11 @@ +{ + "name": "functions-next", + "version": "1.0.0", + "scripts": { + "compile": "cp ../tsconfig.json.template ./tsconfig.json && tsc" + }, + "license": "Apache-2.0", + "dependencies": { + "firebase": "^10.0.0" + } +} diff --git a/functions/callable.js b/functions/callable.js new file mode 100644 index 00000000..66b576d5 --- /dev/null +++ b/functions/callable.js @@ -0,0 +1,51 @@ +// [START fb_functions_imports] +import firebase from "firebase/app"; +import "firebase/functions"; +// [END fb_functions_imports] + +function initialize() { + // [START fb_functions_initialize] + firebase.initializeApp({ + // Your Firebase Web SDK configuration + // [START_EXCLUDE] + projectId: "", + apiKey: "", + // [END_EXCLUDE] + }); + + const functions = firebase.functions(); + // [END fb_functions_initialize] +} + +function callAddMessage() { + const messageText = "Hello, World!"; + + // [START fb_functions_call_add_message] + var addMessage = firebase.functions().httpsCallable('addMessage'); + addMessage({ text: messageText }) + .then((result) => { + // Read result of the Cloud Function. + var sanitizedMessage = result.data.text; + }); + // [END fb_functions_call_add_message] +} + +function callAddMessageError() { + const messageText = "Hello, World!"; + + // [START fb_functions_call_add_message_error] + var addMessage = firebase.functions().httpsCallable('addMessage'); + addMessage({ text: messageText }) + .then((result) => { + // Read result of the Cloud Function. + var sanitizedMessage = result.data.text; + }) + .catch((error) => { + // Getting the Error details. + var code = error.code; + var message = error.message; + var details = error.details; + // ... + }); + // [END fb_functions_call_add_message_error] +} diff --git a/functions/emulator-suite.js b/functions/emulator-suite.js index dd5456a3..e0413370 100644 --- a/functions/emulator-suite.js +++ b/functions/emulator-suite.js @@ -1,7 +1,8 @@ +import firebase from "firebase/app"; +import "firebase/functions"; function emulatorSettings() { - - // [START functions_emulator_connect] - firebase.functions().useFunctionsEmulator("http://localhost:5001") - // [END functions_emulator_connect] + // [START fb_functions_emulator_connect] + firebase.functions().useEmulator("127.0.0.1", 5001); + // [END fb_functions_emulator_connect] } diff --git a/functions/package.json b/functions/package.json new file mode 100644 index 00000000..653086c9 --- /dev/null +++ b/functions/package.json @@ -0,0 +1,11 @@ +{ + "name": "functions", + "version": "1.0.0", + "scripts": { + "compile": "cp ../tsconfig.json.template ./tsconfig.json && tsc" + }, + "license": "Apache-2.0", + "dependencies": { + "firebase": "^8.10.0" + } +} diff --git a/installations/index.js b/installations/index.js new file mode 100644 index 00000000..1764ea74 --- /dev/null +++ b/installations/index.js @@ -0,0 +1,48 @@ +import firebase from "firebase/app"; +import "firebase/installations"; + +async function deleteInstallation() { + try { + // [START delete_installation] + await firebase.installations().delete(); + // [END delete_installation] + } catch (err) { + console.error('Unable to delete installation: ', err); + } +} + +async function getInstallationId() { + try { + // [START get_installation_id] + const installationId = await firebase.installations().getId(); + console.log(installationId); + // [END get_installation_id] + } catch (err) { + console.error('Unable to get Installation ID: ', err); + } +} + +async function getAuthenticationToken() { + try { + // [START get_auth_token] + const installationToken = await firebase.installations() + .getToken(/* forceRefresh */ true); + console.log(installationToken); + // [END get_auth_token] + } catch (err) { + console.error('Unable to get auth token: ', err); + } +} + +async function setOnIdChangeHandler() { + try { + // [START set_id_change_handler] + await firebase.installations().onIdChange((newId) => { + console.log(newId); + // TODO: Handle new installation ID. + }); + // [END set_id_change_handler] + } catch (err) { + console.error('Unable to set ID change handler: ', err); + } +} diff --git a/installations/package.json b/installations/package.json new file mode 100644 index 00000000..1395fca3 --- /dev/null +++ b/installations/package.json @@ -0,0 +1,9 @@ +{ + "name": "installations", + "version": "1.0.0", + "scripts": {}, + "license": "Apache 2.0", + "dependencies": { + "firebase": "^8.10.0" + } +} diff --git a/messaging-next/index.js b/messaging-next/index.js new file mode 100644 index 00000000..491ff84e --- /dev/null +++ b/messaging-next/index.js @@ -0,0 +1,77 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +function getMessagingObject() { + // [START messaging_get_messaging_object] + const { getMessaging } = require("firebase/messaging"); + + const messaging = getMessaging(); + // [END messaging_get_messaging_object] +} + +function receiveMessage() { + // [START messaging_receive_message] + // Handle incoming messages. Called when: + // - a message is received while the app has focus + // - the user clicks on an app notification created by a service worker + // `messaging.onBackgroundMessage` handler. + const { getMessaging, onMessage } = require("firebase/messaging"); + + const messaging = getMessaging(); + onMessage(messaging, (payload) => { + console.log('Message received. ', payload); + // ... + }); + // [END messaging_receive_message] +} + +function getToken() { + // [START messaging_get_token] + const { getMessaging, getToken } = require("firebase/messaging"); + + // Get registration token. Initially this makes a network call, once retrieved + // subsequent calls to getToken will return from cache. + const messaging = getMessaging(); + getToken(messaging, { vapidKey: '' }).then((currentToken) => { + if (currentToken) { + // Send the token to your server and update the UI if necessary + // ... + } else { + // Show permission request UI + console.log('No registration token available. Request permission to generate one.'); + // ... + } + }).catch((err) => { + console.log('An error occurred while retrieving token. ', err); + // ... + }); + // [END messaging_get_token] +} + +function requestPermission() { + // [START messaging_request_permission] + Notification.requestPermission().then((permission) => { + if (permission === 'granted') { + console.log('Notification permission granted.'); + // TODO(developer): Retrieve a registration token for use with FCM. + // ... + } else { + console.log('Unable to get permission to notify.'); + } + }); + // [END messaging_request_permission] +} + +function deleteToken() { + // [START messaging_delete_token] + const { getMessaging, deleteToken } = require("firebase/messaging"); + + const messaging = getMessaging(); + deleteToken(messaging).then(() => { + console.log('Token deleted.'); + // ... + }).catch((err) => { + console.log('Unable to delete token. ', err); + }); + // [END messaging_delete_token] +} diff --git a/messaging-next/package.json b/messaging-next/package.json new file mode 100644 index 00000000..e4887a2a --- /dev/null +++ b/messaging-next/package.json @@ -0,0 +1,11 @@ +{ + "name": "messaging-next", + "version": "1.0.0", + "scripts": { + "compile": "cp ../tsconfig.json.template ./tsconfig.json && tsc" + }, + "license": "Apache-2.0", + "dependencies": { + "firebase": "^10.0.0" + } +} diff --git a/messaging-next/service-worker.js b/messaging-next/service-worker.js new file mode 100644 index 00000000..55f07524 --- /dev/null +++ b/messaging-next/service-worker.js @@ -0,0 +1,60 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +import { initializeApp } from "firebase/app"; + +const firebaseApp = initializeApp({ + apiKey: '### FIREBASE API KEY ###', + appId: '### FIREBASE APP ID ###', + projectId: '### FIREBASE PROJECT ID ###' +}); + +// See: https://github.com/microsoft/TypeScript/issues/14877 +/** @type {ServiceWorkerGlobalScope} */ +let self; + +function initInSw() { + // [START messaging_init_in_sw] + const { initializeApp } = require("firebase/app"); + const { getMessaging } = require("firebase/messaging/sw"); + + // Initialize the Firebase app in the service worker by passing in + // your app's Firebase config object. + // https://firebase.google.com/docs/web/setup#config-object + const firebaseApp = initializeApp({ + apiKey: 'api-key', + authDomain: 'project-id.firebaseapp.com', + databaseURL: 'https://project-id.firebaseio.com', + projectId: 'project-id', + storageBucket: 'project-id.appspot.com', + messagingSenderId: 'sender-id', + appId: 'app-id', + measurementId: 'G-measurement-id', + }); + + // Retrieve an instance of Firebase Messaging so that it can handle background + // messages. + const messaging = getMessaging(firebaseApp); + // [END messaging_init_in_sw] +} + +function onBackgroundMessage() { + // [START messaging_on_background_message] + const { getMessaging } = require("firebase/messaging/sw"); + const { onBackgroundMessage } = require("firebase/messaging/sw"); + + const messaging = getMessaging(); + onBackgroundMessage(messaging, (payload) => { + console.log('[firebase-messaging-sw.js] Received background message ', payload); + // Customize notification here + const notificationTitle = 'Background Message Title'; + const notificationOptions = { + body: 'Background Message body.', + icon: '/firebase-logo.png' + }; + + self.registration.showNotification(notificationTitle, + notificationOptions); + }); + // [END messaging_on_background_message] +} diff --git a/messaging/index.js b/messaging/index.js new file mode 100644 index 00000000..073d8c2a --- /dev/null +++ b/messaging/index.js @@ -0,0 +1,70 @@ +import firebase from "firebase/app"; +import "firebase/messaging"; + +function getMessagingObject() { + // [START messaging_get_messaging_object] + const messaging = firebase.messaging(); + // [END messaging_get_messaging_object] +} + +function receiveMessage() { + const messaging = firebase.messaging(); + // [START messaging_receive_message] + // Handle incoming messages. Called when: + // - a message is received while the app has focus + // - the user clicks on an app notification created by a service worker + // `messaging.onBackgroundMessage` handler. + messaging.onMessage((payload) => { + console.log('Message received. ', payload); + // ... + }); + // [END messaging_receive_message] +} + +function getToken() { + const messaging = firebase.messaging(); + // [START messaging_get_token] + // Get registration token. Initially this makes a network call, once retrieved + // subsequent calls to getToken will return from cache. + messaging.getToken({ vapidKey: '' }).then((currentToken) => { + if (currentToken) { + // Send the token to your server and update the UI if necessary + // ... + } else { + // Show permission request UI + console.log('No registration token available. Request permission to generate one.'); + // ... + } + }).catch((err) => { + console.log('An error occurred while retrieving token. ', err); + // ... + }); + // [END messaging_get_token] +} + +function requestPermission() { + // [START messaging_request_permission] + Notification.requestPermission().then((permission) => { + if (permission === 'granted') { + console.log('Notification permission granted.'); + // TODO(developer): Retrieve a registration token for use with FCM. + // ... + } else { + console.log('Unable to get permission to notify.'); + } + }); + // [END messaging_request_permission] +} + +function deleteToken() { + const messaging = firebase.messaging(); + + // [START messaging_delete_token] + messaging.deleteToken().then(() => { + console.log('Token deleted.'); + // ... + }).catch((err) => { + console.log('Unable to delete token. ', err); + }); + // [END messaging_delete_token] +} diff --git a/messaging/package.json b/messaging/package.json new file mode 100644 index 00000000..96a5720d --- /dev/null +++ b/messaging/package.json @@ -0,0 +1,11 @@ +{ + "name": "messaging", + "version": "1.0.0", + "scripts": { + "compile": "cp ../tsconfig.json.template ./tsconfig.json && tsc" + }, + "license": "Apache-2.0", + "dependencies": { + "firebase": "^8.10.0" + } +} diff --git a/messaging/service-worker.js b/messaging/service-worker.js new file mode 100644 index 00000000..1a202159 --- /dev/null +++ b/messaging/service-worker.js @@ -0,0 +1,56 @@ +import firebase from 'firebase/app'; +import 'firebase/messaging'; + +// See: https://github.com/microsoft/TypeScript/issues/14877 +/** @type {ServiceWorkerGlobalScope} */ +let self; + +function initInSw() { + // [START messaging_init_in_sw] + // Give the service worker access to Firebase Messaging. + // Note that you can only use Firebase Messaging here. Other Firebase libraries + // are not available in the service worker. + // Replace 10.13.2 with latest version of the Firebase JS SDK. + importScripts('https://www.gstatic.com/firebasejs/10.13.2/firebase-app-compat.js'); + importScripts('https://www.gstatic.com/firebasejs/10.13.2/firebase-messaging-compat.js'); + + // Initialize the Firebase app in the service worker by passing in + // your app's Firebase config object. + // https://firebase.google.com/docs/web/setup#config-object + firebase.initializeApp({ + apiKey: 'api-key', + authDomain: 'project-id.firebaseapp.com', + databaseURL: 'https://project-id.firebaseio.com', + projectId: 'project-id', + storageBucket: 'project-id.appspot.com', + messagingSenderId: 'sender-id', + appId: 'app-id', + measurementId: 'G-measurement-id', + }); + + // Retrieve an instance of Firebase Messaging so that it can handle background + // messages. + const messaging = firebase.messaging(); + // [END messaging_init_in_sw] +} + +function onBackgroundMessage() { + const messaging = firebase.messaging(); + + // [START messaging_on_background_message] + messaging.onBackgroundMessage((payload) => { + console.log( + '[firebase-messaging-sw.js] Received background message ', + payload + ); + // Customize notification here + const notificationTitle = 'Background Message Title'; + const notificationOptions = { + body: 'Background Message body.', + icon: '/firebase-logo.png' + }; + + self.registration.showNotification(notificationTitle, notificationOptions); + }); + // [END messaging_on_background_message] +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..cb7d9157 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "snippets-web", + "version": "1.0.0", + "scripts": { + "snippets": "rimraf snippets && ts-node scripts/separate-snippets.ts", + "lint": "git ls-files | grep -v 'snippets/' | grep '.js$' | xargs npx eslint", + "format": "npm run lint -- --fix", + "bootstrap": "pnpm recursive install", + "compile": "pnpm recursive run compile --workspace-concurrency=4" + }, + "license": "Apache-2.0", + "devDependencies": { + "@types/node": "^16.7.10", + "eslint": "^7.32.0", + "pnpm": "^6.14.6", + "rimraf": "^3.0.2", + "ts-node": "^10.2.1", + "typescript": "^4.4.2" + } +} diff --git a/perf-next/index.js b/perf-next/index.js new file mode 100644 index 00000000..e41aa1b0 --- /dev/null +++ b/perf-next/index.js @@ -0,0 +1,111 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +const perf = getInstance(); + +function intialize() { + // [START perf_import_app] + const { initializeApp } = require("firebase/app"); + // [END perf_import_app] + // [START perf_import] + const { getPerformance } = require("firebase/performance"); + // [END perf_import] + + // [START perf_initialize_app] + // TODO: Replace the following with your app's Firebase project configuration + // See: https://firebase.google.com/docs/web/learn-more#config-object + const firebaseConfig = { + // ... + }; + + // Initialize Firebase + const app = initializeApp(firebaseConfig); + // [END perf_initialize_app] + + // [START perf_singleton] + // Initialize Performance Monitoring and get a reference to the service + const perf = getPerformance(app); + // [END perf_singleton] +} + +export function getInstance() { + // [START perf_get_instance] + const { getPerformance } = require("firebase/performance"); + const perf = getPerformance(); + // [END perf_get_instance] + + return perf; +} + +export function addCustomTrace() { + // [START perf_add_custom_trace] + const { trace } = require("firebase/performance"); + + const t = trace(perf, "CUSTOM_TRACE_NAME"); + t.start(); + + // Code that you want to trace + // ... + + t.stop(); + // [END perf_add_custom_trace] +} + +export function userTimingMarks() { + // [START perf_user_timing_marks] + const performance = window.performance; + + performance.mark("measurementStart"); + + // Code that you want to trace + // ... + + performance.mark("measurementStop"); + performance.measure("customTraceName", "measurementStart", "measurementStop"); + // [END perf_user_timing_marks] +} + +export function addCustomAttributes() { + // [START perf_add_custom_attributes] + const { trace } = require("firebase/performance"); + + const t = trace(perf, "test_trace"); + t.putAttribute("experiment", "A"); + + // Update scenario + t.putAttribute("experiment", "B"); + + // Reading scenario + const experimentValue = t.getAttribute("experiment"); + + // Delete scenario + t.removeAttribute("experiment"); + + // Read attributes + const traceAttributes = t.getAttributes(); + // [END perf_add_custom_attributes] +} + +export function addCustomMetrics() { + async function retrieveInventory(inventoryIds) { + return {}; + } + + // [START perf_add_custom_metrics] + const { trace } = require("firebase/performance"); + + async function getInventory(inventoryIds) { + const t = trace(perf, "inventoryRetrieval"); + + // Tracks the number of IDs fetched (the metric could help you to optimize in the future) + t.incrementMetric("numberOfIds", inventoryIds.length); + + // Measures the time it takes to request inventory based on the amount of inventory + t.start(); + const inventoryData = await retrieveInventory(inventoryIds); + t.stop(); + + return inventoryData; + } + // [END perf_add_custom_metrics] +} diff --git a/perf-next/package.json b/perf-next/package.json new file mode 100644 index 00000000..688f6db0 --- /dev/null +++ b/perf-next/package.json @@ -0,0 +1,11 @@ +{ + "name": "perf-next", + "version": "1.0.0", + "scripts": { + "compile": "cp ../tsconfig.json.template ./tsconfig.json && tsc" + }, + "license": "Apache-2.0", + "dependencies": { + "firebase": "^10.0.0" + } +} diff --git a/perf/index.js b/perf/index.js new file mode 100644 index 00000000..c5ae8b22 --- /dev/null +++ b/perf/index.js @@ -0,0 +1,93 @@ +// [START perf_import_app] +import firebase from "firebase/app"; +// [END perf_import_app] +// [START perf_import] +import "firebase/performance"; +// [END perf_import] + +const perf = firebase.performance(); + +function intialize() { + // [START perf_initialize_app] + // TODO: Replace the following with your app's Firebase project configuration + // See: https://firebase.google.com/docs/web/learn-more#config-object + const firebaseConfig = { + // ... + }; + + // Initialize Firebase + firebase.initializeApp(firebaseConfig); + // [END perf_initialize_app] + + // [START perf_singleton] + // Initialize Performance Monitoring and get a reference to the service + const perf = firebase.performance(); + // [END perf_singleton] +} + +function addCustomTrace() { + // [START perf_add_custom_trace] + const trace = perf.trace("CUSTOM_TRACE_NAME"); + trace.start(); + + // Code that you want to trace + // ... + + trace.stop(); + // [END perf_add_custom_trace] +} + +function userTimingMarks() { + // [START perf_user_timing_marks] + const performance = window.performance; + + performance.mark("measurementStart"); + + // Code that you want to trace + // ... + + performance.mark("measurementStop"); + performance.measure("customTraceName", "measurementStart", "measurementStop"); + // [END perf_user_timing_marks] +} + +function addCustomAttributes() { + // [START perf_add_custom_attributes] + const trace = perf.trace("test_trace"); + trace.putAttribute("experiment", "A"); + + // Update scenario + trace.putAttribute("experiment", "B"); + + // Reading scenario + const experimentValue = trace.getAttribute("experiment"); + + // Delete scenario + trace.removeAttribute("experiment"); + + // Read attributes + const traceAttributes = trace.getAttributes(); + // [END perf_add_custom_attributes] +} + +function addCustomMetrics() { + async function retrieveInventory(inventoryIds) { + return {}; + } + + // [START perf_add_custom_metrics] + async function getInventory(inventoryIds) { + const trace = perf.trace("inventoryRetrieval"); + + // Tracks the number of IDs fetched (the metric could help you to optimize in the future) + trace.incrementMetric("numberOfIds", inventoryIds.length); + + // Measures the time it takes to request inventory based on the amount of inventory + trace.start(); + const inventoryData = await retrieveInventory(inventoryIds); + trace.stop(); + + return inventoryData; + } + // [END perf_add_custom_metrics] +} diff --git a/perf/package.json b/perf/package.json new file mode 100644 index 00000000..61906b8e --- /dev/null +++ b/perf/package.json @@ -0,0 +1,11 @@ +{ + "name": "perf", + "version": "1.0.0", + "scripts": { + "compile": "cp ../tsconfig.json.template ./tsconfig.json && tsc" + }, + "license": "Apache-2.0", + "dependencies": { + "firebase": "^8.10.0" + } +} diff --git a/placeholder.js b/placeholder.js new file mode 100644 index 00000000..3d1a70e5 --- /dev/null +++ b/placeholder.js @@ -0,0 +1,6 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +// [START coming_soon] +// TODO: Snippet coming soon! +// [END coming_soon] diff --git a/remoteconfig-next/index.js b/remoteconfig-next/index.js new file mode 100644 index 00000000..287f3c99 --- /dev/null +++ b/remoteconfig-next/index.js @@ -0,0 +1,53 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +function getInstance() { + // [START rc_get_instance] + const { getRemoteConfig } = require("firebase/remote-config"); + + const remoteConfig = getRemoteConfig(); + // [END rc_get_instance] + + return remoteConfig; +} + +function setMinimumFetchTime() { + const remoteConfig = getInstance(); + // [START rc_set_minimum_fetch_time] + // The default and recommended production fetch interval for Remote Config is 12 hours + remoteConfig.settings.minimumFetchIntervalMillis = 3600000; + // [END rc_set_minimum_fetch_time] +} + +function setDefaultValues() { + const remoteConfig = getInstance(); + // [START rc_set_default_values] + remoteConfig.defaultConfig = { + "welcome_message": "Welcome" + }; + // [END rc_set_default_values] +} + +function getValues() { + const remoteConfig = getInstance(); + // [START rc_get_values] + const { getValue } = require("firebase/remote-config"); + + const val = getValue(remoteConfig, "welcome_messsage"); + // [END rc_get_values] +} + +function fetchConfigCallback() { + const remoteConfig = getInstance(); + // [START rc_fetch_config_callback] + const { fetchAndActivate } = require("firebase/remote-config"); + + fetchAndActivate(remoteConfig) + .then(() => { + // ... + }) + .catch((err) => { + // ... + }); + // [END rc_fetch_config_callback] +} diff --git a/remoteconfig-next/package.json b/remoteconfig-next/package.json new file mode 100644 index 00000000..952b1540 --- /dev/null +++ b/remoteconfig-next/package.json @@ -0,0 +1,11 @@ +{ + "name": "remoteconfig-next", + "version": "1.0.0", + "scripts": { + "compile": "cp ../tsconfig.json.template ./tsconfig.json && tsc" + }, + "license": "Apache-2.0", + "dependencies": { + "firebase": "^10.0.0" + } +} diff --git a/remoteconfig/index.js b/remoteconfig/index.js new file mode 100644 index 00000000..bba80ac9 --- /dev/null +++ b/remoteconfig/index.js @@ -0,0 +1,44 @@ +import firebase from "firebase"; +import "firebase/remote-config"; + +function getInstance() { + // [START rc_get_instance] + const remoteConfig = firebase.remoteConfig(); + // [END rc_get_instance] +} + +function setMinimumFetchTime() { + const remoteConfig = firebase.remoteConfig(); + // [START rc_set_minimum_fetch_time] + remoteConfig.settings.minimumFetchIntervalMillis = 3600000; + // [END rc_set_minimum_fetch_time] +} + +function setDefaultValues() { + const remoteConfig = firebase.remoteConfig(); + // [START rc_set_default_values] + remoteConfig.defaultConfig = { + "welcome_message": "Welcome" + }; + // [END rc_set_default_values] +} + +function getValues() { + const remoteConfig = firebase.remoteConfig(); + // [START rc_get_values] + const val = remoteConfig.getValue("welcome_messsage"); + // [END rc_get_values] +} + +function fetchConfigCallback() { + const remoteConfig = firebase.remoteConfig(); + // [START rc_fetch_config_callback] + remoteConfig.fetchAndActivate() + .then(() => { + // ... + }) + .catch((err) => { + // ... + }); + // [END rc_fetch_config_callback] +} diff --git a/remoteconfig/package.json b/remoteconfig/package.json new file mode 100644 index 00000000..8437cbd2 --- /dev/null +++ b/remoteconfig/package.json @@ -0,0 +1,11 @@ +{ + "name": "remoteconfig", + "version": "1.0.0", + "scripts": { + "compile": "cp ../tsconfig.json.template ./tsconfig.json && tsc" + }, + "license": "Apache-2.0", + "dependencies": { + "firebase": "^8.10.0" + } +} diff --git a/scripts/checkdirty.sh b/scripts/checkdirty.sh new file mode 100755 index 00000000..5d3c8bea --- /dev/null +++ b/scripts/checkdirty.sh @@ -0,0 +1,15 @@ +#!/bin/bash +set -e + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +git add . + +if [[ $(git diff --stat HEAD) != '' ]]; then + git diff --stat HEAD + echo + echo 'Error: git diff is dirty ... did you forget to run "npm run snippets" after adding snippets?' + exit 1 +else + echo 'Succes: git diff is clean' +fi diff --git a/scripts/package.json b/scripts/package.json new file mode 100644 index 00000000..72b9bf11 --- /dev/null +++ b/scripts/package.json @@ -0,0 +1,9 @@ +{ + "name": "scripts", + "version": "1.0.0", + "description": "Internal repo scripts", + "scripts": { + }, + "author": "samstern@google.com", + "license": "Apache-2.0" +} diff --git a/scripts/separate-snippets.ts b/scripts/separate-snippets.ts new file mode 100644 index 00000000..b2f41de3 --- /dev/null +++ b/scripts/separate-snippets.ts @@ -0,0 +1,292 @@ +// [SNIPPET_REGISTRY disabled] + +import * as cp from "child_process"; +import * as fs from "fs"; +import * as path from "path"; + +// Regex for comment which must be included in a file for it to be separated +const RE_SNIPPETS_SEPARATION = /\[SNIPPETS_SEPARATION\s+enabled\]/; + +// Regex for comment to control the separator suffix +const RE_SNIPPETS_SUFFIX = /\[SNIPPETS_SUFFIX\s+([A-Za-z0-9_]+)\]/; + +// Regex for [START] and [END] snippet tags. +const RE_START_SNIPPET = /\[START\s+([A-Za-z_]+)\s*\]/; +const RE_END_SNIPPET = /\[END\s+([A-Za-z_]+)\s*\]/; + +// Regex for const = require statements +// TODO: Handle multiline imports? +const RE_REQUIRE = /const {(.+?)} = require\((.+?)\)/; + +// Regex for ref docs URLs +// eg. "https://firebase.google.com/docs/reference/js/v8/firebase.User" +const RE_REF_DOCS = /https:\/\/firebase\.google\.com\/docs\/reference\/js\/(.*)/; + +// Maps v8 ref docs URLs to their v9 counterpart +const REF_DOCS_MAPPINGS: { [key: string]: string } = { + "v8/firebase.User" : "auth.user" +}; + +type SnippetsConfig = { + enabled: boolean; + suffix: string; + map: Record; +}; + +const DEFAULT_SUFFIX = "_modular"; + +function isBlank(line: string) { + return line.trim().length === 0; +} + +/** + * Replace all v8 ref doc urls with their v9 counterpart. + */ +function replaceRefDocsUrls(lines: string[]) { + const outputLines = []; + for (const line of lines) { + if (line.match(RE_REF_DOCS)) { + outputLines.push(line.replace(RE_REF_DOCS, (match: string, p1?: string) => { + return p1 ? `https://firebase.google.com/docs/reference/js/${REF_DOCS_MAPPINGS[p1]}` : match; + })); + } else { + outputLines.push(line); + } + } + return outputLines; +} + +/** + * Replace all const { foo } = require('bar') with import { foo } from 'bar'; + */ +function replaceRequireWithImport(lines: string[]) { + const outputLines = []; + for (const line of lines) { + if (line.match(RE_REQUIRE)) { + outputLines.push(line.replace(RE_REQUIRE, `import {$1} from $2`)); + } else { + outputLines.push(line); + } + } + return outputLines; +} + +/** + * Change all [START foo] and [END foo] to be [START foosuffix] and [END foosuffix] + */ +function addSuffixToSnippetNames(lines: string[], snippetSuffix: string) { + const outputLines = []; + for (const line of lines) { + if (line.match(RE_START_SNIPPET)) { + outputLines.push(line.replace(RE_START_SNIPPET, `[START $1${snippetSuffix}]`)); + } else if (line.match(RE_END_SNIPPET)) { + outputLines.push( + line.replace(RE_END_SNIPPET, `[END $1${snippetSuffix}]`) + ); + } else { + outputLines.push(line); + } + } + return outputLines; +} + +/** + * Remove all left-padding so that the least indented line is left-aligned. + */ +function adjustIndentation(lines: string[]) { + const nonBlankLines = lines.filter((l) => !isBlank(l)); + const indentSizes = nonBlankLines.map((l) => l.length - l.trimLeft().length); + const minIndent = Math.min(...indentSizes); + + const outputLines = []; + for (const line of lines) { + if (isBlank(line)) { + outputLines.push(""); + } else { + outputLines.push(line.slice(minIndent)); + } + } + return outputLines; +} + +/** + * If the first line after leading comments is blank, remove it. + */ +function removeFirstLineAfterComments(lines: string[]) { + const outputLines = [...lines]; + + const firstNonComment = outputLines.findIndex( + (l) => !l.startsWith("//") + ); + if (firstNonComment >= 0 && isBlank(outputLines[firstNonComment])) { + outputLines.splice(firstNonComment, 1); + } + + return outputLines; +} + +/** + * Turns a series of source lines into a standalone snippet file by running + * a series of transformations. + * + * @param lines the lines containing the snippet (including START/END comments) + * @param sourceFile the source file where the original snippet lives (used in preamble) + * @param snippetSuffix the suffix (such as _modular) + */ +function processSnippet( + lines: string[], + sourceFile: string, + snippetSuffix: string +): string { + let outputLines = [...lines]; + + // Perform transformations individually, in order + outputLines = replaceRequireWithImport(outputLines); + outputLines = addSuffixToSnippetNames(outputLines, snippetSuffix); + outputLines = adjustIndentation(outputLines); + outputLines = removeFirstLineAfterComments(outputLines); + outputLines = replaceRefDocsUrls(outputLines); + + // Add a preamble to every snippet + const preambleLines = [ + `// This snippet file was generated by processing the source file:`, + `// ${sourceFile}`, + `//`, + `// To update the snippets in this file, edit the source and then run`, + `// 'npm run snippets'.`, + ``, + ]; + const content = [...preambleLines, ...outputLines].join("\n"); + return content; +} + +/** + * Lists all the files in this repository that should be checked for snippets + */ +function listSnippetFiles(): string[] { + const output = cp + .execSync( + 'find . -type f -name "*.js" -not -path "*node_modules*" -not -path "./snippets*"' + ) + .toString(); + return output.split("\n").filter((x) => !isBlank(x)); +} + +/** + * Collect all the snippets from a file into a map of snippet name to lines. + * @param filePath the file path to read. + */ +function collectSnippets(filePath: string): SnippetsConfig { + const fileContents = fs.readFileSync(filePath).toString(); + const lines = fileContents.split("\n"); + + const config: SnippetsConfig = { + enabled: false, + suffix: DEFAULT_SUFFIX, + map: {}, + }; + + // If a file does not have '// [SNIPPETS_SEPARATION enabled]' in it then + // we don't process it for this script. + config.enabled = lines.some((l) => !!l.match(RE_SNIPPETS_SEPARATION)); + if (!config.enabled) { + return config; + } + + // If the file contains '// [SNIPPETS_SUFFIX _banana]' we use _banana (or whatever) + // as the suffix. Otherwise we default to _modular. + const suffixLine = lines.find((l) => !!l.match(RE_SNIPPETS_SUFFIX)); + if (suffixLine) { + const m = suffixLine.match(RE_SNIPPETS_SUFFIX); + + if (m && m[1]) { + config.suffix = m[1]; + } + } + + // A temporary array holding the names of snippets we're currently within. + // This allows for handling nested snippets. + let inSnippetNames: string[] = []; + + for (const line of lines) { + const startMatch = line.match(RE_START_SNIPPET); + const endMatch = line.match(RE_END_SNIPPET); + + if (startMatch) { + // When we find a new [START foo] tag we are now inside snippet 'foo'. + // Until we find an [END foo] tag. All lines we see between now and then + // are part of the snippet content. + const snippetName = startMatch[1]; + if (config.map[snippetName] !== undefined) { + throw new Error(`Detected more than one snippet with the tag ${snippetName}!`); + } + + config.map[snippetName] = [line]; + inSnippetNames.push(snippetName); + } else if (endMatch) { + // When we find a new [END foo] tag we are now exiting snippet 'foo'. + const snippetName = endMatch[1]; + + // If we were not aware that we were inside this snippet (no previous START) + // then we hard throw. + if (!inSnippetNames.includes(snippetName)) { + throw new Error( + `Unrecognized END tag ${snippetName} in ${filePath}.` + ); + } + + // Collect this line as the final line of the snippet and then + // remove this snippet name from the list we're tracking. + config.map[snippetName].push(line); + inSnippetNames.splice(inSnippetNames.indexOf(snippetName), 1); + } else if (inSnippetNames.length > 0) { + // Any line that is not START or END is appended to the list of + // lines for all the snippets we're currently tracking. + for (const snippetName of inSnippetNames) { + config.map[snippetName].push(line); + } + } + } + + return config; +} + +async function main() { + const fileNames = listSnippetFiles(); + + for (const filePath of fileNames) { + const config = collectSnippets(filePath); + if (!config.enabled) { + continue; + } + + const fileSlug = filePath + .replace(".js", "") + .replace("./", "") + .replace(/\./g, "-"); + const snippetDir = path.join("./snippets", fileSlug); + + console.log( + `Processing: ${filePath} --> ${snippetDir} (suffix=${config.suffix})` + ); + + if (!fs.existsSync(snippetDir)) { + fs.mkdirSync(snippetDir, { recursive: true }); + } + + for (const snippetName in config.map) { + const newFilePath = path.join(snippetDir, `${snippetName}.js`); + + const snippetLines = config.map[snippetName]; + const content = processSnippet( + snippetLines, + filePath, + config.suffix + ); + + fs.writeFileSync(newFilePath, content); + } + } +} + +main(); diff --git a/snippets/analytics-next/ecommerce/analytics_ecommerce_add_cart.js b/snippets/analytics-next/ecommerce/analytics_ecommerce_add_cart.js new file mode 100644 index 00000000..8f0e68bd --- /dev/null +++ b/snippets/analytics-next/ecommerce/analytics_ecommerce_add_cart.js @@ -0,0 +1,29 @@ +// This snippet file was generated by processing the source file: +// ./analytics-next/ecommerce.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START analytics_ecommerce_add_cart_modular] +import { getAnalytics, logEvent } from "firebase/analytics"; + +// Specify order quantity +const item_jeggings_quantity = { + ...item_jeggings, + quantity: 2 +}; + +// Prepare ecommerce bundle +const params4 = { + currency: 'USD', + value: 19.98, + items: [item_jeggings_quantity] +}; + +// Log event when a product is added to a wishlist +const analytics = getAnalytics(); +logEvent(analytics, 'add_to_wishlist', params4); + +// Log event when a product is added to the cart +logEvent(analytics, 'add_to_cart', params4); +// [END analytics_ecommerce_add_cart_modular] \ No newline at end of file diff --git a/snippets/analytics-next/ecommerce/analytics_ecommerce_checkout.js b/snippets/analytics-next/ecommerce/analytics_ecommerce_checkout.js new file mode 100644 index 00000000..beaae722 --- /dev/null +++ b/snippets/analytics-next/ecommerce/analytics_ecommerce_checkout.js @@ -0,0 +1,21 @@ +// This snippet file was generated by processing the source file: +// ./analytics-next/ecommerce.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START analytics_ecommerce_checkout_modular] +import { getAnalytics, logEvent } from "firebase/analytics"; + +// Prepare ecommerce params +const params7 = { + currency: 'USD', + value: 14.98, // Total Revenue + coupon: 'SUMMER_FUN', + items: [item_jeggings] +}; + +// Log event +const analytics = getAnalytics(); +logEvent(analytics, 'begin_checkout', params7); +// [END analytics_ecommerce_checkout_modular] \ No newline at end of file diff --git a/snippets/analytics-next/ecommerce/analytics_ecommerce_items.js b/snippets/analytics-next/ecommerce/analytics_ecommerce_items.js new file mode 100644 index 00000000..03b27b47 --- /dev/null +++ b/snippets/analytics-next/ecommerce/analytics_ecommerce_items.js @@ -0,0 +1,37 @@ +// This snippet file was generated by processing the source file: +// ./analytics-next/ecommerce.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START analytics_ecommerce_items_modular] +// A pair of jeggings +const item_jeggings = { + item_id: 'SKU_123', + item_name: 'jeggings', + item_category: 'pants', + item_variant: 'black', + item_brand: 'Google', + price: 9.99 +}; + +// A pair of boots +const item_boots = { + item_id: 'SKU_456', + item_name: 'boots', + item_category: 'shoes', + item_variant: 'brown', + item_brand: 'Google', + price: 24.99 +}; + +// A pair of socks +const item_socks = { + item_id: 'SKU_789', + item_name: 'ankle_socks', + item_category: 'socks', + item_variant: 'red', + item_brand: 'Google', + price: 5.99 +}; +// [END analytics_ecommerce_items_modular] \ No newline at end of file diff --git a/snippets/analytics-next/ecommerce/analytics_ecommerce_payment_info.js b/snippets/analytics-next/ecommerce/analytics_ecommerce_payment_info.js new file mode 100644 index 00000000..048ad5da --- /dev/null +++ b/snippets/analytics-next/ecommerce/analytics_ecommerce_payment_info.js @@ -0,0 +1,22 @@ +// This snippet file was generated by processing the source file: +// ./analytics-next/ecommerce.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START analytics_ecommerce_payment_info_modular] +import { getAnalytics, logEvent } from "firebase/analytics"; + +// Prepare ecommerce params +const params9 = { + currency: 'USD', + value: 14.98, // Total Revenue + coupon: 'SUMMER_FUN', + payment_type: 'Visa', + items: [item_jeggings] +}; + +// Log event +const analytics = getAnalytics(); +logEvent(analytics, 'add_payment_info', params9); +// [END analytics_ecommerce_payment_info_modular] \ No newline at end of file diff --git a/snippets/analytics-next/ecommerce/analytics_ecommerce_promotions.js b/snippets/analytics-next/ecommerce/analytics_ecommerce_promotions.js new file mode 100644 index 00000000..d0577a18 --- /dev/null +++ b/snippets/analytics-next/ecommerce/analytics_ecommerce_promotions.js @@ -0,0 +1,26 @@ +// This snippet file was generated by processing the source file: +// ./analytics-next/ecommerce.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START analytics_ecommerce_promotions_modular] +import { getAnalytics, logEvent } from "firebase/analytics"; + +// Prepare ecommerce params +const params12 = { + promotion_id: 'ABC123', + promotion_name: 'Summer Sale', + creative_name: 'summer2020_promo.jpg', + creative_slot: 'featured_app_1', + location_id: 'HERO_BANNER', + items: [item_jeggings] +}; + +// Log event when a promotion is displayed +const analytics = getAnalytics(); +logEvent(analytics, 'view_promotion', params12); + +// Log event when a promotion is selected +logEvent(analytics, 'select_promotion', params12); +// [END analytics_ecommerce_promotions_modular] \ No newline at end of file diff --git a/snippets/analytics-next/ecommerce/analytics_ecommerce_purchase.js b/snippets/analytics-next/ecommerce/analytics_ecommerce_purchase.js new file mode 100644 index 00000000..12b6d02d --- /dev/null +++ b/snippets/analytics-next/ecommerce/analytics_ecommerce_purchase.js @@ -0,0 +1,25 @@ +// This snippet file was generated by processing the source file: +// ./analytics-next/ecommerce.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START analytics_ecommerce_purchase_modular] +import { getAnalytics, logEvent } from "firebase/analytics"; + +// Prepare ecommerce bundle +const params10 = { + transaction_id: 'T12345', + affiliation: 'Google Store', + currency: 'USD', + value: 14.98, // Total Revenue + tax: 2.85, + shipping: 5.34, + coupon: 'SUMMER_FUN', + items: [item_jeggings] +}; + +// Log event +const analytics = getAnalytics(); +logEvent(analytics, 'purchase', params10); +// [END analytics_ecommerce_purchase_modular] \ No newline at end of file diff --git a/snippets/analytics-next/ecommerce/analytics_ecommerce_refund.js b/snippets/analytics-next/ecommerce/analytics_ecommerce_refund.js new file mode 100644 index 00000000..706e1d7d --- /dev/null +++ b/snippets/analytics-next/ecommerce/analytics_ecommerce_refund.js @@ -0,0 +1,30 @@ +// This snippet file was generated by processing the source file: +// ./analytics-next/ecommerce.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START analytics_ecommerce_refund_modular] +import { getAnalytics, logEvent } from "firebase/analytics"; + +// Prepare ecommerce params +const params11 = { + transaction_id: 'T12345', // Required + affiliation: 'Google Store', + currency: 'USD', + value: 9.99, + items: [] +}; + +// (Optional) For partial refunds, define the item_id and quantity of refunded items +const refundedProduct = { + item_id: 'SKU_123', // Required + quantity: 1 // Required +}; + +params11.items.push(refundedProduct); + +// Log event +const analytics = getAnalytics(); +logEvent(analytics, 'refund', params11); +// [END analytics_ecommerce_refund_modular] \ No newline at end of file diff --git a/snippets/analytics-next/ecommerce/analytics_ecommerce_remove_cart.js b/snippets/analytics-next/ecommerce/analytics_ecommerce_remove_cart.js new file mode 100644 index 00000000..1ad9790f --- /dev/null +++ b/snippets/analytics-next/ecommerce/analytics_ecommerce_remove_cart.js @@ -0,0 +1,20 @@ +// This snippet file was generated by processing the source file: +// ./analytics-next/ecommerce.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START analytics_ecommerce_remove_cart_modular] +import { getAnalytics, logEvent } from "firebase/analytics"; + +// Prepare ecommerce params +const params6 = { + currency: 'USD', + value: 24.99, + items: [item_jeggings] +}; + +// Log event +const analytics = getAnalytics(); +logEvent(analytics, 'remove_from_cart', params6); +// [END analytics_ecommerce_remove_cart_modular] \ No newline at end of file diff --git a/snippets/analytics-next/ecommerce/analytics_ecommerce_select_item.js b/snippets/analytics-next/ecommerce/analytics_ecommerce_select_item.js new file mode 100644 index 00000000..d4eda55c --- /dev/null +++ b/snippets/analytics-next/ecommerce/analytics_ecommerce_select_item.js @@ -0,0 +1,20 @@ +// This snippet file was generated by processing the source file: +// ./analytics-next/ecommerce.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START analytics_ecommerce_select_item_modular] +import { getAnalytics, logEvent } from "firebase/analytics"; + +// Prepare ecommerce event params +const params2 = { + item_list_id: 'L001', + item_list_name: 'Related products', + items: [item_jeggings] +}; + +// Log event +const analytics = getAnalytics(); +logEvent(analytics, 'select_item', params2); +// [END analytics_ecommerce_select_item_modular] \ No newline at end of file diff --git a/snippets/analytics-next/ecommerce/analytics_ecommerce_shipping_info.js b/snippets/analytics-next/ecommerce/analytics_ecommerce_shipping_info.js new file mode 100644 index 00000000..a4f43cbe --- /dev/null +++ b/snippets/analytics-next/ecommerce/analytics_ecommerce_shipping_info.js @@ -0,0 +1,22 @@ +// This snippet file was generated by processing the source file: +// ./analytics-next/ecommerce.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START analytics_ecommerce_shipping_info_modular] +import { getAnalytics, logEvent } from "firebase/analytics"; + +// Prepare ecommerce params +const params8 = { + currency: 'USD', + value: 14.98, // Total Revenue + coupon: 'SUMMER_FUN', + shipping_tier: 'Ground', + items: [item_jeggings] +}; + +// Log event +const analytics = getAnalytics(); +logEvent(analytics, 'add_shipping_info', params8); +// [END analytics_ecommerce_shipping_info_modular] \ No newline at end of file diff --git a/snippets/analytics-next/ecommerce/analytics_ecommerce_view_cart.js b/snippets/analytics-next/ecommerce/analytics_ecommerce_view_cart.js new file mode 100644 index 00000000..cc2359dc --- /dev/null +++ b/snippets/analytics-next/ecommerce/analytics_ecommerce_view_cart.js @@ -0,0 +1,31 @@ +// This snippet file was generated by processing the source file: +// ./analytics-next/ecommerce.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START analytics_ecommerce_view_cart_modular] +import { getAnalytics, logEvent } from "firebase/analytics"; + +// Specify order quantity +const item_jeggings_quantity = { + ...item_jeggings, + quantity: 2 +}; + +const item_boots_quantity = { + ...item_boots, + quantity: 1 +}; + +// Prepare ecommerce params +const params5 = { + currency: 'USD', + value: 44.97, + items: [item_jeggings_quantity, item_boots_quantity] +}; + +// Log event when the cart is viewed +const analytics = getAnalytics(); +logEvent(analytics, 'view_cart', params5); +// [END analytics_ecommerce_view_cart_modular] \ No newline at end of file diff --git a/snippets/analytics-next/ecommerce/analytics_ecommerce_view_item_details.js b/snippets/analytics-next/ecommerce/analytics_ecommerce_view_item_details.js new file mode 100644 index 00000000..83f3474e --- /dev/null +++ b/snippets/analytics-next/ecommerce/analytics_ecommerce_view_item_details.js @@ -0,0 +1,20 @@ +// This snippet file was generated by processing the source file: +// ./analytics-next/ecommerce.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START analytics_ecommerce_view_item_details_modular] +import { getAnalytics, logEvent } from "firebase/analytics"; + +// Prepare ecommerce event params +const params3 = { + currency: 'USD', + value: 9.99, + items: [item_jeggings] +}; + +// Log event +const analytics = getAnalytics(); +logEvent(analytics, 'view_item', params3); +// [END analytics_ecommerce_view_item_details_modular] \ No newline at end of file diff --git a/snippets/analytics-next/ecommerce/analytics_ecommerce_view_item_list.js b/snippets/analytics-next/ecommerce/analytics_ecommerce_view_item_list.js new file mode 100644 index 00000000..fb2fec01 --- /dev/null +++ b/snippets/analytics-next/ecommerce/analytics_ecommerce_view_item_list.js @@ -0,0 +1,20 @@ +// This snippet file was generated by processing the source file: +// ./analytics-next/ecommerce.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START analytics_ecommerce_view_item_list_modular] +import { getAnalytics, logEvent } from "firebase/analytics"; + +// Prepare ecommerce params +const params1 = { + item_list_id: 'L001', + item_list_name: 'Related products', + items: [item_jeggings, item_boots, item_socks] +}; + +// Log event +const analytics = getAnalytics(); +logEvent(analytics, 'view_item_list', params1); +// [END analytics_ecommerce_view_item_list_modular] \ No newline at end of file diff --git a/snippets/analytics-next/index/analytics_initialize.js b/snippets/analytics-next/index/analytics_initialize.js new file mode 100644 index 00000000..42a9578d --- /dev/null +++ b/snippets/analytics-next/index/analytics_initialize.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./analytics-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START analytics_initialize_modular] +import { getAnalytics } from "firebase/analytics"; + +const analytics = getAnalytics(); +// [END analytics_initialize_modular] \ No newline at end of file diff --git a/snippets/analytics-next/index/analytics_log_event.js b/snippets/analytics-next/index/analytics_log_event.js new file mode 100644 index 00000000..7bd74243 --- /dev/null +++ b/snippets/analytics-next/index/analytics_log_event.js @@ -0,0 +1,12 @@ +// This snippet file was generated by processing the source file: +// ./analytics-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START analytics_log_event_modular] +import { getAnalytics, logEvent } from "firebase/analytics"; + +const analytics = getAnalytics(); +logEvent(analytics, 'notification_received'); +// [END analytics_log_event_modular] \ No newline at end of file diff --git a/snippets/analytics-next/index/analytics_log_event_custom_params.js b/snippets/analytics-next/index/analytics_log_event_custom_params.js new file mode 100644 index 00000000..7d76b3f5 --- /dev/null +++ b/snippets/analytics-next/index/analytics_log_event_custom_params.js @@ -0,0 +1,12 @@ +// This snippet file was generated by processing the source file: +// ./analytics-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START analytics_log_event_custom_params_modular] +import { getAnalytics, logEvent } from "firebase/analytics"; + +const analytics = getAnalytics(); +logEvent(analytics, 'goal_completion', { name: 'lever_puzzle'}); +// [END analytics_log_event_custom_params_modular] \ No newline at end of file diff --git a/snippets/analytics-next/index/analytics_log_event_params.js b/snippets/analytics-next/index/analytics_log_event_params.js new file mode 100644 index 00000000..e79d7ec0 --- /dev/null +++ b/snippets/analytics-next/index/analytics_log_event_params.js @@ -0,0 +1,15 @@ +// This snippet file was generated by processing the source file: +// ./analytics-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START analytics_log_event_params_modular] +import { getAnalytics, logEvent } from "firebase/analytics"; + +const analytics = getAnalytics(); +logEvent(analytics, 'select_content', { + content_type: 'image', + content_id: 'P12453' +}); +// [END analytics_log_event_params_modular] \ No newline at end of file diff --git a/snippets/analytics-next/index/analytics_record_screen_view.js b/snippets/analytics-next/index/analytics_record_screen_view.js new file mode 100644 index 00000000..2eef20b3 --- /dev/null +++ b/snippets/analytics-next/index/analytics_record_screen_view.js @@ -0,0 +1,15 @@ +// This snippet file was generated by processing the source file: +// ./analytics-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START analytics_record_screen_view_modular] +import { getAnalytics, logEvent } from "firebase/analytics"; + +const analytics = getAnalytics(); +logEvent(analytics, 'screen_view', { + firebase_screen: screenName, + firebase_screen_class: screenClass +}); +// [END analytics_record_screen_view_modular] \ No newline at end of file diff --git a/snippets/analytics-next/index/analytics_set_user_properties.js b/snippets/analytics-next/index/analytics_set_user_properties.js new file mode 100644 index 00000000..f432e5bf --- /dev/null +++ b/snippets/analytics-next/index/analytics_set_user_properties.js @@ -0,0 +1,12 @@ +// This snippet file was generated by processing the source file: +// ./analytics-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START analytics_set_user_properties_modular] +import { getAnalytics, setUserProperties } from "firebase/analytics"; + +const analytics = getAnalytics(); +setUserProperties(analytics, { favorite_food: 'apples' }); +// [END analytics_set_user_properties_modular] \ No newline at end of file diff --git a/snippets/appcheck-next/index/appcheck_custom_provider.js b/snippets/appcheck-next/index/appcheck_custom_provider.js new file mode 100644 index 00000000..35c7bc99 --- /dev/null +++ b/snippets/appcheck-next/index/appcheck_custom_provider.js @@ -0,0 +1,30 @@ +// This snippet file was generated by processing the source file: +// ./appcheck-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START appcheck_custom_provider_modular] +import { CustomProvider } from "firebase/app-check"; + +const appCheckCustomProvider = new CustomProvider({ + getToken: () => { + return new Promise((resolve, _reject) => { + // TODO: Logic to exchange proof of authenticity for an App Check token and + // expiration time. + + // [START_EXCLUDE] + const tokenFromServer = "abc1234"; + const expirationFromServer = 1234; + // [END_EXCLUDE] + + const appCheckToken = { + token: tokenFromServer, + expireTimeMillis: expirationFromServer * 1000 + }; + + resolve(appCheckToken); + }); + } +}); +// [END appcheck_custom_provider_modular] \ No newline at end of file diff --git a/snippets/appcheck-next/index/appcheck_initialize.js b/snippets/appcheck-next/index/appcheck_initialize.js new file mode 100644 index 00000000..f7763562 --- /dev/null +++ b/snippets/appcheck-next/index/appcheck_initialize.js @@ -0,0 +1,24 @@ +// This snippet file was generated by processing the source file: +// ./appcheck-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START appcheck_initialize_modular] +import { initializeApp } from "firebase/app"; +import { initializeAppCheck, ReCaptchaV3Provider } from "firebase/app-check"; + +const app = initializeApp({ + // Your firebase configuration object +}); + +// Pass your reCAPTCHA v3 site key (public key) to activate(). Make sure this +// key is the counterpart to the secret key you set in the Firebase console. +const appCheck = initializeAppCheck(app, { + provider: new ReCaptchaV3Provider('abcdefghijklmnopqrstuvwxy-1234567890abcd'), + + // Optional argument. If true, the SDK automatically refreshes App Check + // tokens as needed. + isTokenAutoRefreshEnabled: true +}); +// [END appcheck_initialize_modular] \ No newline at end of file diff --git a/snippets/appcheck-next/index/appcheck_initialize_custom_provider.js b/snippets/appcheck-next/index/appcheck_initialize_custom_provider.js new file mode 100644 index 00000000..03f2baa8 --- /dev/null +++ b/snippets/appcheck-next/index/appcheck_initialize_custom_provider.js @@ -0,0 +1,22 @@ +// This snippet file was generated by processing the source file: +// ./appcheck-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START appcheck_initialize_custom_provider_modular] +import { initializeApp } from "firebase/app"; +import { initializeAppCheck } from "firebase/app-check"; + +const app = initializeApp({ + // Your firebase configuration object +}); + +const appCheck = initializeAppCheck(app, { + provider: appCheckCustomProvider, + + // Optional argument. If true, the SDK automatically refreshes App Check + // tokens as needed. + isTokenAutoRefreshEnabled: true +}); +// [END appcheck_initialize_custom_provider_modular] \ No newline at end of file diff --git a/snippets/appcheck-next/index/appcheck_nonfirebase.js b/snippets/appcheck-next/index/appcheck_nonfirebase.js new file mode 100644 index 00000000..275891f8 --- /dev/null +++ b/snippets/appcheck-next/index/appcheck_nonfirebase.js @@ -0,0 +1,33 @@ +// This snippet file was generated by processing the source file: +// ./appcheck-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START appcheck_nonfirebase_modular] +import { initializeAppCheck, getToken } from 'firebase/app-check'; + +const appCheck = initializeAppCheck( + app, + { provider: provider } // ReCaptchaV3Provider or CustomProvider +); + +const callApiWithAppCheckExample = async () => { + let appCheckTokenResponse; + try { + appCheckTokenResponse = await getToken(appCheck, /* forceRefresh= */ false); + } catch (err) { + // Handle any errors if the token was not retrieved. + return; + } + + // Include the App Check token with requests to your server. + const apiResponse = await fetch('https://yourbackend.example.com/yourApiEndpoint', { + headers: { + 'X-Firebase-AppCheck': appCheckTokenResponse.token, + } + }); + + // Handle response from your backend. +}; +// [END appcheck_nonfirebase_modular] \ No newline at end of file diff --git a/snippets/auth-next/anonymous/auth_anon_sign_in.js b/snippets/auth-next/anonymous/auth_anon_sign_in.js new file mode 100644 index 00000000..3db81772 --- /dev/null +++ b/snippets/auth-next/anonymous/auth_anon_sign_in.js @@ -0,0 +1,20 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/anonymous.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_anon_sign_in_modular] +import { getAuth, signInAnonymously } from "firebase/auth"; + +const auth = getAuth(); +signInAnonymously(auth) + .then(() => { + // Signed in.. + }) + .catch((error) => { + const errorCode = error.code; + const errorMessage = error.message; + // ... + }); +// [END auth_anon_sign_in_modular] \ No newline at end of file diff --git a/snippets/auth-next/apple/auth_apple_link_facebook.js b/snippets/auth-next/apple/auth_apple_link_facebook.js new file mode 100644 index 00000000..1ed9d764 --- /dev/null +++ b/snippets/auth-next/apple/auth_apple_link_facebook.js @@ -0,0 +1,26 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/apple.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_apple_link_facebook_modular] +import { getAuth, linkWithPopup, FacebookAuthProvider } from "firebase/auth"; + +const auth = getAuth(); +const provider = new FacebookAuthProvider(); +provider.addScope('user_birthday'); + +// Assuming the current user is an Apple user linking a Facebook provider. +linkWithPopup(auth.currentUser, provider) + .then((result) => { + // Facebook credential is linked to the current Apple user. + // ... + + // The user can now sign in to the same account + // with either Apple or Facebook. + }) + .catch((error) => { + // Handle error. + }); +// [END auth_apple_link_facebook_modular] \ No newline at end of file diff --git a/snippets/auth-next/apple/auth_apple_nonce_node.js b/snippets/auth-next/apple/auth_apple_nonce_node.js new file mode 100644 index 00000000..9dd417a5 --- /dev/null +++ b/snippets/auth-next/apple/auth_apple_nonce_node.js @@ -0,0 +1,28 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/apple.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_apple_nonce_node_modular] +const crypto = require("crypto"); +const string_decoder = require("string_decoder"); + +// Generate a new random string for each sign-in +const generateNonce = (length) => { + const decoder = new string_decoder.StringDecoder("ascii"); + const buf = Buffer.alloc(length); + let nonce = ""; + while (nonce.length < length) { + crypto.randomFillSync(buf); + nonce = decoder.write(buf); + } + return nonce.slice(0, length); +}; + +const unhashedNonce = generateNonce(10); + +// SHA256-hashed nonce in hex +const hashedNonceHex = crypto.createHash('sha256') + .update(unhashedNonce).digest().toString('hex'); +// [END auth_apple_nonce_node_modular] \ No newline at end of file diff --git a/snippets/auth-next/apple/auth_apple_provider_create.js b/snippets/auth-next/apple/auth_apple_provider_create.js new file mode 100644 index 00000000..b6c0aefd --- /dev/null +++ b/snippets/auth-next/apple/auth_apple_provider_create.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/apple.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_apple_provider_create_modular] +import { OAuthProvider } from "firebase/auth"; + +const provider = new OAuthProvider('apple.com'); +// [END auth_apple_provider_create_modular] \ No newline at end of file diff --git a/snippets/auth-next/apple/auth_apple_provider_params.js b/snippets/auth-next/apple/auth_apple_provider_params.js new file mode 100644 index 00000000..775ab94b --- /dev/null +++ b/snippets/auth-next/apple/auth_apple_provider_params.js @@ -0,0 +1,12 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/apple.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_apple_provider_params_modular] +provider.setCustomParameters({ + // Localize the Apple authentication screen in French. + locale: 'fr' +}); +// [END auth_apple_provider_params_modular] \ No newline at end of file diff --git a/snippets/auth-next/apple/auth_apple_provider_scopes.js b/snippets/auth-next/apple/auth_apple_provider_scopes.js new file mode 100644 index 00000000..86da8bbc --- /dev/null +++ b/snippets/auth-next/apple/auth_apple_provider_scopes.js @@ -0,0 +1,10 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/apple.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_apple_provider_scopes_modular] +provider.addScope('email'); +provider.addScope('name'); +// [END auth_apple_provider_scopes_modular] \ No newline at end of file diff --git a/snippets/auth-next/apple/auth_apple_reauthenticate_popup.js b/snippets/auth-next/apple/auth_apple_reauthenticate_popup.js new file mode 100644 index 00000000..cd40151d --- /dev/null +++ b/snippets/auth-next/apple/auth_apple_reauthenticate_popup.js @@ -0,0 +1,41 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/apple.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_apple_reauthenticate_popup_modular] +import { getAuth, reauthenticateWithPopup, OAuthProvider } from "firebase/auth"; + +// Result from Redirect auth flow. +const auth = getAuth(); +const provider = new OAuthProvider('apple.com'); + +reauthenticateWithPopup(auth.currentUser, provider) + .then((result) => { + // User is re-authenticated with fresh tokens minted and can perform + // sensitive operations like account deletion, or updating their email + // address or password. + + // The signed-in user info. + const user = result.user; + + // You can also get the Apple OAuth Access and ID Tokens. + const credential = OAuthProvider.credentialFromResult(result); + const accessToken = credential.accessToken; + const idToken = credential.idToken; + + // ... + }) + .catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The credential that was used. + const credential = OAuthProvider.credentialFromError(error); + + // ... + }); +// [END auth_apple_reauthenticate_popup_modular] \ No newline at end of file diff --git a/snippets/auth-next/apple/auth_apple_signin_nonce.js b/snippets/auth-next/apple/auth_apple_signin_nonce.js new file mode 100644 index 00000000..ffa3a3d2 --- /dev/null +++ b/snippets/auth-next/apple/auth_apple_signin_nonce.js @@ -0,0 +1,30 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/apple.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_apple_signin_nonce_modular] +import { getAuth, signInWithCredential, OAuthProvider } from "firebase/auth"; + +const auth = getAuth(); + +// Build Firebase credential with the Apple ID token. +const provider = new OAuthProvider('apple.com'); +const authCredential = provider.credential({ + idToken: appleIdToken, + rawNonce: unhashedNonce, +}); + +// Sign in with credential form the Apple user. +signInWithCredential(auth, authCredential) + .then((result) => { + // User signed in. + }) + .catch((error) => { + // An error occurred. If error.code == 'auth/missing-or-invalid-nonce', + // make sure you're sending the SHA256-hashed nonce as a hex string + // with your request to Apple. + console.log(error); + }); +// [END auth_apple_signin_nonce_modular] \ No newline at end of file diff --git a/snippets/auth-next/apple/auth_apple_signin_popup.js b/snippets/auth-next/apple/auth_apple_signin_popup.js new file mode 100644 index 00000000..e3a0f11b --- /dev/null +++ b/snippets/auth-next/apple/auth_apple_signin_popup.js @@ -0,0 +1,35 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/apple.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_apple_signin_popup_modular] +import { getAuth, signInWithPopup, OAuthProvider } from "firebase/auth"; + +const auth = getAuth(); +signInWithPopup(auth, provider) + .then((result) => { + // The signed-in user info. + const user = result.user; + + // Apple credential + const credential = OAuthProvider.credentialFromResult(result); + const accessToken = credential.accessToken; + const idToken = credential.idToken; + + // IdP data available using getAdditionalUserInfo(result) + // ... + }) + .catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The credential that was used. + const credential = OAuthProvider.credentialFromError(error); + + // ... + }); +// [END auth_apple_signin_popup_modular] \ No newline at end of file diff --git a/snippets/auth-next/apple/auth_apple_signin_redirect.js b/snippets/auth-next/apple/auth_apple_signin_redirect.js new file mode 100644 index 00000000..f000195d --- /dev/null +++ b/snippets/auth-next/apple/auth_apple_signin_redirect.js @@ -0,0 +1,12 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/apple.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_apple_signin_redirect_modular] +import { getAuth, signInWithRedirect } from "firebase/auth"; + +const auth = getAuth(); +signInWithRedirect(auth, provider); +// [END auth_apple_signin_redirect_modular] \ No newline at end of file diff --git a/snippets/auth-next/apple/auth_apple_signin_redirect_result.js b/snippets/auth-next/apple/auth_apple_signin_redirect_result.js new file mode 100644 index 00000000..654f95e5 --- /dev/null +++ b/snippets/auth-next/apple/auth_apple_signin_redirect_result.js @@ -0,0 +1,34 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/apple.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_apple_signin_redirect_result_modular] +import { getAuth, getRedirectResult, OAuthProvider } from "firebase/auth"; + +// Result from Redirect auth flow. +const auth = getAuth(); +getRedirectResult(auth) + .then((result) => { + const credential = OAuthProvider.credentialFromResult(result); + if (credential) { + // You can also get the Apple OAuth Access and ID Tokens. + const accessToken = credential.accessToken; + const idToken = credential.idToken; + } + // The signed-in user info. + const user = result.user; + }) + .catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The credential that was used. + const credential = OAuthProvider.credentialFromError(error); + + // ... + }); +// [END auth_apple_signin_redirect_result_modular] \ No newline at end of file diff --git a/snippets/auth-next/auth-state-persistence/auth_set_persistence_none.js b/snippets/auth-next/auth-state-persistence/auth_set_persistence_none.js new file mode 100644 index 00000000..5224cc21 --- /dev/null +++ b/snippets/auth-next/auth-state-persistence/auth_set_persistence_none.js @@ -0,0 +1,24 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/auth-state-persistence.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_set_persistence_none_modular] +import { getAuth, setPersistence, signInWithRedirect, inMemoryPersistence, GoogleAuthProvider } from "firebase/auth"; + +const auth = getAuth(); +setPersistence(auth, inMemoryPersistence) + .then(() => { + const provider = new GoogleAuthProvider(); + // In memory persistence will be applied to the signed in Google user + // even though the persistence was set to 'none' and a page redirect + // occurred. + return signInWithRedirect(auth, provider); + }) + .catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + }); +// [END auth_set_persistence_none_modular] \ No newline at end of file diff --git a/snippets/auth-next/auth-state-persistence/auth_set_persistence_session.js b/snippets/auth-next/auth-state-persistence/auth_set_persistence_session.js new file mode 100644 index 00000000..d21183ff --- /dev/null +++ b/snippets/auth-next/auth-state-persistence/auth_set_persistence_session.js @@ -0,0 +1,25 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/auth-state-persistence.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_set_persistence_session_modular] +import { getAuth, setPersistence, signInWithEmailAndPassword, browserSessionPersistence } from "firebase/auth"; + +const auth = getAuth(); +setPersistence(auth, browserSessionPersistence) + .then(() => { + // Existing and future Auth states are now persisted in the current + // session only. Closing the window would clear any existing state even + // if a user forgets to sign out. + // ... + // New sign-in will be persisted with session persistence. + return signInWithEmailAndPassword(auth, email, password); + }) + .catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + }); +// [END auth_set_persistence_session_modular] \ No newline at end of file diff --git a/snippets/auth-next/cordova/auth_cordova_redirect_result.js b/snippets/auth-next/cordova/auth_cordova_redirect_result.js new file mode 100644 index 00000000..f1c87596 --- /dev/null +++ b/snippets/auth-next/cordova/auth_cordova_redirect_result.js @@ -0,0 +1,28 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/cordova.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_cordova_redirect_result_modular] +import { getAuth, getRedirectResult, GoogleAuthProvider } from "firebase/auth/cordova"; + +const auth = getAuth(); +getRedirectResult(auth) + .then((result) => { + const credential = GoogleAuthProvider.credentialFromResult(result); + if (credential) { + // This gives you a Google Access Token. + // You can use it to access the Google API. + const token = credential.accessToken; + // The signed-in user info. + const user = result.user; + // ... + } + }) + .catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + }); +// [END auth_cordova_redirect_result_modular] \ No newline at end of file diff --git a/snippets/auth-next/cordova/auth_cordova_sign_in_redirect.js b/snippets/auth-next/cordova/auth_cordova_sign_in_redirect.js new file mode 100644 index 00000000..ed3cc8d1 --- /dev/null +++ b/snippets/auth-next/cordova/auth_cordova_sign_in_redirect.js @@ -0,0 +1,30 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/cordova.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_cordova_sign_in_redirect_modular] +import { getAuth, signInWithRedirect, getRedirectResult, GoogleAuthProvider } from "firebase/auth/cordova"; + +const auth = getAuth(); +signInWithRedirect(auth, new GoogleAuthProvider()) + .then(() => { + return getRedirectResult(auth); + }) + .then((result) => { + const credential = GoogleAuthProvider.credentialFromResult(result); + + // This gives you a Google Access Token. + // You can use it to access the Google API. + const token = credential.accessToken; + + // The signed-in user info. + const user = result.user; + // ... + }).catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + }); +// [END auth_cordova_sign_in_redirect_modular] \ No newline at end of file diff --git a/snippets/auth-next/cordova/auth_create_google_provider.js b/snippets/auth-next/cordova/auth_create_google_provider.js new file mode 100644 index 00000000..fcf96401 --- /dev/null +++ b/snippets/auth-next/cordova/auth_create_google_provider.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/cordova.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_create_google_provider_modular] +import { GoogleAuthProvider } from "firebase/auth/cordova"; + +const provider = new GoogleAuthProvider(); +// [END auth_create_google_provider_modular] \ No newline at end of file diff --git a/snippets/auth-next/custom-dependencies/auth_get_auth_equivalent.js b/snippets/auth-next/custom-dependencies/auth_get_auth_equivalent.js new file mode 100644 index 00000000..37292487 --- /dev/null +++ b/snippets/auth-next/custom-dependencies/auth_get_auth_equivalent.js @@ -0,0 +1,16 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/custom-dependencies.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_get_auth_equivalent_modular] +import {initializeAuth, browserLocalPersistence, browserPopupRedirectResolver, browserSessionPersistence, indexedDBLocalPersistence} from "firebase/auth"; +import {initializeApp} from "firebase/app"; + +const app = initializeApp({/** Your app config */}); +const auth = initializeAuth(app, { + persistence: [indexedDBLocalPersistence, browserLocalPersistence, browserSessionPersistence], + popupRedirectResolver: browserPopupRedirectResolver, +}); +// [END auth_get_auth_equivalent_modular] \ No newline at end of file diff --git a/snippets/auth-next/custom-dependencies/auth_only_browser_local.js b/snippets/auth-next/custom-dependencies/auth_only_browser_local.js new file mode 100644 index 00000000..549307ac --- /dev/null +++ b/snippets/auth-next/custom-dependencies/auth_only_browser_local.js @@ -0,0 +1,16 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/custom-dependencies.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_only_browser_local_modular] +import {initializeAuth, browserLocalPersistence} from "firebase/auth"; +import {initializeApp} from "firebase/app"; + +const app = initializeApp({/** Your app config */}); +const auth = initializeAuth(app, { + persistence: browserLocalPersistence, + // No popupRedirectResolver defined +}); +// [END auth_only_browser_local_modular] \ No newline at end of file diff --git a/snippets/auth-next/custom-dependencies/auth_only_indexed_db.js b/snippets/auth-next/custom-dependencies/auth_only_indexed_db.js new file mode 100644 index 00000000..1427ae6b --- /dev/null +++ b/snippets/auth-next/custom-dependencies/auth_only_indexed_db.js @@ -0,0 +1,16 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/custom-dependencies.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_only_indexed_db_modular] +import {initializeAuth, indexedDBLocalPersistence} from "firebase/auth"; +import {initializeApp} from "firebase/app"; + +const app = initializeApp({/** Your app config */}); +const auth = initializeAuth(app, { + persistence: indexedDBLocalPersistence, + // No popupRedirectResolver defined +}); +// [END auth_only_indexed_db_modular] \ No newline at end of file diff --git a/snippets/auth-next/custom-dependencies/auth_sign_in_redirect_manual_deps.js b/snippets/auth-next/custom-dependencies/auth_sign_in_redirect_manual_deps.js new file mode 100644 index 00000000..75fd0d5d --- /dev/null +++ b/snippets/auth-next/custom-dependencies/auth_sign_in_redirect_manual_deps.js @@ -0,0 +1,18 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/custom-dependencies.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_sign_in_redirect_manual_deps_modular] +import {initializeAuth, browserLocalPersistence, browserPopupRedirectResolver, indexedDBLocalPersistence, signInWithRedirect, GoogleAuthProvider} from "firebase/auth"; +import {initializeApp} from "firebase/app"; + +const app = initializeApp({/** Your app config */}); +const auth = initializeAuth(app, { + persistence: [indexedDBLocalPersistence, browserLocalPersistence], +}); + +// Later +signInWithRedirect(auth, new GoogleAuthProvider(), browserPopupRedirectResolver); +// [END auth_sign_in_redirect_manual_deps_modular] \ No newline at end of file diff --git a/snippets/auth-next/custom-email-handler/auth_handle_mgmt_query_params.js b/snippets/auth-next/custom-email-handler/auth_handle_mgmt_query_params.js new file mode 100644 index 00000000..d0a22696 --- /dev/null +++ b/snippets/auth-next/custom-email-handler/auth_handle_mgmt_query_params.js @@ -0,0 +1,50 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/custom-email-handler.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_handle_mgmt_query_params_modular] +import { initializeApp } from "firebase/app"; +import { getAuth } from "firebase/auth"; + +document.addEventListener('DOMContentLoaded', () => { + // TODO: Implement getParameterByName() + + // Get the action to complete. + const mode = getParameterByName('mode'); + // Get the one-time code from the query parameter. + const actionCode = getParameterByName('oobCode'); + // (Optional) Get the continue URL from the query parameter if available. + const continueUrl = getParameterByName('continueUrl'); + // (Optional) Get the language code if available. + const lang = getParameterByName('lang') || 'en'; + + // Configure the Firebase SDK. + // This is the minimum configuration required for the API to be used. + const config = { + 'apiKey': "YOUR_API_KEY" // Copy this key from the web initialization + // snippet found in the Firebase console. + }; + const app = initializeApp(config); + const auth = getAuth(app); + + // Handle the user management action. + switch (mode) { + case 'resetPassword': + // Display reset password handler and UI. + handleResetPassword(auth, actionCode, continueUrl, lang); + break; + case 'recoverEmail': + // Display email recovery handler and UI. + handleRecoverEmail(auth, actionCode, lang); + break; + case 'verifyEmail': + // Display email verification handler and UI. + handleVerifyEmail(auth, actionCode, continueUrl, lang); + break; + default: + // Error: invalid mode. + } +}, false); +// [END auth_handle_mgmt_query_params_modular] \ No newline at end of file diff --git a/snippets/auth-next/custom-email-handler/auth_handle_recover_email.js b/snippets/auth-next/custom-email-handler/auth_handle_recover_email.js new file mode 100644 index 00000000..a4351a50 --- /dev/null +++ b/snippets/auth-next/custom-email-handler/auth_handle_recover_email.js @@ -0,0 +1,37 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/custom-email-handler.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_handle_recover_email_modular] +import { checkActionCode, applyActionCode, sendPasswordResetEmail } from "firebase/auth"; + +function handleRecoverEmail(auth, actionCode, lang) { + // Localize the UI to the selected language as determined by the lang + // parameter. + let restoredEmail = null; + // Confirm the action code is valid. + checkActionCode(auth, actionCode).then((info) => { + // Get the restored email address. + restoredEmail = info['data']['email']; + + // Revert to the old email. + return applyActionCode(auth, actionCode); + }).then(() => { + // Account email reverted to restoredEmail + + // TODO: Display a confirmation message to the user. + + // You might also want to give the user the option to reset their password + // in case the account was compromised: + sendPasswordResetEmail(auth, restoredEmail).then(() => { + // Password reset confirmation sent. Ask user to check their email. + }).catch((error) => { + // Error encountered while sending password reset code. + }); + }).catch((error) => { + // Invalid code. + }); +} +// [END auth_handle_recover_email_modular] \ No newline at end of file diff --git a/snippets/auth-next/custom-email-handler/auth_handle_reset_password.js b/snippets/auth-next/custom-email-handler/auth_handle_reset_password.js new file mode 100644 index 00000000..95a3faea --- /dev/null +++ b/snippets/auth-next/custom-email-handler/auth_handle_reset_password.js @@ -0,0 +1,42 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/custom-email-handler.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_handle_reset_password_modular] +import { verifyPasswordResetCode, confirmPasswordReset } from "firebase/auth"; + +function handleResetPassword(auth, actionCode, continueUrl, lang) { + // Localize the UI to the selected language as determined by the lang + // parameter. + + // Verify the password reset code is valid. + verifyPasswordResetCode(auth, actionCode).then((email) => { + const accountEmail = email; + + // TODO: Show the reset screen with the user's email and ask the user for + // the new password. + const newPassword = "..."; + + // Save the new password. + confirmPasswordReset(auth, actionCode, newPassword).then((resp) => { + // Password reset has been confirmed and new password updated. + + // TODO: Display a link back to the app, or sign-in the user directly + // if the page belongs to the same domain as the app: + // auth.signInWithEmailAndPassword(accountEmail, newPassword); + + // TODO: If a continue URL is available, display a button which on + // click redirects the user back to the app via continueUrl with + // additional state determined from that URL's parameters. + }).catch((error) => { + // Error occurred during confirmation. The code might have expired or the + // password is too weak. + }); + }).catch((error) => { + // Invalid or expired action code. Ask user to try to reset the password + // again. + }); +} +// [END auth_handle_reset_password_modular] \ No newline at end of file diff --git a/snippets/auth-next/custom-email-handler/auth_handle_verify_email.js b/snippets/auth-next/custom-email-handler/auth_handle_verify_email.js new file mode 100644 index 00000000..730d4601 --- /dev/null +++ b/snippets/auth-next/custom-email-handler/auth_handle_verify_email.js @@ -0,0 +1,26 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/custom-email-handler.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_handle_verify_email_modular] +function handleVerifyEmail(auth, actionCode, continueUrl, lang) { + // Localize the UI to the selected language as determined by the lang + // parameter. + // Try to apply the email verification code. + applyActionCode(auth, actionCode).then((resp) => { + // Email address has been verified. + + // TODO: Display a confirmation message to the user. + // You could also provide the user with a link back to the app. + + // TODO: If a continue URL is available, display a button which on + // click redirects the user back to the app via continueUrl with + // additional state determined from that URL's parameters. + }).catch((error) => { + // Code is invalid or expired. Ask the user to verify their email address + // again. + }); +} +// [END auth_handle_verify_email_modular] \ No newline at end of file diff --git a/snippets/auth-next/custom/auth_sign_in_custom.js b/snippets/auth-next/custom/auth_sign_in_custom.js new file mode 100644 index 00000000..c345c0ce --- /dev/null +++ b/snippets/auth-next/custom/auth_sign_in_custom.js @@ -0,0 +1,22 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/custom.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_sign_in_custom_modular] +import { getAuth, signInWithCustomToken } from "firebase/auth"; + +const auth = getAuth(); +signInWithCustomToken(auth, token) + .then((userCredential) => { + // Signed in + const user = userCredential.user; + // ... + }) + .catch((error) => { + const errorCode = error.code; + const errorMessage = error.message; + // ... + }); +// [END auth_sign_in_custom_modular] \ No newline at end of file diff --git a/snippets/auth-next/email-link-auth/auth_email_link_actioncode_settings.js b/snippets/auth-next/email-link-auth/auth_email_link_actioncode_settings.js new file mode 100644 index 00000000..010b5c09 --- /dev/null +++ b/snippets/auth-next/email-link-auth/auth_email_link_actioncode_settings.js @@ -0,0 +1,25 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/email-link-auth.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_email_link_actioncode_settings_modular] +const actionCodeSettings = { + // URL you want to redirect back to. The domain (www.example.com) for this + // URL must be in the authorized domains list in the Firebase Console. + url: 'https://www.example.com/finishSignUp?cartId=1234', + // This must be true. + handleCodeInApp: true, + iOS: { + bundleId: 'com.example.ios' + }, + android: { + packageName: 'com.example.android', + installApp: true, + minimumVersion: '12' + }, + // The domain must be configured in Firebase Hosting and owned by the project. + linkDomain: 'custom-domain.com' +}; +// [END auth_email_link_actioncode_settings_modular] \ No newline at end of file diff --git a/snippets/auth-next/email-link-auth/auth_email_link_link.js b/snippets/auth-next/email-link-auth/auth_email_link_link.js new file mode 100644 index 00000000..a49b979f --- /dev/null +++ b/snippets/auth-next/email-link-auth/auth_email_link_link.js @@ -0,0 +1,24 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/email-link-auth.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_email_link_link_modular] +import { getAuth, linkWithCredential, EmailAuthProvider } from "firebase/auth"; + +// Construct the email link credential from the current URL. +const credential = EmailAuthProvider.credentialWithLink( + email, window.location.href); + +// Link the credential to the current user. +const auth = getAuth(); +linkWithCredential(auth.currentUser, credential) + .then((usercred) => { + // The provider is now successfully linked. + // The phone user can now sign in with their phone number or email. + }) + .catch((error) => { + // Some error occurred. + }); +// [END auth_email_link_link_modular] \ No newline at end of file diff --git a/snippets/auth-next/email-link-auth/auth_email_link_reauth.js b/snippets/auth-next/email-link-auth/auth_email_link_reauth.js new file mode 100644 index 00000000..128650cb --- /dev/null +++ b/snippets/auth-next/email-link-auth/auth_email_link_reauth.js @@ -0,0 +1,24 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/email-link-auth.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_email_link_reauth_modular] +import { getAuth, reauthenticateWithCredential, EmailAuthProvider } from "firebase/auth"; + +// Construct the email link credential from the current URL. +const credential = EmailAuthProvider.credentialWithLink( + email, window.location.href); + +// Re-authenticate the user with this credential. +const auth = getAuth(); +reauthenticateWithCredential(auth.currentUser, credential) + .then((usercred) => { + // The user is now successfully re-authenticated and can execute sensitive + // operations. + }) + .catch((error) => { + // Some error occurred. + }); +// [END auth_email_link_reauth_modular] \ No newline at end of file diff --git a/snippets/auth-next/email-link-auth/auth_email_link_send.js b/snippets/auth-next/email-link-auth/auth_email_link_send.js new file mode 100644 index 00000000..5b97d87f --- /dev/null +++ b/snippets/auth-next/email-link-auth/auth_email_link_send.js @@ -0,0 +1,24 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/email-link-auth.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_email_link_send_modular] +import { getAuth, sendSignInLinkToEmail } from "firebase/auth"; + +const auth = getAuth(); +sendSignInLinkToEmail(auth, email, actionCodeSettings) + .then(() => { + // The link was successfully sent. Inform the user. + // Save the email locally so you don't need to ask the user for it again + // if they open the link on the same device. + window.localStorage.setItem('emailForSignIn', email); + // ... + }) + .catch((error) => { + const errorCode = error.code; + const errorMessage = error.message; + // ... + }); +// [END auth_email_link_send_modular] \ No newline at end of file diff --git a/snippets/auth-next/email-link-auth/email_link_complete.js b/snippets/auth-next/email-link-auth/email_link_complete.js new file mode 100644 index 00000000..dbe1143d --- /dev/null +++ b/snippets/auth-next/email-link-auth/email_link_complete.js @@ -0,0 +1,42 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/email-link-auth.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START email_link_complete_modular] +import { getAuth, isSignInWithEmailLink, signInWithEmailLink } from "firebase/auth"; + +// Confirm the link is a sign-in with email link. +const auth = getAuth(); +if (isSignInWithEmailLink(auth, window.location.href)) { + // Additional state parameters can also be passed via URL. + // This can be used to continue the user's intended action before triggering + // the sign-in operation. + // Get the email if available. This should be available if the user completes + // the flow on the same device where they started it. + let email = window.localStorage.getItem('emailForSignIn'); + if (!email) { + // User opened the link on a different device. To prevent session fixation + // attacks, ask the user to provide the associated email again. For example: + email = window.prompt('Please provide your email for confirmation'); + } + // The client SDK will parse the code from the link for you. + signInWithEmailLink(auth, email, window.location.href) + .then((result) => { + // Clear email from storage. + window.localStorage.removeItem('emailForSignIn'); + // You can access the new user by importing getAdditionalUserInfo + // and calling it with result: + // getAdditionalUserInfo(result) + // You can access the user's profile via: + // getAdditionalUserInfo(result)?.profile + // You can check if the user is new or existing: + // getAdditionalUserInfo(result)?.isNewUser + }) + .catch((error) => { + // Some error occurred, you can inspect the code: error.code + // Common errors could be invalid email and invalid or expired OTPs. + }); +} +// [END email_link_complete_modular] \ No newline at end of file diff --git a/snippets/auth-next/email-link-auth/email_link_diferentiate.js b/snippets/auth-next/email-link-auth/email_link_diferentiate.js new file mode 100644 index 00000000..4fc06b41 --- /dev/null +++ b/snippets/auth-next/email-link-auth/email_link_diferentiate.js @@ -0,0 +1,32 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/email-link-auth.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START email_link_diferentiate_modular] +import { getAuth, fetchSignInMethodsForEmail, EmailAuthProvider} from "firebase/auth"; + +// After asking the user for their email. +const email = window.prompt('Please provide your email'); + +const auth = getAuth(); +fetchSignInMethodsForEmail(auth, email) + .then((signInMethods) => { + // This returns the same array as fetchProvidersForEmail but for email + // provider identified by 'password' string, signInMethods would contain 2 + // different strings: + // 'emailLink' if the user previously signed in with an email/link + // 'password' if the user has a password. + // A user could have both. + if (signInMethods.indexOf(EmailAuthProvider.EMAIL_PASSWORD_SIGN_IN_METHOD) != -1) { + // User can sign in with email/password. + } + if (signInMethods.indexOf(EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD) != -1) { + // User can sign in with email/link. + } + }) + .catch((error) => { + // Some error occurred, you can inspect the code: error.code + }); +// [END email_link_diferentiate_modular] \ No newline at end of file diff --git a/snippets/auth-next/email/auth_send_email_verification.js b/snippets/auth-next/email/auth_send_email_verification.js new file mode 100644 index 00000000..9290a421 --- /dev/null +++ b/snippets/auth-next/email/auth_send_email_verification.js @@ -0,0 +1,16 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/email.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_send_email_verification_modular] +import { getAuth, sendEmailVerification } from "firebase/auth"; + +const auth = getAuth(); +sendEmailVerification(auth.currentUser) + .then(() => { + // Email verification sent! + // ... + }); +// [END auth_send_email_verification_modular] \ No newline at end of file diff --git a/snippets/auth-next/email/auth_send_password_reset.js b/snippets/auth-next/email/auth_send_password_reset.js new file mode 100644 index 00000000..e8e18a58 --- /dev/null +++ b/snippets/auth-next/email/auth_send_password_reset.js @@ -0,0 +1,21 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/email.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_send_password_reset_modular] +import { getAuth, sendPasswordResetEmail } from "firebase/auth"; + +const auth = getAuth(); +sendPasswordResetEmail(auth, email) + .then(() => { + // Password reset email sent! + // .. + }) + .catch((error) => { + const errorCode = error.code; + const errorMessage = error.message; + // .. + }); +// [END auth_send_password_reset_modular] \ No newline at end of file diff --git a/snippets/auth-next/email/auth_signin_password.js b/snippets/auth-next/email/auth_signin_password.js new file mode 100644 index 00000000..f0a211d5 --- /dev/null +++ b/snippets/auth-next/email/auth_signin_password.js @@ -0,0 +1,21 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/email.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_signin_password_modular] +import { getAuth, signInWithEmailAndPassword } from "firebase/auth"; + +const auth = getAuth(); +signInWithEmailAndPassword(auth, email, password) + .then((userCredential) => { + // Signed in + const user = userCredential.user; + // ... + }) + .catch((error) => { + const errorCode = error.code; + const errorMessage = error.message; + }); +// [END auth_signin_password_modular] \ No newline at end of file diff --git a/snippets/auth-next/email/auth_signup_password.js b/snippets/auth-next/email/auth_signup_password.js new file mode 100644 index 00000000..936b64ce --- /dev/null +++ b/snippets/auth-next/email/auth_signup_password.js @@ -0,0 +1,22 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/email.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_signup_password_modular] +import { getAuth, createUserWithEmailAndPassword } from "firebase/auth"; + +const auth = getAuth(); +createUserWithEmailAndPassword(auth, email, password) + .then((userCredential) => { + // Signed up + const user = userCredential.user; + // ... + }) + .catch((error) => { + const errorCode = error.code; + const errorMessage = error.message; + // .. + }); +// [END auth_signup_password_modular] \ No newline at end of file diff --git a/snippets/auth-next/emulator-suite/auth_emulator_connect.js b/snippets/auth-next/emulator-suite/auth_emulator_connect.js new file mode 100644 index 00000000..525a92b8 --- /dev/null +++ b/snippets/auth-next/emulator-suite/auth_emulator_connect.js @@ -0,0 +1,12 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/emulator-suite.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_emulator_connect_modular] +import { getAuth, connectAuthEmulator } from "firebase/auth"; + +const auth = getAuth(); +connectAuthEmulator(auth, "http://127.0.0.1:9099"); +// [END auth_emulator_connect_modular] \ No newline at end of file diff --git a/snippets/auth-next/emulator-suite/auth_emulator_google_credential.js b/snippets/auth-next/emulator-suite/auth_emulator_google_credential.js new file mode 100644 index 00000000..d3683bb0 --- /dev/null +++ b/snippets/auth-next/emulator-suite/auth_emulator_google_credential.js @@ -0,0 +1,14 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/emulator-suite.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_emulator_google_credential_modular] +import { getAuth, signInWithCredential, GoogleAuthProvider } from "firebase/auth"; + +const auth = getAuth(); +signInWithCredential(auth, GoogleAuthProvider.credential( + '{"sub": "abc123", "email": "foo@example.com", "email_verified": true}' +)); +// [END auth_emulator_google_credential_modular] \ No newline at end of file diff --git a/snippets/auth-next/facebook/auth_facebook_callback.js b/snippets/auth-next/facebook/auth_facebook_callback.js new file mode 100644 index 00000000..63106871 --- /dev/null +++ b/snippets/auth-next/facebook/auth_facebook_callback.js @@ -0,0 +1,43 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/facebook.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_facebook_callback_modular] +import { getAuth, onAuthStateChanged, signInWithCredential, signOut, FacebookAuthProvider } from "firebase/auth"; +const auth = getAuth(); + +function checkLoginState(response) { + if (response.authResponse) { + // User is signed-in Facebook. + const unsubscribe = onAuthStateChanged(auth, (firebaseUser) => { + unsubscribe(); + // Check if we are already signed-in Firebase with the correct user. + if (!isUserEqual(response.authResponse, firebaseUser)) { + // Build Firebase credential with the Facebook auth token. + const credential = FacebookAuthProvider.credential( + response.authResponse.accessToken); + + // Sign in with the credential from the Facebook user. + signInWithCredential(auth, credential) + .catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The AuthCredential type that was used. + const credential = FacebookAuthProvider.credentialFromError(error); + // ... + }); + } else { + // User is already signed-in Firebase with the correct user. + } + }); + } else { + // User is signed-out of Facebook. + signOut(auth); + } +} +// [END auth_facebook_callback_modular] \ No newline at end of file diff --git a/snippets/auth-next/facebook/auth_facebook_checksameuser.js b/snippets/auth-next/facebook/auth_facebook_checksameuser.js new file mode 100644 index 00000000..0b1d170b --- /dev/null +++ b/snippets/auth-next/facebook/auth_facebook_checksameuser.js @@ -0,0 +1,23 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/facebook.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_facebook_checksameuser_modular] +import { FacebookAuthProvider } from "firebase/auth"; + +function isUserEqual(facebookAuthResponse, firebaseUser) { + if (firebaseUser) { + const providerData = firebaseUser.providerData; + for (let i = 0; i < providerData.length; i++) { + if (providerData[i].providerId === FacebookAuthProvider.PROVIDER_ID && + providerData[i].uid === facebookAuthResponse.userID) { + // We don't need to re-auth the Firebase connection. + return true; + } + } + } + return false; +} +// [END auth_facebook_checksameuser_modular] \ No newline at end of file diff --git a/snippets/auth-next/facebook/auth_facebook_provider_create.js b/snippets/auth-next/facebook/auth_facebook_provider_create.js new file mode 100644 index 00000000..403abcd2 --- /dev/null +++ b/snippets/auth-next/facebook/auth_facebook_provider_create.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/facebook.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_facebook_provider_create_modular] +import { FacebookAuthProvider } from "firebase/auth"; + +const provider = new FacebookAuthProvider(); +// [END auth_facebook_provider_create_modular] \ No newline at end of file diff --git a/snippets/auth-next/facebook/auth_facebook_provider_credential.js b/snippets/auth-next/facebook/auth_facebook_provider_credential.js new file mode 100644 index 00000000..b7bbca25 --- /dev/null +++ b/snippets/auth-next/facebook/auth_facebook_provider_credential.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/facebook.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_facebook_provider_credential_modular] +import { FacebookAuthProvider } from "firebase/auth"; + +const credential = FacebookAuthProvider.credential(accessToken); +// [END auth_facebook_provider_credential_modular] \ No newline at end of file diff --git a/snippets/auth-next/facebook/auth_facebook_provider_params.js b/snippets/auth-next/facebook/auth_facebook_provider_params.js new file mode 100644 index 00000000..308fadd4 --- /dev/null +++ b/snippets/auth-next/facebook/auth_facebook_provider_params.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/facebook.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_facebook_provider_params_modular] +provider.setCustomParameters({ + 'display': 'popup' +}); +// [END auth_facebook_provider_params_modular] \ No newline at end of file diff --git a/snippets/auth-next/facebook/auth_facebook_provider_scopes.js b/snippets/auth-next/facebook/auth_facebook_provider_scopes.js new file mode 100644 index 00000000..06386449 --- /dev/null +++ b/snippets/auth-next/facebook/auth_facebook_provider_scopes.js @@ -0,0 +1,9 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/facebook.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// / [START auth_facebook_provider_scopes_modular] +provider.addScope('user_birthday'); +// [END auth_facebook_provider_scopes_modular] \ No newline at end of file diff --git a/snippets/auth-next/facebook/auth_facebook_signin_credential.js b/snippets/auth-next/facebook/auth_facebook_signin_credential.js new file mode 100644 index 00000000..3b784f24 --- /dev/null +++ b/snippets/auth-next/facebook/auth_facebook_signin_credential.js @@ -0,0 +1,27 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/facebook.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_facebook_signin_credential_modular] +import { getAuth, signInWithCredential, FacebookAuthProvider } from "firebase/auth"; + +// Sign in with the credential from the Facebook user. +const auth = getAuth(); +signInWithCredential(auth, credential) + .then((result) => { + // Signed in + const credential = FacebookAuthProvider.credentialFromResult(result); + }) + .catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The AuthCredential type that was used. + const credential = FacebookAuthProvider.credentialFromError(error); + // ... + }); +// [END auth_facebook_signin_credential_modular] \ No newline at end of file diff --git a/snippets/auth-next/facebook/auth_facebook_signin_popup.js b/snippets/auth-next/facebook/auth_facebook_signin_popup.js new file mode 100644 index 00000000..524a11ac --- /dev/null +++ b/snippets/auth-next/facebook/auth_facebook_signin_popup.js @@ -0,0 +1,34 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/facebook.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_facebook_signin_popup_modular] +import { getAuth, signInWithPopup, FacebookAuthProvider } from "firebase/auth"; + +const auth = getAuth(); +signInWithPopup(auth, provider) + .then((result) => { + // The signed-in user info. + const user = result.user; + + // This gives you a Facebook Access Token. You can use it to access the Facebook API. + const credential = FacebookAuthProvider.credentialFromResult(result); + const accessToken = credential.accessToken; + + // IdP data available using getAdditionalUserInfo(result) + // ... + }) + .catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The AuthCredential type that was used. + const credential = FacebookAuthProvider.credentialFromError(error); + + // ... + }); +// [END auth_facebook_signin_popup_modular] \ No newline at end of file diff --git a/snippets/auth-next/facebook/auth_facebook_signin_redirect_result.js b/snippets/auth-next/facebook/auth_facebook_signin_redirect_result.js new file mode 100644 index 00000000..c65c47d7 --- /dev/null +++ b/snippets/auth-next/facebook/auth_facebook_signin_redirect_result.js @@ -0,0 +1,30 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/facebook.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_facebook_signin_redirect_result_modular] +import { getAuth, getRedirectResult, FacebookAuthProvider } from "firebase/auth"; + +const auth = getAuth(); +getRedirectResult(auth) + .then((result) => { + // This gives you a Facebook Access Token. You can use it to access the Facebook API. + const credential = FacebookAuthProvider.credentialFromResult(result); + const token = credential.accessToken; + + const user = result.user; + // IdP data available using getAdditionalUserInfo(result) + // ... + }).catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // AuthCredential type that was used. + const credential = FacebookAuthProvider.credentialFromError(error); + // ... + }); +// [END auth_facebook_signin_redirect_result_modular] \ No newline at end of file diff --git a/snippets/auth-next/github/auth_github_provider_create.js b/snippets/auth-next/github/auth_github_provider_create.js new file mode 100644 index 00000000..92cc4171 --- /dev/null +++ b/snippets/auth-next/github/auth_github_provider_create.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/github.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_github_provider_create_modular] +import { GithubAuthProvider } from "firebase/auth"; + +const provider = new GithubAuthProvider(); +// [END auth_github_provider_create_modular] \ No newline at end of file diff --git a/snippets/auth-next/github/auth_github_provider_credential.js b/snippets/auth-next/github/auth_github_provider_credential.js new file mode 100644 index 00000000..ca122a35 --- /dev/null +++ b/snippets/auth-next/github/auth_github_provider_credential.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/github.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_github_provider_credential_modular] +import { GithubAuthProvider } from "firebase/auth"; + +const credential = GithubAuthProvider.credential(token); +// [END auth_github_provider_credential_modular] \ No newline at end of file diff --git a/snippets/auth-next/github/auth_github_provider_params.js b/snippets/auth-next/github/auth_github_provider_params.js new file mode 100644 index 00000000..e2d4f341 --- /dev/null +++ b/snippets/auth-next/github/auth_github_provider_params.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/github.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_github_provider_params_modular] +provider.setCustomParameters({ + 'allow_signup': 'false' +}); +// [END auth_github_provider_params_modular] \ No newline at end of file diff --git a/snippets/auth-next/github/auth_github_provider_scopes.js b/snippets/auth-next/github/auth_github_provider_scopes.js new file mode 100644 index 00000000..b9237208 --- /dev/null +++ b/snippets/auth-next/github/auth_github_provider_scopes.js @@ -0,0 +1,9 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/github.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_github_provider_scopes_modular] +provider.addScope('repo'); +// [END auth_github_provider_scopes_modular] \ No newline at end of file diff --git a/snippets/auth-next/github/auth_github_signin_popup.js b/snippets/auth-next/github/auth_github_signin_popup.js new file mode 100644 index 00000000..3839a70f --- /dev/null +++ b/snippets/auth-next/github/auth_github_signin_popup.js @@ -0,0 +1,31 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/github.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_github_signin_popup_modular] +import { getAuth, signInWithPopup, GithubAuthProvider } from "firebase/auth"; + +const auth = getAuth(); +signInWithPopup(auth, provider) + .then((result) => { + // This gives you a GitHub Access Token. You can use it to access the GitHub API. + const credential = GithubAuthProvider.credentialFromResult(result); + const token = credential.accessToken; + + // The signed-in user info. + const user = result.user; + // IdP data available using getAdditionalUserInfo(result) + // ... + }).catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The AuthCredential type that was used. + const credential = GithubAuthProvider.credentialFromError(error); + // ... + }); +// [END auth_github_signin_popup_modular] \ No newline at end of file diff --git a/snippets/auth-next/github/auth_github_signin_redirect_result.js b/snippets/auth-next/github/auth_github_signin_redirect_result.js new file mode 100644 index 00000000..fb5f7ca2 --- /dev/null +++ b/snippets/auth-next/github/auth_github_signin_redirect_result.js @@ -0,0 +1,34 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/github.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_github_signin_redirect_result_modular] +import { getAuth, getRedirectResult, GithubAuthProvider } from "firebase/auth"; + +const auth = getAuth(); +getRedirectResult(auth) + .then((result) => { + const credential = GithubAuthProvider.credentialFromResult(result); + if (credential) { + // This gives you a GitHub Access Token. You can use it to access the GitHub API. + const token = credential.accessToken; + // ... + } + + // The signed-in user info. + const user = result.user; + // IdP data available using getAdditionalUserInfo(result) + // ... + }).catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The AuthCredential type that was used. + const credential = GithubAuthProvider.credentialFromError(error); + // ... + }); +// [END auth_github_signin_redirect_result_modular] \ No newline at end of file diff --git a/snippets/auth-next/google-signin/auth_google_build_signin.js b/snippets/auth-next/google-signin/auth_google_build_signin.js new file mode 100644 index 00000000..98f35eb2 --- /dev/null +++ b/snippets/auth-next/google-signin/auth_google_build_signin.js @@ -0,0 +1,25 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/google-signin.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_google_build_signin_modular] +import { getAuth, signInWithCredential, GoogleAuthProvider } from "firebase/auth"; + +// Build Firebase credential with the Google ID token. +const credential = GoogleAuthProvider.credential(id_token); + +// Sign in with credential from the Google user. +const auth = getAuth(); +signInWithCredential(auth, credential).catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The AuthCredential type that was used. + const credential = GoogleAuthProvider.credentialFromError(error); + // ... +}); +// [END auth_google_build_signin_modular] \ No newline at end of file diff --git a/snippets/auth-next/google-signin/auth_google_callback.js b/snippets/auth-next/google-signin/auth_google_callback.js new file mode 100644 index 00000000..873778dd --- /dev/null +++ b/snippets/auth-next/google-signin/auth_google_callback.js @@ -0,0 +1,38 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/google-signin.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_google_callback_modular] +import { getAuth, onAuthStateChanged, signInWithCredential, GoogleAuthProvider } from "firebase/auth"; +const auth = getAuth(); + +function onSignIn(googleUser) { + console.log('Google Auth Response', googleUser); + // We need to register an Observer on Firebase Auth to make sure auth is initialized. + const unsubscribe = onAuthStateChanged(auth, (firebaseUser) => { + unsubscribe(); + // Check if we are already signed-in Firebase with the correct user. + if (!isUserEqual(googleUser, firebaseUser)) { + // Build Firebase credential with the Google ID token. + const credential = GoogleAuthProvider.credential( + googleUser.getAuthResponse().id_token); + + // Sign in with credential from the Google user. + signInWithCredential(auth, credential).catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The credential that was used. + const credential = GoogleAuthProvider.credentialFromError(error); + // ... + }); + } else { + console.log('User already signed-in Firebase.'); + } + }); +} +// [END auth_google_callback_modular] \ No newline at end of file diff --git a/snippets/auth-next/google-signin/auth_google_checksameuser.js b/snippets/auth-next/google-signin/auth_google_checksameuser.js new file mode 100644 index 00000000..cc451991 --- /dev/null +++ b/snippets/auth-next/google-signin/auth_google_checksameuser.js @@ -0,0 +1,23 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/google-signin.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_google_checksameuser_modular] +import { GoogleAuthProvider } from "firebase/auth"; + +function isUserEqual(googleUser, firebaseUser) { + if (firebaseUser) { + const providerData = firebaseUser.providerData; + for (let i = 0; i < providerData.length; i++) { + if (providerData[i].providerId === GoogleAuthProvider.PROVIDER_ID && + providerData[i].uid === googleUser.getBasicProfile().getId()) { + // We don't need to reauth the Firebase connection. + return true; + } + } + } + return false; +} +// [END auth_google_checksameuser_modular] \ No newline at end of file diff --git a/snippets/auth-next/google-signin/auth_google_provider_create.js b/snippets/auth-next/google-signin/auth_google_provider_create.js new file mode 100644 index 00000000..76b356d6 --- /dev/null +++ b/snippets/auth-next/google-signin/auth_google_provider_create.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/google-signin.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_google_provider_create_modular] +import { GoogleAuthProvider } from "firebase/auth"; + +const provider = new GoogleAuthProvider(); +// [END auth_google_provider_create_modular] \ No newline at end of file diff --git a/snippets/auth-next/google-signin/auth_google_provider_credential.js b/snippets/auth-next/google-signin/auth_google_provider_credential.js new file mode 100644 index 00000000..924aa167 --- /dev/null +++ b/snippets/auth-next/google-signin/auth_google_provider_credential.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/google-signin.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_google_provider_credential_modular] +import { GoogleAuthProvider } from "firebase/auth"; + +const credential = GoogleAuthProvider.credential(idToken); +// [END auth_google_provider_credential_modular] \ No newline at end of file diff --git a/snippets/auth-next/google-signin/auth_google_provider_params.js b/snippets/auth-next/google-signin/auth_google_provider_params.js new file mode 100644 index 00000000..25f1ea99 --- /dev/null +++ b/snippets/auth-next/google-signin/auth_google_provider_params.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/google-signin.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_google_provider_params_modular] +provider.setCustomParameters({ + 'login_hint': 'user@example.com' +}); +// [END auth_google_provider_params_modular] \ No newline at end of file diff --git a/snippets/auth-next/google-signin/auth_google_provider_scopes.js b/snippets/auth-next/google-signin/auth_google_provider_scopes.js new file mode 100644 index 00000000..346b6432 --- /dev/null +++ b/snippets/auth-next/google-signin/auth_google_provider_scopes.js @@ -0,0 +1,9 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/google-signin.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_google_provider_scopes_modular] +provider.addScope('https://www.googleapis.com/auth/contacts.readonly'); +// [END auth_google_provider_scopes_modular] \ No newline at end of file diff --git a/snippets/auth-next/google-signin/auth_google_signin_credential.js b/snippets/auth-next/google-signin/auth_google_signin_credential.js new file mode 100644 index 00000000..25c6f3ba --- /dev/null +++ b/snippets/auth-next/google-signin/auth_google_signin_credential.js @@ -0,0 +1,18 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/google-signin.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_google_signin_credential_modular] +signInWithCredential(auth, credential).catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The credential that was used. + const credential = GoogleAuthProvider.credentialFromError(error); + // ... +}); +// [END auth_google_signin_credential_modular] \ No newline at end of file diff --git a/snippets/auth-next/google-signin/auth_google_signin_popup.js b/snippets/auth-next/google-signin/auth_google_signin_popup.js new file mode 100644 index 00000000..433bb88e --- /dev/null +++ b/snippets/auth-next/google-signin/auth_google_signin_popup.js @@ -0,0 +1,30 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/google-signin.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_google_signin_popup_modular] +import { getAuth, signInWithPopup, GoogleAuthProvider } from "firebase/auth"; + +const auth = getAuth(); +signInWithPopup(auth, provider) + .then((result) => { + // This gives you a Google Access Token. You can use it to access the Google API. + const credential = GoogleAuthProvider.credentialFromResult(result); + const token = credential.accessToken; + // The signed-in user info. + const user = result.user; + // IdP data available using getAdditionalUserInfo(result) + // ... + }).catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The AuthCredential type that was used. + const credential = GoogleAuthProvider.credentialFromError(error); + // ... + }); +// [END auth_google_signin_popup_modular] \ No newline at end of file diff --git a/snippets/auth-next/google-signin/auth_google_signin_redirect_result.js b/snippets/auth-next/google-signin/auth_google_signin_redirect_result.js new file mode 100644 index 00000000..2953ab5f --- /dev/null +++ b/snippets/auth-next/google-signin/auth_google_signin_redirect_result.js @@ -0,0 +1,31 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/google-signin.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_google_signin_redirect_result_modular] +import { getAuth, getRedirectResult, GoogleAuthProvider } from "firebase/auth"; + +const auth = getAuth(); +getRedirectResult(auth) + .then((result) => { + // This gives you a Google Access Token. You can use it to access Google APIs. + const credential = GoogleAuthProvider.credentialFromResult(result); + const token = credential.accessToken; + + // The signed-in user info. + const user = result.user; + // IdP data available using getAdditionalUserInfo(result) + // ... + }).catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The AuthCredential type that was used. + const credential = GoogleAuthProvider.credentialFromError(error); + // ... + }); +// [END auth_google_signin_redirect_result_modular] \ No newline at end of file diff --git a/snippets/auth-next/index/auth_current_user.js b/snippets/auth-next/index/auth_current_user.js new file mode 100644 index 00000000..6925b021 --- /dev/null +++ b/snippets/auth-next/index/auth_current_user.js @@ -0,0 +1,20 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_current_user_modular] +import { getAuth } from "firebase/auth"; + +const auth = getAuth(); +const user = auth.currentUser; + +if (user) { + // User is signed in, see docs for a list of available properties + // https://firebase.google.com/docs/reference/js/auth.user + // ... +} else { + // No user is signed in. +} +// [END auth_current_user_modular] \ No newline at end of file diff --git a/snippets/auth-next/index/auth_init_custom_domain.js b/snippets/auth-next/index/auth_init_custom_domain.js new file mode 100644 index 00000000..971e5c56 --- /dev/null +++ b/snippets/auth-next/index/auth_init_custom_domain.js @@ -0,0 +1,17 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_init_custom_domain_modular] +import { initializeApp } from "firebase/app"; + +const firebaseConfig = { + apiKey: "...", + // By default, authDomain is '[YOUR_APP].firebaseapp.com'. + // You may replace it with a custom domain. + authDomain: '[YOUR_CUSTOM_DOMAIN]' +}; +const firebaseApp = initializeApp(firebaseConfig); +// [END auth_init_custom_domain_modular] \ No newline at end of file diff --git a/snippets/auth-next/index/auth_make_email_credential.js b/snippets/auth-next/index/auth_make_email_credential.js new file mode 100644 index 00000000..6487fe62 --- /dev/null +++ b/snippets/auth-next/index/auth_make_email_credential.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_make_email_credential_modular] +import { EmailAuthProvider } from "firebase/auth"; + +const credential = EmailAuthProvider.credential(email, password); +// [END auth_make_email_credential_modular] \ No newline at end of file diff --git a/snippets/auth-next/index/auth_make_facebook_credential.js b/snippets/auth-next/index/auth_make_facebook_credential.js new file mode 100644 index 00000000..91f5ae63 --- /dev/null +++ b/snippets/auth-next/index/auth_make_facebook_credential.js @@ -0,0 +1,12 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_make_facebook_credential_modular] +import { FacebookAuthProvider } from "firebase/auth"; + +const credential = FacebookAuthProvider.credential( + response.authResponse.accessToken); +// [END auth_make_facebook_credential_modular] \ No newline at end of file diff --git a/snippets/auth-next/index/auth_make_google_credential.js b/snippets/auth-next/index/auth_make_google_credential.js new file mode 100644 index 00000000..2640d101 --- /dev/null +++ b/snippets/auth-next/index/auth_make_google_credential.js @@ -0,0 +1,12 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_make_google_credential_modular] +import { GoogleAuthProvider } from "firebase/auth"; + +const credential = GoogleAuthProvider.credential( + googleUser.getAuthResponse().id_token); +// [END auth_make_google_credential_modular] \ No newline at end of file diff --git a/snippets/auth-next/index/auth_set_language_code.js b/snippets/auth-next/index/auth_set_language_code.js new file mode 100644 index 00000000..fe9a2025 --- /dev/null +++ b/snippets/auth-next/index/auth_set_language_code.js @@ -0,0 +1,14 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_set_language_code_modular] +import { getAuth } from "firebase/auth"; + +const auth = getAuth(); +auth.languageCode = 'it'; +// To apply the default browser preference instead of explicitly setting it. +// auth.useDeviceLanguage(); +// [END auth_set_language_code_modular] \ No newline at end of file diff --git a/snippets/auth-next/index/auth_sign_out.js b/snippets/auth-next/index/auth_sign_out.js new file mode 100644 index 00000000..4cef4757 --- /dev/null +++ b/snippets/auth-next/index/auth_sign_out.js @@ -0,0 +1,16 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_sign_out_modular] +import { getAuth, signOut } from "firebase/auth"; + +const auth = getAuth(); +signOut(auth).then(() => { + // Sign-out successful. +}).catch((error) => { + // An error happened. +}); +// [END auth_sign_out_modular] \ No newline at end of file diff --git a/snippets/auth-next/index/auth_signin_credential.js b/snippets/auth-next/index/auth_signin_credential.js new file mode 100644 index 00000000..c2b027a3 --- /dev/null +++ b/snippets/auth-next/index/auth_signin_credential.js @@ -0,0 +1,25 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_signin_credential_modular] +import { getAuth, signInWithCredential } from "firebase/auth"; + +// Sign in with the credential from the user. +const auth = getAuth(); +signInWithCredential(auth, credential) + .then((result) => { + // Signed in + // ... + }) + .catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // ... + }); +// [END auth_signin_credential_modular] \ No newline at end of file diff --git a/snippets/auth-next/index/auth_signin_redirect.js b/snippets/auth-next/index/auth_signin_redirect.js new file mode 100644 index 00000000..49c95513 --- /dev/null +++ b/snippets/auth-next/index/auth_signin_redirect.js @@ -0,0 +1,12 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_signin_redirect_modular] +import { getAuth, signInWithRedirect } from "firebase/auth"; + +const auth = getAuth(); +signInWithRedirect(auth, provider); +// [END auth_signin_redirect_modular] \ No newline at end of file diff --git a/snippets/auth-next/index/auth_state_listener.js b/snippets/auth-next/index/auth_state_listener.js new file mode 100644 index 00000000..0f3e4e88 --- /dev/null +++ b/snippets/auth-next/index/auth_state_listener.js @@ -0,0 +1,22 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_state_listener_modular] +import { getAuth, onAuthStateChanged } from "firebase/auth"; + +const auth = getAuth(); +onAuthStateChanged(auth, (user) => { + if (user) { + // User is signed in, see docs for a list of available properties + // https://firebase.google.com/docs/reference/js/auth.user + const uid = user.uid; + // ... + } else { + // User is signed out + // ... + } +}); +// [END auth_state_listener_modular] \ No newline at end of file diff --git a/snippets/auth-next/link-multiple-accounts/account_exists_popup.js b/snippets/auth-next/link-multiple-accounts/account_exists_popup.js new file mode 100644 index 00000000..2c667882 --- /dev/null +++ b/snippets/auth-next/link-multiple-accounts/account_exists_popup.js @@ -0,0 +1,61 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/link-multiple-accounts.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + + // [START account_exists_popup_modular] + import { signInWithPopup, signInWithEmailAndPassword, linkWithCredential } from "firebase/auth"; + + // User tries to sign in with Facebook. + signInWithPopup(auth, facebookProvider).catch((error) => { + // User's email already exists. + if (error.code === 'auth/account-exists-with-different-credential') { + // The pending Facebook credential. + const pendingCred = error.credential; + // The provider account's email address. + const email = error.customData.email; + + // Present the user with a list of providers they might have + // used to create the original account. + // Then, ask the user to sign in with the existing provider. + const method = promptUserForSignInMethod(); + + if (method === 'password') { + // TODO: Ask the user for their password. + // In real scenario, you should handle this asynchronously. + const password = promptUserForPassword(); + signInWithEmailAndPassword(auth, email, password).then((result) => { + return linkWithCredential(result.user, pendingCred); + }).then(() => { + // Facebook account successfully linked to the existing user. + goToApp(); + }); + return; + } + + // All other cases are external providers. + // Construct provider object for that provider. + // TODO: Implement getProviderForProviderId. + const provider = getProviderForProviderId(method); + // At this point, you should let the user know that they already have an + // account with a different provider, and validate they want to sign in + // with the new provider. + // Note: Browsers usually block popups triggered asynchronously, so in + // real app, you should ask the user to click on a "Continue" button + // that will trigger signInWithPopup(). + signInWithPopup(auth, provider).then((result) => { + // Note: Identity Platform doesn't control the provider's sign-in + // flow, so it's possible for the user to sign in with an account + // with a different email from the first one. + + // Link the Facebook credential. We have access to the pending + // credential, so we can directly call the link method. + linkWithCredential(result.user, pendingCred).then((userCred) => { + // Success. + goToApp(); + }); + }); + } +}); +// [END account_exists_popup_modular] \ No newline at end of file diff --git a/snippets/auth-next/link-multiple-accounts/auth_anonymous_link.js b/snippets/auth-next/link-multiple-accounts/auth_anonymous_link.js new file mode 100644 index 00000000..3bf9ab56 --- /dev/null +++ b/snippets/auth-next/link-multiple-accounts/auth_anonymous_link.js @@ -0,0 +1,18 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/link-multiple-accounts.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_anonymous_link_modular] +import { getAuth, linkWithCredential } from "firebase/auth"; + +const auth = getAuth(); +linkWithCredential(auth.currentUser, credential) + .then((usercred) => { + const user = usercred.user; + console.log("Anonymous account successfully upgraded", user); + }).catch((error) => { + console.log("Error upgrading anonymous account", error); + }); +// [END auth_anonymous_link_modular] \ No newline at end of file diff --git a/snippets/auth-next/link-multiple-accounts/auth_get_providers.js b/snippets/auth-next/link-multiple-accounts/auth_get_providers.js new file mode 100644 index 00000000..0f1ebbc1 --- /dev/null +++ b/snippets/auth-next/link-multiple-accounts/auth_get_providers.js @@ -0,0 +1,14 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/link-multiple-accounts.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_get_providers_modular] +import { GoogleAuthProvider, FacebookAuthProvider, TwitterAuthProvider, GithubAuthProvider } from "firebase/auth"; + +const googleProvider = new GoogleAuthProvider(); +const facebookProvider = new FacebookAuthProvider(); +const twitterProvider = new TwitterAuthProvider(); +const githubProvider = new GithubAuthProvider(); +// [END auth_get_providers_modular] \ No newline at end of file diff --git a/snippets/auth-next/link-multiple-accounts/auth_get_redirect_result.js b/snippets/auth-next/link-multiple-accounts/auth_get_redirect_result.js new file mode 100644 index 00000000..1608a933 --- /dev/null +++ b/snippets/auth-next/link-multiple-accounts/auth_get_redirect_result.js @@ -0,0 +1,20 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/link-multiple-accounts.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_get_redirect_result_modular] +import { getRedirectResult } from "firebase/auth"; +getRedirectResult(auth).then((result) => { + const credential = GoogleAuthProvider.credentialFromResult(result); + if (credential) { + // Accounts successfully linked. + const user = result.user; + // ... + } +}).catch((error) => { + // Handle Errors here. + // ... +}); +// [END auth_get_redirect_result_modular] \ No newline at end of file diff --git a/snippets/auth-next/link-multiple-accounts/auth_link_with_popup.js b/snippets/auth-next/link-multiple-accounts/auth_link_with_popup.js new file mode 100644 index 00000000..b6619cad --- /dev/null +++ b/snippets/auth-next/link-multiple-accounts/auth_link_with_popup.js @@ -0,0 +1,21 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/link-multiple-accounts.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_link_with_popup_modular] +import { getAuth, linkWithPopup, GoogleAuthProvider } from "firebase/auth"; +const provider = new GoogleAuthProvider(); + +const auth = getAuth(); +linkWithPopup(auth.currentUser, provider).then((result) => { + // Accounts successfully linked. + const credential = GoogleAuthProvider.credentialFromResult(result); + const user = result.user; + // ... +}).catch((error) => { + // Handle Errors here. + // ... +}); +// [END auth_link_with_popup_modular] \ No newline at end of file diff --git a/snippets/auth-next/link-multiple-accounts/auth_link_with_redirect.js b/snippets/auth-next/link-multiple-accounts/auth_link_with_redirect.js new file mode 100644 index 00000000..1e496f42 --- /dev/null +++ b/snippets/auth-next/link-multiple-accounts/auth_link_with_redirect.js @@ -0,0 +1,15 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/link-multiple-accounts.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_link_with_redirect_modular] +import { getAuth, linkWithRedirect, GoogleAuthProvider } from "firebase/auth"; +const provider = new GoogleAuthProvider(); + +const auth = getAuth(); +linkWithRedirect(auth.currentUser, provider) + .then(/* ... */) + .catch(/* ... */); +// [END auth_link_with_redirect_modular] \ No newline at end of file diff --git a/snippets/auth-next/link-multiple-accounts/auth_make_email_credential.js b/snippets/auth-next/link-multiple-accounts/auth_make_email_credential.js new file mode 100644 index 00000000..861d77e8 --- /dev/null +++ b/snippets/auth-next/link-multiple-accounts/auth_make_email_credential.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/link-multiple-accounts.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_make_email_credential_modular] +import { EmailAuthProvider } from "firebase/auth"; + +const credential = EmailAuthProvider.credential(email, password); +// [END auth_make_email_credential_modular] \ No newline at end of file diff --git a/snippets/auth-next/link-multiple-accounts/auth_merge_accounts.js b/snippets/auth-next/link-multiple-accounts/auth_merge_accounts.js new file mode 100644 index 00000000..f7cc113c --- /dev/null +++ b/snippets/auth-next/link-multiple-accounts/auth_merge_accounts.js @@ -0,0 +1,50 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/link-multiple-accounts.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_merge_accounts_modular] +import { getAuth, signInWithCredential, linkWithCredential, OAuthProvider } from "firebase/auth"; + +// The implementation of how you store your user data depends on your application +const repo = new MyUserDataRepo(); + +// Get reference to the currently signed-in user +const auth = getAuth(); +const prevUser = auth.currentUser; + +// Get the data which you will want to merge. This should be done now +// while the app is still signed in as this user. +const prevUserData = repo.get(prevUser); + +// Delete the user's data now, we will restore it if the merge fails +repo.delete(prevUser); + +// Sign in user with the account you want to link to +signInWithCredential(auth, newCredential).then((result) => { + console.log("Sign In Success", result); + const currentUser = result.user; + const currentUserData = repo.get(currentUser); + + // Merge prevUser and currentUser data stored in Firebase. + // Note: How you handle this is specific to your application + const mergedData = repo.merge(prevUserData, currentUserData); + + const credential = OAuthProvider.credentialFromResult(result); + return linkWithCredential(prevUser, credential) + .then((linkResult) => { + // Sign in with the newly linked credential + const linkCredential = OAuthProvider.credentialFromResult(linkResult); + return signInWithCredential(auth, linkCredential); + }) + .then((signInResult) => { + // Save the merged data to the new user + repo.set(signInResult.user, mergedData); + }); +}).catch((error) => { + // If there are errors we want to undo the data merge/deletion + console.log("Sign In Error", error); + repo.set(prevUser, prevUserData); +}); +// [END auth_merge_accounts_modular] \ No newline at end of file diff --git a/snippets/auth-next/link-multiple-accounts/auth_simple_link.js b/snippets/auth-next/link-multiple-accounts/auth_simple_link.js new file mode 100644 index 00000000..15c434a2 --- /dev/null +++ b/snippets/auth-next/link-multiple-accounts/auth_simple_link.js @@ -0,0 +1,18 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/link-multiple-accounts.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_simple_link_modular] +import { getAuth, linkWithCredential } from "firebase/auth"; + +const auth = getAuth(); +linkWithCredential(auth.currentUser, credential) + .then((usercred) => { + const user = usercred.user; + console.log("Account linking success", user); + }).catch((error) => { + console.log("Account linking error", error); + }); +// [END auth_simple_link_modular] \ No newline at end of file diff --git a/snippets/auth-next/link-multiple-accounts/auth_unlink_provider.js b/snippets/auth-next/link-multiple-accounts/auth_unlink_provider.js new file mode 100644 index 00000000..46617b26 --- /dev/null +++ b/snippets/auth-next/link-multiple-accounts/auth_unlink_provider.js @@ -0,0 +1,18 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/link-multiple-accounts.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_unlink_provider_modular] +import { getAuth, unlink } from "firebase/auth"; + +const auth = getAuth(); +unlink(auth.currentUser, providerId).then(() => { + // Auth provider unlinked from account + // ... +}).catch((error) => { + // An error happened + // ... +}); +// [END auth_unlink_provider_modular] \ No newline at end of file diff --git a/snippets/auth-next/manage/auth_delete_user.js b/snippets/auth-next/manage/auth_delete_user.js new file mode 100644 index 00000000..389a0852 --- /dev/null +++ b/snippets/auth-next/manage/auth_delete_user.js @@ -0,0 +1,19 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/manage.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_delete_user_modular] +import { getAuth, deleteUser } from "firebase/auth"; + +const auth = getAuth(); +const user = auth.currentUser; + +deleteUser(user).then(() => { + // User deleted. +}).catch((error) => { + // An error ocurred + // ... +}); +// [END auth_delete_user_modular] \ No newline at end of file diff --git a/snippets/auth-next/manage/auth_get_user_profile.js b/snippets/auth-next/manage/auth_get_user_profile.js new file mode 100644 index 00000000..9dce7fdc --- /dev/null +++ b/snippets/auth-next/manage/auth_get_user_profile.js @@ -0,0 +1,24 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/manage.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_get_user_profile_modular] +import { getAuth } from "firebase/auth"; + +const auth = getAuth(); +const user = auth.currentUser; +if (user !== null) { + // The user object has basic properties such as display name, email, etc. + const displayName = user.displayName; + const email = user.email; + const photoURL = user.photoURL; + const emailVerified = user.emailVerified; + + // The user's ID, unique to the Firebase project. Do NOT use + // this value to authenticate with your backend server, if + // you have one. Use User.getToken() instead. + const uid = user.uid; +} +// [END auth_get_user_profile_modular] \ No newline at end of file diff --git a/snippets/auth-next/manage/auth_get_user_profile_provider.js b/snippets/auth-next/manage/auth_get_user_profile_provider.js new file mode 100644 index 00000000..1d165d49 --- /dev/null +++ b/snippets/auth-next/manage/auth_get_user_profile_provider.js @@ -0,0 +1,22 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/manage.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_get_user_profile_provider_modular] +import { getAuth } from "firebase/auth"; + +const auth = getAuth(); +const user = auth.currentUser; + +if (user !== null) { + user.providerData.forEach((profile) => { + console.log("Sign-in provider: " + profile.providerId); + console.log(" Provider-specific UID: " + profile.uid); + console.log(" Name: " + profile.displayName); + console.log(" Email: " + profile.email); + console.log(" Photo URL: " + profile.photoURL); + }); +} +// [END auth_get_user_profile_provider_modular] \ No newline at end of file diff --git a/snippets/auth-next/manage/auth_reauth_with_credential.js b/snippets/auth-next/manage/auth_reauth_with_credential.js new file mode 100644 index 00000000..74e84732 --- /dev/null +++ b/snippets/auth-next/manage/auth_reauth_with_credential.js @@ -0,0 +1,22 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/manage.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_reauth_with_credential_modular] +import { getAuth, reauthenticateWithCredential } from "firebase/auth"; + +const auth = getAuth(); +const user = auth.currentUser; + +// TODO(you): prompt the user to re-provide their sign-in credentials +const credential = promptForCredentials(); + +reauthenticateWithCredential(user, credential).then(() => { + // User re-authenticated. +}).catch((error) => { + // An error ocurred + // ... +}); +// [END auth_reauth_with_credential_modular] \ No newline at end of file diff --git a/snippets/auth-next/manage/auth_send_password_reset.js b/snippets/auth-next/manage/auth_send_password_reset.js new file mode 100644 index 00000000..9e8f2c3c --- /dev/null +++ b/snippets/auth-next/manage/auth_send_password_reset.js @@ -0,0 +1,19 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/manage.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_send_password_reset_modular] +import { getAuth, sendPasswordResetEmail } from "firebase/auth"; + +const auth = getAuth(); +const emailAddress = "user@example.com"; + +sendPasswordResetEmail(auth, emailAddress).then(() => { + // Email sent. +}).catch((error) => { + // An error ocurred + // ... +}); +// [END auth_send_password_reset_modular] \ No newline at end of file diff --git a/snippets/auth-next/manage/auth_update_password.js b/snippets/auth-next/manage/auth_update_password.js new file mode 100644 index 00000000..56e578b9 --- /dev/null +++ b/snippets/auth-next/manage/auth_update_password.js @@ -0,0 +1,21 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/manage.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_update_password_modular] +import { getAuth, updatePassword } from "firebase/auth"; + +const auth = getAuth(); + +const user = auth.currentUser; +const newPassword = getASecureRandomPassword(); + +updatePassword(user, newPassword).then(() => { + // Update successful. +}).catch((error) => { + // An error ocurred + // ... +}); +// [END auth_update_password_modular] \ No newline at end of file diff --git a/snippets/auth-next/manage/auth_update_user_email.js b/snippets/auth-next/manage/auth_update_user_email.js new file mode 100644 index 00000000..d3607f75 --- /dev/null +++ b/snippets/auth-next/manage/auth_update_user_email.js @@ -0,0 +1,17 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/manage.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_update_user_email_modular] +import { getAuth, updateEmail } from "firebase/auth"; +const auth = getAuth(); +updateEmail(auth.currentUser, "user@example.com").then(() => { + // Email updated! + // ... +}).catch((error) => { + // An error occurred + // ... +}); +// [END auth_update_user_email_modular] \ No newline at end of file diff --git a/snippets/auth-next/manage/auth_update_user_profile.js b/snippets/auth-next/manage/auth_update_user_profile.js new file mode 100644 index 00000000..2af9185a --- /dev/null +++ b/snippets/auth-next/manage/auth_update_user_profile.js @@ -0,0 +1,19 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/manage.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_update_user_profile_modular] +import { getAuth, updateProfile } from "firebase/auth"; +const auth = getAuth(); +updateProfile(auth.currentUser, { + displayName: "Jane Q. User", photoURL: "https://example.com/jane-q-user/profile.jpg" +}).then(() => { + // Profile updated! + // ... +}).catch((error) => { + // An error occurred + // ... +}); +// [END auth_update_user_profile_modular] \ No newline at end of file diff --git a/snippets/auth-next/manage/send_email_verification.js b/snippets/auth-next/manage/send_email_verification.js new file mode 100644 index 00000000..34f06fdb --- /dev/null +++ b/snippets/auth-next/manage/send_email_verification.js @@ -0,0 +1,19 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/manage.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START send_email_verification_modular] +import { getAuth, sendEmailVerification } from "firebase/auth"; + +const auth = getAuth(); +const user = auth.currentUser; + +sendEmailVerification(user).then(() => { + // Email sent. +}).catch((error) => { + // An error ocurred + // ... +}); +// [END send_email_verification_modular] \ No newline at end of file diff --git a/snippets/auth-next/microsoft-oauth/auth_msft_create_provider.js b/snippets/auth-next/microsoft-oauth/auth_msft_create_provider.js new file mode 100644 index 00000000..c0a9039c --- /dev/null +++ b/snippets/auth-next/microsoft-oauth/auth_msft_create_provider.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/microsoft-oauth.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_msft_create_provider_modular] +import { OAuthProvider } from "firebase/auth"; + +const provider = new OAuthProvider('microsoft.com'); +// [END auth_msft_create_provider_modular] \ No newline at end of file diff --git a/snippets/auth-next/microsoft-oauth/auth_msft_link_popup.js b/snippets/auth-next/microsoft-oauth/auth_msft_link_popup.js new file mode 100644 index 00000000..1a029c7e --- /dev/null +++ b/snippets/auth-next/microsoft-oauth/auth_msft_link_popup.js @@ -0,0 +1,26 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/microsoft-oauth.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_msft_link_popup_modular] +import { getAuth, linkWithPopup, OAuthProvider } from "firebase/auth"; + +const provider = new OAuthProvider('microsoft.com'); +const auth = getAuth(); + +linkWithPopup(auth.currentUser, provider) + .then((result) => { + // Microsoft credential is linked to the current user. + // IdP data available in result.additionalUserInfo.profile. + + // Get the OAuth access token and ID Token + const credential = OAuthProvider.credentialFromResult(result); + const accessToken = credential.accessToken; + const idToken = credential.idToken; + }) + .catch((error) => { + // Handle error. + }); +// [END auth_msft_link_popup_modular] \ No newline at end of file diff --git a/snippets/auth-next/microsoft-oauth/auth_msft_provider_params.js b/snippets/auth-next/microsoft-oauth/auth_msft_provider_params.js new file mode 100644 index 00000000..007ae646 --- /dev/null +++ b/snippets/auth-next/microsoft-oauth/auth_msft_provider_params.js @@ -0,0 +1,14 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/microsoft-oauth.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_msft_provider_params_modular] +provider.setCustomParameters({ + // Force re-consent. + prompt: 'consent', + // Target specific email with login hint. + login_hint: 'user@firstadd.onmicrosoft.com' +}); +// [END auth_msft_provider_params_modular] \ No newline at end of file diff --git a/snippets/auth-next/microsoft-oauth/auth_msft_provider_params_tenant.js b/snippets/auth-next/microsoft-oauth/auth_msft_provider_params_tenant.js new file mode 100644 index 00000000..341ff1f1 --- /dev/null +++ b/snippets/auth-next/microsoft-oauth/auth_msft_provider_params_tenant.js @@ -0,0 +1,15 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/microsoft-oauth.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_msft_provider_params_tenant_modular] +provider.setCustomParameters({ + // Optional "tenant" parameter in case you are using an Azure AD tenant. + // eg. '8eaef023-2b34-4da1-9baa-8bc8c9d6a490' or 'contoso.onmicrosoft.com' + // or "common" for tenant-independent tokens. + // The default value is "common". + tenant: 'TENANT_ID' +}); +// [END auth_msft_provider_params_tenant_modular] \ No newline at end of file diff --git a/snippets/auth-next/microsoft-oauth/auth_msft_provider_scopes.js b/snippets/auth-next/microsoft-oauth/auth_msft_provider_scopes.js new file mode 100644 index 00000000..ad86ac6e --- /dev/null +++ b/snippets/auth-next/microsoft-oauth/auth_msft_provider_scopes.js @@ -0,0 +1,10 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/microsoft-oauth.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_msft_provider_scopes_modular] +provider.addScope('mail.read'); +provider.addScope('calendars.read'); +// [END auth_msft_provider_scopes_modular] \ No newline at end of file diff --git a/snippets/auth-next/microsoft-oauth/auth_msft_reauth_popup.js b/snippets/auth-next/microsoft-oauth/auth_msft_reauth_popup.js new file mode 100644 index 00000000..1d222fba --- /dev/null +++ b/snippets/auth-next/microsoft-oauth/auth_msft_reauth_popup.js @@ -0,0 +1,27 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/microsoft-oauth.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_msft_reauth_popup_modular] +import { getAuth, reauthenticateWithPopup, OAuthProvider } from "firebase/auth"; + +const provider = new OAuthProvider('microsoft.com'); +const auth = getAuth(); +reauthenticateWithPopup(auth.currentUser, provider) + .then((result) => { + // User is re-authenticated with fresh tokens minted and + // should be able to perform sensitive operations like account + // deletion and email or password update. + // IdP data available in result.additionalUserInfo.profile. + + // Get the OAuth access token and ID Token + const credential = OAuthProvider.credentialFromResult(result); + const accessToken = credential.accessToken; + const idToken = credential.idToken; + }) + .catch((error) => { + // Handle error. + }); +// [END auth_msft_reauth_popup_modular] \ No newline at end of file diff --git a/snippets/auth-next/microsoft-oauth/auth_msft_signin_popup.js b/snippets/auth-next/microsoft-oauth/auth_msft_signin_popup.js new file mode 100644 index 00000000..a19e598d --- /dev/null +++ b/snippets/auth-next/microsoft-oauth/auth_msft_signin_popup.js @@ -0,0 +1,24 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/microsoft-oauth.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_msft_signin_popup_modular] +import { getAuth, signInWithPopup, OAuthProvider } from "firebase/auth"; + +const auth = getAuth(); +signInWithPopup(auth, provider) + .then((result) => { + // User is signed in. + // IdP data available in result.additionalUserInfo.profile. + + // Get the OAuth access token and ID Token + const credential = OAuthProvider.credentialFromResult(result); + const accessToken = credential.accessToken; + const idToken = credential.idToken; + }) + .catch((error) => { + // Handle error. + }); +// [END auth_msft_signin_popup_modular] \ No newline at end of file diff --git a/snippets/auth-next/microsoft-oauth/auth_msft_signin_redirect.js b/snippets/auth-next/microsoft-oauth/auth_msft_signin_redirect.js new file mode 100644 index 00000000..c020dcf5 --- /dev/null +++ b/snippets/auth-next/microsoft-oauth/auth_msft_signin_redirect.js @@ -0,0 +1,12 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/microsoft-oauth.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_msft_signin_redirect_modular] +import { getAuth, signInWithRedirect } from "firebase/auth"; + +const auth = getAuth(); +signInWithRedirect(auth, provider); +// [END auth_msft_signin_redirect_modular] \ No newline at end of file diff --git a/snippets/auth-next/microsoft-oauth/auth_msft_signin_redirect_result.js b/snippets/auth-next/microsoft-oauth/auth_msft_signin_redirect_result.js new file mode 100644 index 00000000..e1249de1 --- /dev/null +++ b/snippets/auth-next/microsoft-oauth/auth_msft_signin_redirect_result.js @@ -0,0 +1,24 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/microsoft-oauth.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_msft_signin_redirect_result_modular] +import { getAuth, getRedirectResult, OAuthProvider } from "firebase/auth"; + +const auth = getAuth(); +getRedirectResult(auth) + .then((result) => { + // User is signed in. + // IdP data available in result.additionalUserInfo.profile. + + // Get the OAuth access token and ID Token + const credential = OAuthProvider.credentialFromResult(result); + const accessToken = credential.accessToken; + const idToken = credential.idToken; + }) + .catch((error) => { + // Handle error. + }); +// [END auth_msft_signin_redirect_result_modular] \ No newline at end of file diff --git a/snippets/auth-next/multi-tenancy/multitenant_account_exists_popup.js b/snippets/auth-next/multi-tenancy/multitenant_account_exists_popup.js new file mode 100644 index 00000000..7bf33262 --- /dev/null +++ b/snippets/auth-next/multi-tenancy/multitenant_account_exists_popup.js @@ -0,0 +1,44 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/multi-tenancy.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START multitenant_account_exists_popup_modular] +import { signInWithPopup, fetchSignInMethodsForEmail, linkWithCredential } from "firebase/auth"; +// Step 1. +// User tries to sign in to the SAML provider in that tenant. +auth.tenantId = 'TENANT_ID'; +signInWithPopup(auth, samlProvider) + .catch((error) => { + // An error happened. + if (error.code === 'auth/account-exists-with-different-credential') { + // Step 2. + // User's email already exists. + // The pending SAML credential. + const pendingCred = error.credential; + // The credential's tenantId if needed: error.tenantId + // The provider account's email address. + const email = error.customData.email; + // Get sign-in methods for this email. + fetchSignInMethodsForEmail(email, auth) + .then((methods) => { + // Step 3. + // Ask the user to sign in with existing Google account. + if (methods[0] == 'google.com') { + signInWithPopup(auth, googleProvider) + .then((result) => { + // Step 4 + // Link the SAML AuthCredential to the existing user. + linkWithCredential(result.user, pendingCred) + .then((linkResult) => { + // SAML account successfully linked to the existing + // user. + goToApp(); + }); + }); + } + }); + } + }); +// [END multitenant_account_exists_popup_modular] \ No newline at end of file diff --git a/snippets/auth-next/multi-tenancy/multitenant_account_exists_redirect.js b/snippets/auth-next/multi-tenancy/multitenant_account_exists_redirect.js new file mode 100644 index 00000000..a81617dd --- /dev/null +++ b/snippets/auth-next/multi-tenancy/multitenant_account_exists_redirect.js @@ -0,0 +1,51 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/multi-tenancy.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START multitenant_account_exists_redirect_modular] +import { signInWithRedirect, getRedirectResult, fetchSignInMethodsForEmail, linkWithCredential } from "firebase/auth"; +// Step 1. +// User tries to sign in to SAML provider. +auth.tenantId = 'TENANT_ID'; +signInWithRedirect(auth, samlProvider); +var pendingCred; +// Redirect back from SAML IDP. auth.tenantId is null after redirecting. +getRedirectResult(auth).catch((error) => { + if (error.code === 'auth/account-exists-with-different-credential') { + // Step 2. + // User's email already exists. + const tenantId = error.tenantId; + // The pending SAML credential. + pendingCred = error.credential; + // The provider account's email address. + const email = error.customData.email; + // Need to set the tenant ID again as the page was reloaded and the + // previous setting was reset. + auth.tenantId = tenantId; + // Get sign-in methods for this email. + fetchSignInMethodsForEmail(auth, email) + .then((methods) => { + // Step 3. + // Ask the user to sign in with existing Google account. + if (methods[0] == 'google.com') { + signInWithRedirect(auth, googleProvider); + } + }); + } +}); + +// Redirect back from Google. auth.tenantId is null after redirecting. +getRedirectResult(auth).then((result) => { + // Step 4 + // Link the SAML AuthCredential to the existing user. + // result.user.tenantId is 'TENANT_ID'. + linkWithCredential(result.user, pendingCred) + .then((linkResult) => { + // SAML account successfully linked to the existing + // user. + goToApp(); + }); +}); +// [END multitenant_account_exists_redirect_modular] \ No newline at end of file diff --git a/snippets/auth-next/multi-tenancy/multitenant_account_linking.js b/snippets/auth-next/multi-tenancy/multitenant_account_linking.js new file mode 100644 index 00000000..c43ff90d --- /dev/null +++ b/snippets/auth-next/multi-tenancy/multitenant_account_linking.js @@ -0,0 +1,34 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/multi-tenancy.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START multitenant_account_linking_modular] +import { signInWithPopup, EmailAuthProvider, linkWithCredential, SAMLAuthProvider, signInWithCredential } from "firebase/auth"; +// Switch to TENANT_ID1 +auth.tenantId = 'TENANT_ID1'; + +// Sign-in with popup +signInWithPopup(auth, provider) + .then((userCredential) => { + // Existing user with e.g. SAML provider. + const prevUser = userCredential.user; + const emailCredential = + EmailAuthProvider.credential(email, password); + return linkWithCredential(prevUser, emailCredential) + .then((linkResult) => { + // Sign in with the newly linked credential + const linkCredential = SAMLAuthProvider.credentialFromResult(linkResult); + return signInWithCredential(auth, linkCredential); + }) + .then((signInResult) => { + // Handle sign in of merged user + // ... + }); + }) + .catch((error) => { + // Handle / display error. + // ... + }); +// [END multitenant_account_linking_modular] \ No newline at end of file diff --git a/snippets/auth-next/multi-tenancy/multitenant_create_custom_token.js b/snippets/auth-next/multi-tenancy/multitenant_create_custom_token.js new file mode 100644 index 00000000..1426cf8a --- /dev/null +++ b/snippets/auth-next/multi-tenancy/multitenant_create_custom_token.js @@ -0,0 +1,20 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/multi-tenancy.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START multitenant_create_custom_token_modular] +// Ensure you're using a tenant-aware auth instance +const tenantManager = admin.auth().tenantManager(); +const tenantAuth = tenantManager.authForTenant('TENANT_ID1'); + +// Create a custom token in the usual manner +tenantAuth.createCustomToken(uid) + .then((customToken) => { + // Send token back to client + }) + .catch((error) => { + console.log('Error creating custom token:', error); + }); +// [END multitenant_create_custom_token_modular] \ No newline at end of file diff --git a/snippets/auth-next/multi-tenancy/multitenant_send_emaillink.js b/snippets/auth-next/multi-tenancy/multitenant_send_emaillink.js new file mode 100644 index 00000000..4c4d95c5 --- /dev/null +++ b/snippets/auth-next/multi-tenancy/multitenant_send_emaillink.js @@ -0,0 +1,23 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/multi-tenancy.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START multitenant_send_emaillink_modular] +import { sendSignInLinkToEmail } from "firebase/auth"; +// Switch to TENANT_ID1 +auth.tenantId = 'TENANT_ID1'; + +sendSignInLinkToEmail(auth, email, actionCodeSettings) + .then(() => { + // The link was successfully sent. Inform the user. + // Save the email locally so you don't need to ask the user for it again + // if they open the link on the same device. + window.localStorage.setItem('emailForSignIn', email); + }) + .catch((error) => { + // Handle / display error. + // ... + }); +// [END multitenant_send_emaillink_modular] \ No newline at end of file diff --git a/snippets/auth-next/multi-tenancy/multitenant_set_tenant.js b/snippets/auth-next/multi-tenancy/multitenant_set_tenant.js new file mode 100644 index 00000000..9510bc5e --- /dev/null +++ b/snippets/auth-next/multi-tenancy/multitenant_set_tenant.js @@ -0,0 +1,12 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/multi-tenancy.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START multitenant_set_tenant_modular] +import { getAuth } from "firebase/auth"; +const auth = getAuth(); +const tenantId = "TENANT_ID1"; +auth.tenantId = tenantId; +// [END multitenant_set_tenant_modular] \ No newline at end of file diff --git a/snippets/auth-next/multi-tenancy/multitenant_signin_custom_token.js b/snippets/auth-next/multi-tenancy/multitenant_signin_custom_token.js new file mode 100644 index 00000000..b9fa9c58 --- /dev/null +++ b/snippets/auth-next/multi-tenancy/multitenant_signin_custom_token.js @@ -0,0 +1,16 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/multi-tenancy.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START multitenant_signin_custom_token_modular] +import { signInWithCustomToken } from "firebase/auth"; +auth.tenantId = 'TENANT_ID1'; + +signInWithCustomToken(auth, token) + .catch((error) => { + // Handle / display error. + // ... + }); +// [END multitenant_signin_custom_token_modular] \ No newline at end of file diff --git a/snippets/auth-next/multi-tenancy/multitenant_signin_emaillink.js b/snippets/auth-next/multi-tenancy/multitenant_signin_emaillink.js new file mode 100644 index 00000000..0032af1c --- /dev/null +++ b/snippets/auth-next/multi-tenancy/multitenant_signin_emaillink.js @@ -0,0 +1,29 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/multi-tenancy.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START multitenant_signin_emaillink_modular] +import { isSignInWithEmailLink, parseActionCodeURL, signInWithEmailLink } from "firebase/auth"; +if (isSignInWithEmailLink(auth, window.location.href)) { + const actionCodeUrl = parseActionCodeURL(window.location.href); + if (actionCodeUrl.tenantId) { + auth.tenantId = actionCodeUrl.tenantId; + } + let email = window.localStorage.getItem('emailForSignIn'); + if (!email) { + // User opened the link on a different device. To prevent session fixation + // attacks, ask the user to provide the associated email again. For example: + email = window.prompt('Please provide your email for confirmation'); + } + // The client SDK will parse the code from the link for you. + signInWithEmailLink(auth, email, window.location.href) + .then((result) => { + // User is signed in. + // tenant ID available in result.user.tenantId. + // Clear email from storage. + window.localStorage.removeItem('emailForSignIn'); + }); +} +// [END multitenant_signin_emaillink_modular] \ No newline at end of file diff --git a/snippets/auth-next/multi-tenancy/multitenant_signin_password.js b/snippets/auth-next/multi-tenancy/multitenant_signin_password.js new file mode 100644 index 00000000..73642a8d --- /dev/null +++ b/snippets/auth-next/multi-tenancy/multitenant_signin_password.js @@ -0,0 +1,19 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/multi-tenancy.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START multitenant_signin_password_modular] +import { signInWithEmailAndPassword } from "firebase/auth"; +auth.tenantId = 'TENANT_ID'; + +signInWithEmailAndPassword(auth, email, password) + .then((userCredential) => { + // User is signed in. + // userCredential.user.tenantId is 'TENANT_ID'. + }).catch((error) => { + // Handle / display error. + // ... + }); +// [END multitenant_signin_password_modular] \ No newline at end of file diff --git a/snippets/auth-next/multi-tenancy/multitenant_signin_password_demo.js b/snippets/auth-next/multi-tenancy/multitenant_signin_password_demo.js new file mode 100644 index 00000000..87882773 --- /dev/null +++ b/snippets/auth-next/multi-tenancy/multitenant_signin_password_demo.js @@ -0,0 +1,33 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/multi-tenancy.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START multitenant_signin_password_demo_modular] +import { signInWithEmailAndPassword, onAuthStateChanged } from "firebase/auth"; +// Switch to TENANT_ID1 +auth.tenantId = 'TENANT_ID1'; + +// Sign in with tenant +signInWithEmailAndPassword(auth, email, password) + .then((userCredential) => { + // User is signed in. + const user = userCredential.user; + // user.tenantId is set to 'TENANT_ID1'. + // Switch to 'TENANT_ID2'. + auth.tenantId = 'TENANT_ID2'; + // auth.currentUser still points to the user. + // auth.currentUser.tenantId is 'TENANT_ID1'. + }); + +// You could also get the current user from Auth state observer. +onAuthStateChanged(auth, (user) => { + if (user) { + // User is signed in. + // user.tenantId is set to 'TENANT_ID1'. + } else { + // No user is signed in. + } +}); +// [END multitenant_signin_password_demo_modular] \ No newline at end of file diff --git a/snippets/auth-next/multi-tenancy/multitenant_signin_saml_popup.js b/snippets/auth-next/multi-tenancy/multitenant_signin_saml_popup.js new file mode 100644 index 00000000..6aa90550 --- /dev/null +++ b/snippets/auth-next/multi-tenancy/multitenant_signin_saml_popup.js @@ -0,0 +1,25 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/multi-tenancy.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START multitenant_signin_saml_popup_modular] +import { signInWithPopup } from "firebase/auth"; +// Switch to TENANT_ID1. +auth.tenantId = 'TENANT_ID1'; + +// Sign-in with popup. +signInWithPopup(auth, provider) + .then((userCredential) => { + // User is signed in. + const user = userCredential.user; + // user.tenantId is set to 'TENANT_ID1'. + // Provider data available from the result.user.getIdToken() + // or from result.user.providerData + }) + .catch((error) => { + // Handle / display error. + // ... + }); +// [END multitenant_signin_saml_popup_modular] \ No newline at end of file diff --git a/snippets/auth-next/multi-tenancy/multitenant_signin_saml_redirect.js b/snippets/auth-next/multi-tenancy/multitenant_signin_saml_redirect.js new file mode 100644 index 00000000..d3552a56 --- /dev/null +++ b/snippets/auth-next/multi-tenancy/multitenant_signin_saml_redirect.js @@ -0,0 +1,29 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/multi-tenancy.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START multitenant_signin_saml_redirect_modular] +import { signInWithRedirect, getRedirectResult } from "firebase/auth"; +// Switch to TENANT_ID1. +auth.tenantId = 'TENANT_ID1'; + +// Sign-in with redirect. +signInWithRedirect(auth, provider); + +// After the user completes sign-in and returns to the app, you can get +// the sign-in result by calling getRedirectResult. However, if they sign out +// and sign in again with an IdP, no tenant is used. +getRedirectResult(auth) + .then((result) => { + // User is signed in. + // The tenant ID available in result.user.tenantId. + // Provider data available from the result.user.getIdToken() + // or from result.user.providerData + }) + .catch((error) => { + // Handle / display error. + // ... + }); +// [END multitenant_signin_saml_redirect_modular] \ No newline at end of file diff --git a/snippets/auth-next/multi-tenancy/multitenant_signup_password.js b/snippets/auth-next/multi-tenancy/multitenant_signup_password.js new file mode 100644 index 00000000..d89d5edf --- /dev/null +++ b/snippets/auth-next/multi-tenancy/multitenant_signup_password.js @@ -0,0 +1,19 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/multi-tenancy.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START multitenant_signup_password_modular] +import { createUserWithEmailAndPassword } from "firebase/auth"; +auth.tenantId = 'TENANT_ID'; + +createUserWithEmailAndPassword(auth, email, password) + .then((userCredential) => { + // User is signed in. + // userCredential.user.tenantId is 'TENANT_ID'. + }).catch((error) => { + // Handle / display error. + // ... + }); +// [END multitenant_signup_password_modular] \ No newline at end of file diff --git a/snippets/auth-next/multi-tenancy/multitenant_switch_tenant.js b/snippets/auth-next/multi-tenancy/multitenant_switch_tenant.js new file mode 100644 index 00000000..455e4e3b --- /dev/null +++ b/snippets/auth-next/multi-tenancy/multitenant_switch_tenant.js @@ -0,0 +1,15 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/multi-tenancy.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START multitenant_switch_tenant_modular] +// One Auth instance +// Switch to tenant1 +auth.tenantId = "TENANT_ID1"; +// Switch to tenant2 +auth.tenantId = "TENANT_ID2"; +// Switch back to project level IdPs +auth.tenantId = null; +// [END multitenant_switch_tenant_modular] \ No newline at end of file diff --git a/snippets/auth-next/multi-tenancy/multitenant_switch_tenant_multiinstance.js b/snippets/auth-next/multi-tenancy/multitenant_switch_tenant_multiinstance.js new file mode 100644 index 00000000..72550493 --- /dev/null +++ b/snippets/auth-next/multi-tenancy/multitenant_switch_tenant_multiinstance.js @@ -0,0 +1,19 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/multi-tenancy.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START multitenant_switch_tenant_multiinstance_modular] +// Multiple Auth instances +import { initializeApp } from "firebase/app"; +import { getAuth } from "firebase/auth"; +const firebaseApp1 = initializeApp(firebaseConfig1, 'app1_for_tenantId1'); +const firebaseApp2 = initializeApp(firebaseConfig2, 'app2_for_tenantId2'); + +const auth1 = getAuth(firebaseApp1); +const auth2 = getAuth(firebaseApp2); + +auth1.tenantId = "TENANT_ID1"; +auth2.tenantId = "TENANT_ID2"; +// [END multitenant_switch_tenant_multiinstance_modular] \ No newline at end of file diff --git a/snippets/auth-next/oidc/auth_oidc_direct_sign_in.js b/snippets/auth-next/oidc/auth_oidc_direct_sign_in.js new file mode 100644 index 00000000..558fd915 --- /dev/null +++ b/snippets/auth-next/oidc/auth_oidc_direct_sign_in.js @@ -0,0 +1,31 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/oidc.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_oidc_direct_sign_in_modular] +import { getAuth, OAuthProvider, signInWithCredential } from "firebase/auth"; + +const auth = getAuth(); +const credential = provider.credential({ + idToken: oidcIdToken, +}); +signInWithCredential(auth, credential) + .then((result) => { + // User is signed in. + const newCredential = OAuthProvider.credentialFromResult(result); + // This gives you a new access token for the OIDC provider. You can use it to directly interact with that provider. + }) + .catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The AuthCredential type that was used. + const credential = OAuthProvider.credentialFromError(error); + // Handle / display error. + // ... + }); +// [END auth_oidc_direct_sign_in_modular] \ No newline at end of file diff --git a/snippets/auth-next/oidc/auth_oidc_provider_create.js b/snippets/auth-next/oidc/auth_oidc_provider_create.js new file mode 100644 index 00000000..ce1846b9 --- /dev/null +++ b/snippets/auth-next/oidc/auth_oidc_provider_create.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/oidc.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_oidc_provider_create_modular] +import { OAuthProvider } from "firebase/auth"; + +const provider = new OAuthProvider("oidc.myProvider"); +// [END auth_oidc_provider_create_modular] \ No newline at end of file diff --git a/snippets/auth-next/oidc/auth_oidc_signin_popup.js b/snippets/auth-next/oidc/auth_oidc_signin_popup.js new file mode 100644 index 00000000..130d617e --- /dev/null +++ b/snippets/auth-next/oidc/auth_oidc_signin_popup.js @@ -0,0 +1,27 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/oidc.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_oidc_signin_popup_modular] +import { getAuth, signInWithPopup, OAuthProvider } from "firebase/auth"; + +const auth = getAuth(); +signInWithPopup(auth, provider) + .then((result) => { + // User is signed in. + const credential = OAuthProvider.credentialFromResult(result); + // This gives you an access token for the OIDC provider. You can use it to directly interact with that provider + }).catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The AuthCredential type that was used. + const credential = OAuthProvider.credentialFromError(error); + // Handle / display error. + // ... + }); +// [END auth_oidc_signin_popup_modular] \ No newline at end of file diff --git a/snippets/auth-next/oidc/auth_oidc_signin_redirect.js b/snippets/auth-next/oidc/auth_oidc_signin_redirect.js new file mode 100644 index 00000000..2038d116 --- /dev/null +++ b/snippets/auth-next/oidc/auth_oidc_signin_redirect.js @@ -0,0 +1,12 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/oidc.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_oidc_signin_redirect_modular] +import { getAuth, signInWithRedirect } from "firebase/auth"; + +const auth = getAuth(); +signInWithRedirect(auth, provider); +// [END auth_oidc_signin_redirect_modular] \ No newline at end of file diff --git a/snippets/auth-next/oidc/auth_oidc_signin_redirect_result.js b/snippets/auth-next/oidc/auth_oidc_signin_redirect_result.js new file mode 100644 index 00000000..bc76a620 --- /dev/null +++ b/snippets/auth-next/oidc/auth_oidc_signin_redirect_result.js @@ -0,0 +1,28 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/oidc.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_oidc_signin_redirect_result_modular] +import { getAuth, getRedirectResult, OAuthProvider } from "firebase/auth"; + +const auth = getAuth(); +getRedirectResult(auth) + .then((result) => { + // User is signed in. + const credential = OAuthProvider.credentialFromResult(result); + // This gives you an access token for the OIDC provider. You can use it to directly interact with that provider + }) + .catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The AuthCredential type that was used. + const credential = OAuthProvider.credentialFromError(error); + // Handle / display error. + // ... + }); +// [END auth_oidc_signin_redirect_result_modular] \ No newline at end of file diff --git a/snippets/auth-next/phone-auth/auth_get_recaptcha_response.js b/snippets/auth-next/phone-auth/auth_get_recaptcha_response.js new file mode 100644 index 00000000..f19a93e6 --- /dev/null +++ b/snippets/auth-next/phone-auth/auth_get_recaptcha_response.js @@ -0,0 +1,9 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/phone-auth.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_get_recaptcha_response_modular] +const recaptchaResponse = grecaptcha.getResponse(recaptchaWidgetId); +// [END auth_get_recaptcha_response_modular] \ No newline at end of file diff --git a/snippets/auth-next/phone-auth/auth_phone_recaptcha_render.js b/snippets/auth-next/phone-auth/auth_phone_recaptcha_render.js new file mode 100644 index 00000000..27e9d8c9 --- /dev/null +++ b/snippets/auth-next/phone-auth/auth_phone_recaptcha_render.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/phone-auth.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_phone_recaptcha_render_modular] +recaptchaVerifier.render().then((widgetId) => { + window.recaptchaWidgetId = widgetId; +}); +// [END auth_phone_recaptcha_render_modular] \ No newline at end of file diff --git a/snippets/auth-next/phone-auth/auth_phone_recaptcha_verifier_invisible.js b/snippets/auth-next/phone-auth/auth_phone_recaptcha_verifier_invisible.js new file mode 100644 index 00000000..7fd16eea --- /dev/null +++ b/snippets/auth-next/phone-auth/auth_phone_recaptcha_verifier_invisible.js @@ -0,0 +1,18 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/phone-auth.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_phone_recaptcha_verifier_invisible_modular] +import { getAuth, RecaptchaVerifier } from "firebase/auth"; + +const auth = getAuth(); +window.recaptchaVerifier = new RecaptchaVerifier(auth, 'sign-in-button', { + 'size': 'invisible', + 'callback': (response) => { + // reCAPTCHA solved, allow signInWithPhoneNumber. + onSignInSubmit(); + } +}); +// [END auth_phone_recaptcha_verifier_invisible_modular] \ No newline at end of file diff --git a/snippets/auth-next/phone-auth/auth_phone_recaptcha_verifier_simple.js b/snippets/auth-next/phone-auth/auth_phone_recaptcha_verifier_simple.js new file mode 100644 index 00000000..b57eb37b --- /dev/null +++ b/snippets/auth-next/phone-auth/auth_phone_recaptcha_verifier_simple.js @@ -0,0 +1,12 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/phone-auth.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_phone_recaptcha_verifier_simple_modular] +import { getAuth, RecaptchaVerifier } from "firebase/auth"; + +const auth = getAuth(); +window.recaptchaVerifier = new RecaptchaVerifier(auth, 'recaptcha-container', {}); +// [END auth_phone_recaptcha_verifier_simple_modular] \ No newline at end of file diff --git a/snippets/auth-next/phone-auth/auth_phone_recaptcha_verifier_visible.js b/snippets/auth-next/phone-auth/auth_phone_recaptcha_verifier_visible.js new file mode 100644 index 00000000..42ebbe69 --- /dev/null +++ b/snippets/auth-next/phone-auth/auth_phone_recaptcha_verifier_visible.js @@ -0,0 +1,22 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/phone-auth.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_phone_recaptcha_verifier_visible_modular] +import { getAuth, RecaptchaVerifier } from "firebase/auth"; + +const auth = getAuth(); +window.recaptchaVerifier = new RecaptchaVerifier(auth, 'recaptcha-container', { + 'size': 'normal', + 'callback': (response) => { + // reCAPTCHA solved, allow signInWithPhoneNumber. + // ... + }, + 'expired-callback': () => { + // Response expired. Ask user to solve reCAPTCHA again. + // ... + } +}); +// [END auth_phone_recaptcha_verifier_visible_modular] \ No newline at end of file diff --git a/snippets/auth-next/phone-auth/auth_phone_signin.js b/snippets/auth-next/phone-auth/auth_phone_signin.js new file mode 100644 index 00000000..31c3d248 --- /dev/null +++ b/snippets/auth-next/phone-auth/auth_phone_signin.js @@ -0,0 +1,24 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/phone-auth.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_phone_signin_modular] +import { getAuth, signInWithPhoneNumber } from "firebase/auth"; + +const phoneNumber = getPhoneNumberFromUserInput(); +const appVerifier = window.recaptchaVerifier; + +const auth = getAuth(); +signInWithPhoneNumber(auth, phoneNumber, appVerifier) + .then((confirmationResult) => { + // SMS sent. Prompt user to type the code from the message, then sign the + // user in with confirmationResult.confirm(code). + window.confirmationResult = confirmationResult; + // ... + }).catch((error) => { + // Error; SMS not sent + // ... + }); +// [END auth_phone_signin_modular] \ No newline at end of file diff --git a/snippets/auth-next/phone-auth/auth_phone_verify_code.js b/snippets/auth-next/phone-auth/auth_phone_verify_code.js new file mode 100644 index 00000000..299f8cf3 --- /dev/null +++ b/snippets/auth-next/phone-auth/auth_phone_verify_code.js @@ -0,0 +1,17 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/phone-auth.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_phone_verify_code_modular] +const code = getCodeFromUserInput(); +confirmationResult.confirm(code).then((result) => { + // User signed in successfully. + const user = result.user; + // ... +}).catch((error) => { + // User couldn't sign in (bad verification code?) + // ... +}); +// [END auth_phone_verify_code_modular] \ No newline at end of file diff --git a/snippets/auth-next/saml/auth_saml_provider_create.js b/snippets/auth-next/saml/auth_saml_provider_create.js new file mode 100644 index 00000000..e492d490 --- /dev/null +++ b/snippets/auth-next/saml/auth_saml_provider_create.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/saml.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_saml_provider_create_modular] +import { SAMLAuthProvider } from "firebase/auth"; + +const provider = new SAMLAuthProvider("saml.myProvider"); +// [END auth_saml_provider_create_modular] \ No newline at end of file diff --git a/snippets/auth-next/saml/auth_saml_signin_popup.js b/snippets/auth-next/saml/auth_saml_signin_popup.js new file mode 100644 index 00000000..3a922a9a --- /dev/null +++ b/snippets/auth-next/saml/auth_saml_signin_popup.js @@ -0,0 +1,27 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/saml.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_saml_signin_popup_modular] +import { getAuth, signInWithPopup, SAMLAuthProvider } from "firebase/auth"; + +const auth = getAuth(); +signInWithPopup(auth, provider) + .then((result) => { + // User is signed in. + // Provider data available from the result.user.getIdToken() + // or from result.user.providerData + }).catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The AuthCredential type that was used. + const credential = SAMLAuthProvider.credentialFromError(error); + // Handle / display error. + // ... + }); +// [END auth_saml_signin_popup_modular] \ No newline at end of file diff --git a/snippets/auth-next/saml/auth_saml_signin_redirect.js b/snippets/auth-next/saml/auth_saml_signin_redirect.js new file mode 100644 index 00000000..619af67f --- /dev/null +++ b/snippets/auth-next/saml/auth_saml_signin_redirect.js @@ -0,0 +1,12 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/saml.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_saml_signin_redirect_modular] +import { getAuth, signInWithRedirect } from "firebase/auth"; + +const auth = getAuth(); +signInWithRedirect(auth, provider); +// [END auth_saml_signin_redirect_modular] \ No newline at end of file diff --git a/snippets/auth-next/saml/auth_saml_signin_redirect_result.js b/snippets/auth-next/saml/auth_saml_signin_redirect_result.js new file mode 100644 index 00000000..e4bf73de --- /dev/null +++ b/snippets/auth-next/saml/auth_saml_signin_redirect_result.js @@ -0,0 +1,28 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/saml.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_saml_signin_redirect_result_modular] +import { getAuth, getRedirectResult, SAMLAuthProvider } from "firebase/auth"; + +const auth = getAuth(); +getRedirectResult(auth) + .then((result) => { + // User is signed in. + // Provider data available from the result.user.getIdToken() + // or from result.user.providerData + }) + .catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The AuthCredential type that was used. + const credential = SAMLAuthProvider.credentialFromError(error); + // Handle / display error. + // ... + }); +// [END auth_saml_signin_redirect_result_modular] \ No newline at end of file diff --git a/snippets/auth-next/service-worker-sessions/auth_svc_get_idtoken.js b/snippets/auth-next/service-worker-sessions/auth_svc_get_idtoken.js new file mode 100644 index 00000000..dd78ada8 --- /dev/null +++ b/snippets/auth-next/service-worker-sessions/auth_svc_get_idtoken.js @@ -0,0 +1,18 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/service-worker-sessions.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_svc_get_idtoken_modular] +import { getAuth, getIdToken } from "firebase/auth"; + +const auth = getAuth(); +getIdToken(auth.currentUser) + .then((idToken) => { + // idToken can be passed back to server. + }) + .catch((error) => { + // Error occurred. + }); +// [END auth_svc_get_idtoken_modular] \ No newline at end of file diff --git a/snippets/auth-next/service-worker-sessions/auth_svc_intercept.js b/snippets/auth-next/service-worker-sessions/auth_svc_intercept.js new file mode 100644 index 00000000..419614ff --- /dev/null +++ b/snippets/auth-next/service-worker-sessions/auth_svc_intercept.js @@ -0,0 +1,82 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/service-worker-sessions.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_svc_intercept_modular] +const getOriginFromUrl = (url) => { + // https://stackoverflow.com/questions/1420881/how-to-extract-base-url-from-a-string-in-javascript + const pathArray = url.split('/'); + const protocol = pathArray[0]; + const host = pathArray[2]; + return protocol + '//' + host; +}; + +// Get underlying body if available. Works for text and json bodies. +const getBodyContent = (req) => { + return Promise.resolve().then(() => { + if (req.method !== 'GET') { + if (req.headers.get('Content-Type').indexOf('json') !== -1) { + return req.json() + .then((json) => { + return JSON.stringify(json); + }); + } else { + return req.text(); + } + } + }).catch((error) => { + // Ignore error. + }); +}; + +self.addEventListener('fetch', (event) => { + /** @type {FetchEvent} */ + const evt = event; + + const requestProcessor = (idToken) => { + let req = evt.request; + let processRequestPromise = Promise.resolve(); + // For same origin https requests, append idToken to header. + if (self.location.origin == getOriginFromUrl(evt.request.url) && + (self.location.protocol == 'https:' || + self.location.hostname == 'localhost') && + idToken) { + // Clone headers as request headers are immutable. + const headers = new Headers(); + req.headers.forEach((val, key) => { + headers.append(key, val); + }); + // Add ID token to header. + headers.append('Authorization', 'Bearer ' + idToken); + processRequestPromise = getBodyContent(req).then((body) => { + try { + req = new Request(req.url, { + method: req.method, + headers: headers, + mode: 'same-origin', + credentials: req.credentials, + cache: req.cache, + redirect: req.redirect, + referrer: req.referrer, + body, + // bodyUsed: req.bodyUsed, + // context: req.context + }); + } catch (e) { + // This will fail for CORS requests. We just continue with the + // fetch caching logic below and do not pass the ID token. + } + }); + } + return processRequestPromise.then(() => { + return fetch(req); + }); + }; + // Fetch the resource after checking for the ID token. + // This can also be integrated with existing logic to serve cached files + // in offline mode. + evt.respondWith(getIdTokenPromise().then(requestProcessor, requestProcessor)); +}); +// [END auth_svc_intercept_modular] \ No newline at end of file diff --git a/snippets/auth-next/service-worker-sessions/auth_svc_listen_activate.js b/snippets/auth-next/service-worker-sessions/auth_svc_listen_activate.js new file mode 100644 index 00000000..0337fc83 --- /dev/null +++ b/snippets/auth-next/service-worker-sessions/auth_svc_listen_activate.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/service-worker-sessions.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_svc_listen_activate_modular] +self.addEventListener('activate', (event) => { + event.waitUntil(clients.claim()); +}); +// [END auth_svc_listen_activate_modular] \ No newline at end of file diff --git a/snippets/auth-next/service-worker-sessions/auth_svc_register.js b/snippets/auth-next/service-worker-sessions/auth_svc_register.js new file mode 100644 index 00000000..0cb36be3 --- /dev/null +++ b/snippets/auth-next/service-worker-sessions/auth_svc_register.js @@ -0,0 +1,12 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/service-worker-sessions.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_svc_register_modular] +// Install servicerWorker if supported on sign-in/sign-up page. +if ('serviceWorker' in navigator) { + navigator.serviceWorker.register('/service-worker.js', {scope: '/'}); +} +// [END auth_svc_register_modular] \ No newline at end of file diff --git a/snippets/auth-next/service-worker-sessions/auth_svc_sign_in_email.js b/snippets/auth-next/service-worker-sessions/auth_svc_sign_in_email.js new file mode 100644 index 00000000..02e3a62a --- /dev/null +++ b/snippets/auth-next/service-worker-sessions/auth_svc_sign_in_email.js @@ -0,0 +1,21 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/service-worker-sessions.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_svc_sign_in_email_modular] +import { getAuth, signInWithEmailAndPassword } from "firebase/auth"; + +// Sign in screen. +const auth = getAuth(); +signInWithEmailAndPassword(auth, email, password) + .then((result) => { + // Redirect to profile page after sign-in. The service worker will detect + // this and append the ID token to the header. + window.location.assign('/profile'); + }) + .catch((error) => { + // Error occurred. + }); +// [END auth_svc_sign_in_email_modular] \ No newline at end of file diff --git a/snippets/auth-next/service-worker-sessions/auth_svc_subscribe.js b/snippets/auth-next/service-worker-sessions/auth_svc_subscribe.js new file mode 100644 index 00000000..76448570 --- /dev/null +++ b/snippets/auth-next/service-worker-sessions/auth_svc_subscribe.js @@ -0,0 +1,36 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/service-worker-sessions.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_svc_subscribe_modular] +import { initializeApp } from "firebase/app"; +import { getAuth, onAuthStateChanged, getIdToken } from "firebase/auth"; + +// Initialize the Firebase app in the service worker script. +initializeApp(config); + +/** + * Returns a promise that resolves with an ID token if available. + * @return {!Promise} The promise that resolves with an ID token if + * available. Otherwise, the promise resolves with null. + */ +const auth = getAuth(); +const getIdTokenPromise = () => { + return new Promise((resolve, reject) => { + const unsubscribe = onAuthStateChanged(auth, (user) => { + unsubscribe(); + if (user) { + getIdToken(user).then((idToken) => { + resolve(idToken); + }, (error) => { + resolve(null); + }); + } else { + resolve(null); + } + }); + }); +}; +// [END auth_svc_subscribe_modular] \ No newline at end of file diff --git a/snippets/auth-next/twitter/auth_twitter_provider_create.js b/snippets/auth-next/twitter/auth_twitter_provider_create.js new file mode 100644 index 00000000..7fb82507 --- /dev/null +++ b/snippets/auth-next/twitter/auth_twitter_provider_create.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/twitter.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_twitter_provider_create_modular] +import { TwitterAuthProvider } from "firebase/auth"; + +const provider = new TwitterAuthProvider(); +// [END auth_twitter_provider_create_modular] \ No newline at end of file diff --git a/snippets/auth-next/twitter/auth_twitter_provider_credential.js b/snippets/auth-next/twitter/auth_twitter_provider_credential.js new file mode 100644 index 00000000..e25b369c --- /dev/null +++ b/snippets/auth-next/twitter/auth_twitter_provider_credential.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/twitter.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_twitter_provider_credential_modular] +import { TwitterAuthProvider } from "firebase/auth"; + +const credential = TwitterAuthProvider.credential(accessToken, secret); +// [END auth_twitter_provider_credential_modular] \ No newline at end of file diff --git a/snippets/auth-next/twitter/auth_twitter_provider_params.js b/snippets/auth-next/twitter/auth_twitter_provider_params.js new file mode 100644 index 00000000..14d457f0 --- /dev/null +++ b/snippets/auth-next/twitter/auth_twitter_provider_params.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/twitter.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_twitter_provider_params_modular] +provider.setCustomParameters({ + 'lang': 'es' +}); +// [END auth_twitter_provider_params_modular] \ No newline at end of file diff --git a/snippets/auth-next/twitter/auth_twitter_signin_popup.js b/snippets/auth-next/twitter/auth_twitter_signin_popup.js new file mode 100644 index 00000000..adb7322e --- /dev/null +++ b/snippets/auth-next/twitter/auth_twitter_signin_popup.js @@ -0,0 +1,33 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/twitter.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_twitter_signin_popup_modular] +import { getAuth, signInWithPopup, TwitterAuthProvider } from "firebase/auth"; + +const auth = getAuth(); +signInWithPopup(auth, provider) + .then((result) => { + // This gives you a the Twitter OAuth 1.0 Access Token and Secret. + // You can use these server side with your app's credentials to access the Twitter API. + const credential = TwitterAuthProvider.credentialFromResult(result); + const token = credential.accessToken; + const secret = credential.secret; + + // The signed-in user info. + const user = result.user; + // IdP data available using getAdditionalUserInfo(result) + // ... + }).catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The AuthCredential type that was used. + const credential = TwitterAuthProvider.credentialFromError(error); + // ... + }); +// [END auth_twitter_signin_popup_modular] \ No newline at end of file diff --git a/snippets/auth-next/twitter/auth_twitter_signin_redirect_result.js b/snippets/auth-next/twitter/auth_twitter_signin_redirect_result.js new file mode 100644 index 00000000..357faa4e --- /dev/null +++ b/snippets/auth-next/twitter/auth_twitter_signin_redirect_result.js @@ -0,0 +1,34 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/twitter.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_twitter_signin_redirect_result_modular] +import { getAuth, getRedirectResult, TwitterAuthProvider } from "firebase/auth"; + +const auth = getAuth(); +getRedirectResult(auth) + .then((result) => { + // This gives you a the Twitter OAuth 1.0 Access Token and Secret. + // You can use these server side with your app's credentials to access the Twitter API. + const credential = TwitterAuthProvider.credentialFromResult(result); + const token = credential.accessToken; + const secret = credential.secret; + // ... + + // The signed-in user info. + const user = result.user; + // IdP data available using getAdditionalUserInfo(result) + // ... + }).catch((error) => { + // Handle Errors here. + const errorCode = error.code; + const errorMessage = error.message; + // The email of the user's account used. + const email = error.customData.email; + // The AuthCredential type that was used. + const credential = TwitterAuthProvider.credentialFromError(error); + // ... + }); +// [END auth_twitter_signin_redirect_result_modular] \ No newline at end of file diff --git a/snippets/auth-next/yahoo-oauth/auth_yahoo_link_popup.js b/snippets/auth-next/yahoo-oauth/auth_yahoo_link_popup.js new file mode 100644 index 00000000..1a921bce --- /dev/null +++ b/snippets/auth-next/yahoo-oauth/auth_yahoo_link_popup.js @@ -0,0 +1,25 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/yahoo-oauth.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_yahoo_link_popup_modular] +import { getAuth, linkWithPopup, OAuthProvider } from "firebase/auth"; + +const provider = new OAuthProvider('yahoo.com'); +const auth = getAuth(); +linkWithPopup(auth.currentUser, provider) + .then((result) => { + // Yahoo credential is linked to the current user. + // IdP data available in result.additionalUserInfo.profile. + + // Get the OAuth access token and ID Token + const credential = OAuthProvider.credentialFromResult(result); + const accessToken = credential.accessToken; + const idToken = credential.idToken; + }) + .catch((error) => { + // Handle error. + }); +// [END auth_yahoo_link_popup_modular] \ No newline at end of file diff --git a/snippets/auth-next/yahoo-oauth/auth_yahoo_provider_create.js b/snippets/auth-next/yahoo-oauth/auth_yahoo_provider_create.js new file mode 100644 index 00000000..c26cd5fd --- /dev/null +++ b/snippets/auth-next/yahoo-oauth/auth_yahoo_provider_create.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/yahoo-oauth.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_yahoo_provider_create_modular] +import { OAuthProvider } from "firebase/auth"; + +const provider = new OAuthProvider('yahoo.com'); +// [END auth_yahoo_provider_create_modular] \ No newline at end of file diff --git a/snippets/auth-next/yahoo-oauth/auth_yahoo_provider_params.js b/snippets/auth-next/yahoo-oauth/auth_yahoo_provider_params.js new file mode 100644 index 00000000..f8d918f9 --- /dev/null +++ b/snippets/auth-next/yahoo-oauth/auth_yahoo_provider_params.js @@ -0,0 +1,14 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/yahoo-oauth.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_yahoo_provider_params_modular] +provider.setCustomParameters({ + // Prompt user to re-authenticate to Yahoo. + prompt: 'login', + // Localize to French. + language: 'fr' +}); +// [END auth_yahoo_provider_params_modular] \ No newline at end of file diff --git a/snippets/auth-next/yahoo-oauth/auth_yahoo_provider_scopes.js b/snippets/auth-next/yahoo-oauth/auth_yahoo_provider_scopes.js new file mode 100644 index 00000000..8a02ff7b --- /dev/null +++ b/snippets/auth-next/yahoo-oauth/auth_yahoo_provider_scopes.js @@ -0,0 +1,13 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/yahoo-oauth.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_yahoo_provider_scopes_modular] +// Request access to Yahoo Mail API. +provider.addScope('mail-r'); +// Request read/write access to user contacts. +// This must be preconfigured in the app's API permissions. +provider.addScope('sdct-w'); +// [END auth_yahoo_provider_scopes_modular] \ No newline at end of file diff --git a/snippets/auth-next/yahoo-oauth/auth_yahoo_reauth_popup.js b/snippets/auth-next/yahoo-oauth/auth_yahoo_reauth_popup.js new file mode 100644 index 00000000..57243f05 --- /dev/null +++ b/snippets/auth-next/yahoo-oauth/auth_yahoo_reauth_popup.js @@ -0,0 +1,27 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/yahoo-oauth.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_yahoo_reauth_popup_modular] +import { getAuth, reauthenticateWithPopup, OAuthProvider } from "firebase/auth"; + +const provider = new OAuthProvider('yahoo.com'); +const auth = getAuth(); +reauthenticateWithPopup(auth.currentUser, provider) + .then((result) => { + // User is re-authenticated with fresh tokens minted and + // should be able to perform sensitive operations like account + // deletion and email or password update. + // IdP data available in result.additionalUserInfo.profile. + + // Get the OAuth access token and ID Token + const credential = OAuthProvider.credentialFromResult(result); + const accessToken = credential.accessToken; + const idToken = credential.idToken; + }) + .catch((error) => { + // Handle error. + }); +// [END auth_yahoo_reauth_popup_modular] \ No newline at end of file diff --git a/snippets/auth-next/yahoo-oauth/auth_yahoo_signin_popup.js b/snippets/auth-next/yahoo-oauth/auth_yahoo_signin_popup.js new file mode 100644 index 00000000..91c04abc --- /dev/null +++ b/snippets/auth-next/yahoo-oauth/auth_yahoo_signin_popup.js @@ -0,0 +1,24 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/yahoo-oauth.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_yahoo_signin_popup_modular] +import { getAuth, signInWithPopup, OAuthProvider } from "firebase/auth"; + +const auth = getAuth(); +signInWithPopup(auth, provider) + .then((result) => { + // IdP data available in result.additionalUserInfo.profile + // ... + + // Yahoo OAuth access token and ID token can be retrieved by calling: + const credential = OAuthProvider.credentialFromResult(result); + const accessToken = credential.accessToken; + const idToken = credential.idToken; + }) + .catch((error) => { + // Handle error. + }); +// [END auth_yahoo_signin_popup_modular] \ No newline at end of file diff --git a/snippets/auth-next/yahoo-oauth/auth_yahoo_signin_redirect.js b/snippets/auth-next/yahoo-oauth/auth_yahoo_signin_redirect.js new file mode 100644 index 00000000..f16cac73 --- /dev/null +++ b/snippets/auth-next/yahoo-oauth/auth_yahoo_signin_redirect.js @@ -0,0 +1,12 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/yahoo-oauth.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_yahoo_signin_redirect_modular] +import { getAuth, signInWithRedirect } from "firebase/auth"; + +const auth = getAuth(); +signInWithRedirect(auth, provider); +// [END auth_yahoo_signin_redirect_modular] \ No newline at end of file diff --git a/snippets/auth-next/yahoo-oauth/auth_yahoo_signin_redirect_result.js b/snippets/auth-next/yahoo-oauth/auth_yahoo_signin_redirect_result.js new file mode 100644 index 00000000..61709b2c --- /dev/null +++ b/snippets/auth-next/yahoo-oauth/auth_yahoo_signin_redirect_result.js @@ -0,0 +1,24 @@ +// This snippet file was generated by processing the source file: +// ./auth-next/yahoo-oauth.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START auth_yahoo_signin_redirect_result_modular] +import { getAuth, getRedirectResult, OAuthProvider } from "firebase/auth"; + +const auth = getAuth(); +getRedirectResult(auth) + .then((result) => { + // IdP data available in result.additionalUserInfo.profile + // ... + + // Yahoo OAuth access token and ID token can be retrieved by calling: + const credential = OAuthProvider.credentialFromResult(result); + const accessToken = credential.accessToken; + const idToken = credential.idToken; + }) + .catch((error) => { + // Handle error. + }); +// [END auth_yahoo_signin_redirect_result_modular] \ No newline at end of file diff --git a/snippets/database-next/emulator-suite/rtdb_emulator_connect.js b/snippets/database-next/emulator-suite/rtdb_emulator_connect.js new file mode 100644 index 00000000..0bd145f6 --- /dev/null +++ b/snippets/database-next/emulator-suite/rtdb_emulator_connect.js @@ -0,0 +1,15 @@ +// This snippet file was generated by processing the source file: +// ./database-next/emulator-suite.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START rtdb_emulator_connect_modular] +import { getDatabase, connectDatabaseEmulator } from "firebase/database"; + +const db = getDatabase(); +if (location.hostname === "localhost") { + // Point to the RTDB emulator running on localhost. + connectDatabaseEmulator(db, "127.0.0.1", 9000); +} +// [END rtdb_emulator_connect_modular] \ No newline at end of file diff --git a/snippets/database-next/emulator-suite/rtdb_emulator_flush.js b/snippets/database-next/emulator-suite/rtdb_emulator_flush.js new file mode 100644 index 00000000..3e17ef9a --- /dev/null +++ b/snippets/database-next/emulator-suite/rtdb_emulator_flush.js @@ -0,0 +1,13 @@ +// This snippet file was generated by processing the source file: +// ./database-next/emulator-suite.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START rtdb_emulator_flush_modular] +import { getDatabase, ref, set } from "firebase/database"; + +// With a database Reference, write null to clear the database. +const db = getDatabase(); +set(ref(db), null); +// [END rtdb_emulator_flush_modular] \ No newline at end of file diff --git a/snippets/database-next/index/rtdb_get_reference.js b/snippets/database-next/index/rtdb_get_reference.js new file mode 100644 index 00000000..ee87937e --- /dev/null +++ b/snippets/database-next/index/rtdb_get_reference.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./database-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START rtdb_get_reference_modular] +import { getDatabase } from "firebase/database"; + +const database = getDatabase(); +// [END rtdb_get_reference_modular] \ No newline at end of file diff --git a/snippets/database-next/lists-of-data/rtdb_social_listen_children.js b/snippets/database-next/lists-of-data/rtdb_social_listen_children.js new file mode 100644 index 00000000..78f90ff2 --- /dev/null +++ b/snippets/database-next/lists-of-data/rtdb_social_listen_children.js @@ -0,0 +1,23 @@ +// This snippet file was generated by processing the source file: +// ./database-next/lists-of-data.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START rtdb_social_listen_children_modular] +import { getDatabase, ref, onChildAdded, onChildChanged, onChildRemoved } from "firebase/database"; + +const db = getDatabase(); +const commentsRef = ref(db, 'post-comments/' + postId); +onChildAdded(commentsRef, (data) => { + addCommentElement(postElement, data.key, data.val().text, data.val().author); +}); + +onChildChanged(commentsRef, (data) => { + setCommentValues(postElement, data.key, data.val().text, data.val().author); +}); + +onChildRemoved(commentsRef, (data) => { + deleteComment(postElement, data.key); +}); +// [END rtdb_social_listen_children_modular] \ No newline at end of file diff --git a/snippets/database-next/lists-of-data/rtdb_social_listen_value.js b/snippets/database-next/lists-of-data/rtdb_social_listen_value.js new file mode 100644 index 00000000..d234eeea --- /dev/null +++ b/snippets/database-next/lists-of-data/rtdb_social_listen_value.js @@ -0,0 +1,22 @@ +// This snippet file was generated by processing the source file: +// ./database-next/lists-of-data.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START rtdb_social_listen_value_modular] +import { getDatabase, ref, onValue } from "firebase/database"; + +const db = getDatabase(); +const dbRef = ref(db, '/a/b/c'); + +onValue(dbRef, (snapshot) => { + snapshot.forEach((childSnapshot) => { + const childKey = childSnapshot.key; + const childData = childSnapshot.val(); + // ... + }); +}, { + onlyOnce: true +}); +// [END rtdb_social_listen_value_modular] \ No newline at end of file diff --git a/snippets/database-next/lists-of-data/rtdb_social_most_starred.js b/snippets/database-next/lists-of-data/rtdb_social_most_starred.js new file mode 100644 index 00000000..63237fdf --- /dev/null +++ b/snippets/database-next/lists-of-data/rtdb_social_most_starred.js @@ -0,0 +1,16 @@ +// This snippet file was generated by processing the source file: +// ./database-next/lists-of-data.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START rtdb_social_most_starred_modular] +import { getDatabase, ref, query, orderByChild } from "firebase/database"; +import { getAuth } from "firebase/auth"; + +const db = getDatabase(); +const auth = getAuth(); + +const myUserId = auth.currentUser.uid; +const topUserPostsRef = query(ref(db, 'user-posts/' + myUserId), orderByChild('starCount')); +// [END rtdb_social_most_starred_modular] \ No newline at end of file diff --git a/snippets/database-next/lists-of-data/rtdb_social_most_viewed.js b/snippets/database-next/lists-of-data/rtdb_social_most_viewed.js new file mode 100644 index 00000000..1ebe7edc --- /dev/null +++ b/snippets/database-next/lists-of-data/rtdb_social_most_viewed.js @@ -0,0 +1,12 @@ +// This snippet file was generated by processing the source file: +// ./database-next/lists-of-data.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START rtdb_social_most_viewed_modular] +import { getDatabase, ref, query, orderByChild } from "firebase/database"; + +const db = getDatabase(); +const mostViewedPosts = query(ref(db, 'posts'), orderByChild('metrics/views')); +// [END rtdb_social_most_viewed_modular] \ No newline at end of file diff --git a/snippets/database-next/lists-of-data/rtdb_social_push.js b/snippets/database-next/lists-of-data/rtdb_social_push.js new file mode 100644 index 00000000..ec366352 --- /dev/null +++ b/snippets/database-next/lists-of-data/rtdb_social_push.js @@ -0,0 +1,17 @@ +// This snippet file was generated by processing the source file: +// ./database-next/lists-of-data.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START rtdb_social_push_modular] +import { getDatabase, ref, push, set } from "firebase/database"; + +// Create a new post reference with an auto-generated id +const db = getDatabase(); +const postListRef = ref(db, 'posts'); +const newPostRef = push(postListRef); +set(newPostRef, { + // ... +}); +// [END rtdb_social_push_modular] \ No newline at end of file diff --git a/snippets/database-next/lists-of-data/rtdb_social_recent.js b/snippets/database-next/lists-of-data/rtdb_social_recent.js new file mode 100644 index 00000000..8b35d6e6 --- /dev/null +++ b/snippets/database-next/lists-of-data/rtdb_social_recent.js @@ -0,0 +1,12 @@ +// This snippet file was generated by processing the source file: +// ./database-next/lists-of-data.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START rtdb_social_recent_modular] +import { getDatabase, ref, query, limitToLast } from "firebase/database"; + +const db = getDatabase(); +const recentPostsRef = query(ref(db, 'posts'), limitToLast(100)); +// [END rtdb_social_recent_modular] \ No newline at end of file diff --git a/snippets/database-next/offline/rtdb_detect_connection_state.js b/snippets/database-next/offline/rtdb_detect_connection_state.js new file mode 100644 index 00000000..313af6ee --- /dev/null +++ b/snippets/database-next/offline/rtdb_detect_connection_state.js @@ -0,0 +1,19 @@ +// This snippet file was generated by processing the source file: +// ./database-next/offline.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START rtdb_detect_connection_state_modular] +import { getDatabase, ref, onValue } from "firebase/database"; + +const db = getDatabase(); +const connectedRef = ref(db, ".info/connected"); +onValue(connectedRef, (snap) => { + if (snap.val() === true) { + console.log("connected"); + } else { + console.log("not connected"); + } +}); +// [END rtdb_detect_connection_state_modular] \ No newline at end of file diff --git a/snippets/database-next/offline/rtdb_estimate_clock_skew.js b/snippets/database-next/offline/rtdb_estimate_clock_skew.js new file mode 100644 index 00000000..e1a92581 --- /dev/null +++ b/snippets/database-next/offline/rtdb_estimate_clock_skew.js @@ -0,0 +1,16 @@ +// This snippet file was generated by processing the source file: +// ./database-next/offline.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START rtdb_estimate_clock_skew_modular] +import { getDatabase, ref, onValue } from "firebase/database"; + +const db = getDatabase(); +const offsetRef = ref(db, ".info/serverTimeOffset"); +onValue(offsetRef, (snap) => { + const offset = snap.val(); + const estimatedServerTimeMs = new Date().getTime() + offset; +}); +// [END rtdb_estimate_clock_skew_modular] \ No newline at end of file diff --git a/snippets/database-next/offline/rtdb_ondisconnect_callback.js b/snippets/database-next/offline/rtdb_ondisconnect_callback.js new file mode 100644 index 00000000..16071828 --- /dev/null +++ b/snippets/database-next/offline/rtdb_ondisconnect_callback.js @@ -0,0 +1,13 @@ +// This snippet file was generated by processing the source file: +// ./database-next/offline.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START rtdb_ondisconnect_callback_modular] +onDisconnect(presenceRef).remove().catch((err) => { + if (err) { + console.error("could not establish onDisconnect event", err); + } +}); +// [END rtdb_ondisconnect_callback_modular] \ No newline at end of file diff --git a/snippets/database-next/offline/rtdb_ondisconnect_cancel.js b/snippets/database-next/offline/rtdb_ondisconnect_cancel.js new file mode 100644 index 00000000..f18cab8e --- /dev/null +++ b/snippets/database-next/offline/rtdb_ondisconnect_cancel.js @@ -0,0 +1,12 @@ +// This snippet file was generated by processing the source file: +// ./database-next/offline.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START rtdb_ondisconnect_cancel_modular] +const onDisconnectRef = onDisconnect(presenceRef); +onDisconnectRef.set("I disconnected"); +// some time later when we change our minds +onDisconnectRef.cancel(); +// [END rtdb_ondisconnect_cancel_modular] \ No newline at end of file diff --git a/snippets/database-next/offline/rtdb_ondisconnect_simple.js b/snippets/database-next/offline/rtdb_ondisconnect_simple.js new file mode 100644 index 00000000..340e3fe5 --- /dev/null +++ b/snippets/database-next/offline/rtdb_ondisconnect_simple.js @@ -0,0 +1,14 @@ +// This snippet file was generated by processing the source file: +// ./database-next/offline.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START rtdb_ondisconnect_simple_modular] +import { getDatabase, ref, onDisconnect } from "firebase/database"; + +const db = getDatabase(); +const presenceRef = ref(db, "disconnectmessage"); +// Write a string when this client loses connection +onDisconnect(presenceRef).set("I disconnected!"); +// [END rtdb_ondisconnect_simple_modular] \ No newline at end of file diff --git a/snippets/database-next/offline/rtdb_sample_presence_app.js b/snippets/database-next/offline/rtdb_sample_presence_app.js new file mode 100644 index 00000000..394e64e6 --- /dev/null +++ b/snippets/database-next/offline/rtdb_sample_presence_app.js @@ -0,0 +1,35 @@ +// This snippet file was generated by processing the source file: +// ./database-next/offline.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START rtdb_sample_presence_app_modular] +import { getDatabase, ref, onValue, push, onDisconnect, set, serverTimestamp } from "firebase/database"; + +// Since I can connect from multiple devices or browser tabs, we store each connection instance separately +// any time that connectionsRef's value is null (i.e. has no children) I am offline +const db = getDatabase(); +const myConnectionsRef = ref(db, 'users/joe/connections'); + +// stores the timestamp of my last disconnect (the last time I was seen online) +const lastOnlineRef = ref(db, 'users/joe/lastOnline'); + +const connectedRef = ref(db, '.info/connected'); +onValue(connectedRef, (snap) => { + if (snap.val() === true) { + // We're connected (or reconnected)! Do anything here that should happen only if online (or on reconnect) + const con = push(myConnectionsRef); + + // When I disconnect, remove this device + onDisconnect(con).remove(); + + // Add this device to my connections list + // this value could contain info about the device or a timestamp too + set(con, true); + + // When I disconnect, update the last time I was seen online + onDisconnect(lastOnlineRef).set(serverTimestamp()); + } +}); +// [END rtdb_sample_presence_app_modular] \ No newline at end of file diff --git a/snippets/database-next/offline/rtdb_set_server_timestamp.js b/snippets/database-next/offline/rtdb_set_server_timestamp.js new file mode 100644 index 00000000..8a9d2e8a --- /dev/null +++ b/snippets/database-next/offline/rtdb_set_server_timestamp.js @@ -0,0 +1,13 @@ +// This snippet file was generated by processing the source file: +// ./database-next/offline.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START rtdb_set_server_timestamp_modular] +import { getDatabase, ref, onDisconnect, serverTimestamp } from "firebase/database"; + +const db = getDatabase(); +const userLastOnlineRef = ref(db, "users/joe/lastOnline"); +onDisconnect(userLastOnlineRef).set(serverTimestamp()); +// [END rtdb_set_server_timestamp_modular] \ No newline at end of file diff --git a/snippets/database-next/read-and-write/rtdb_read_once_get.js b/snippets/database-next/read-and-write/rtdb_read_once_get.js new file mode 100644 index 00000000..a0cf77a5 --- /dev/null +++ b/snippets/database-next/read-and-write/rtdb_read_once_get.js @@ -0,0 +1,20 @@ +// This snippet file was generated by processing the source file: +// ./database-next/read-and-write.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START rtdb_read_once_get_modular] +import { getDatabase, ref, child, get } from "firebase/database"; + +const dbRef = ref(getDatabase()); +get(child(dbRef, `users/${userId}`)).then((snapshot) => { + if (snapshot.exists()) { + console.log(snapshot.val()); + } else { + console.log("No data available"); + } +}).catch((error) => { + console.error(error); +}); +// [END rtdb_read_once_get_modular] \ No newline at end of file diff --git a/snippets/database-next/read-and-write/rtdb_social_completion_callback.js b/snippets/database-next/read-and-write/rtdb_social_completion_callback.js new file mode 100644 index 00000000..b1ab42a1 --- /dev/null +++ b/snippets/database-next/read-and-write/rtdb_social_completion_callback.js @@ -0,0 +1,22 @@ +// This snippet file was generated by processing the source file: +// ./database-next/read-and-write.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START rtdb_social_completion_callback_modular] +import { getDatabase, ref, set } from "firebase/database"; + +const db = getDatabase(); +set(ref(db, 'users/' + userId), { + username: name, + email: email, + profile_picture : imageUrl +}) +.then(() => { + // Data saved successfully! +}) +.catch((error) => { + // The write failed... +}); +// [END rtdb_social_completion_callback_modular] \ No newline at end of file diff --git a/snippets/database-next/read-and-write/rtdb_social_listen_star_count.js b/snippets/database-next/read-and-write/rtdb_social_listen_star_count.js new file mode 100644 index 00000000..eb782894 --- /dev/null +++ b/snippets/database-next/read-and-write/rtdb_social_listen_star_count.js @@ -0,0 +1,16 @@ +// This snippet file was generated by processing the source file: +// ./database-next/read-and-write.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START rtdb_social_listen_star_count_modular] +import { getDatabase, ref, onValue } from "firebase/database"; + +const db = getDatabase(); +const starCountRef = ref(db, 'posts/' + postId + '/starCount'); +onValue(starCountRef, (snapshot) => { + const data = snapshot.val(); + updateStarCount(postElement, data); +}); +// [END rtdb_social_listen_star_count_modular] \ No newline at end of file diff --git a/snippets/database-next/read-and-write/rtdb_social_single_value_read.js b/snippets/database-next/read-and-write/rtdb_social_single_value_read.js new file mode 100644 index 00000000..3debb5e5 --- /dev/null +++ b/snippets/database-next/read-and-write/rtdb_social_single_value_read.js @@ -0,0 +1,21 @@ +// This snippet file was generated by processing the source file: +// ./database-next/read-and-write.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START rtdb_social_single_value_read_modular] +import { getDatabase, ref, onValue } from "firebase/database"; +import { getAuth } from "firebase/auth"; + +const db = getDatabase(); +const auth = getAuth(); + +const userId = auth.currentUser.uid; +return onValue(ref(db, '/users/' + userId), (snapshot) => { + const username = (snapshot.val() && snapshot.val().username) || 'Anonymous'; + // ... +}, { + onlyOnce: true +}); +// [END rtdb_social_single_value_read_modular] \ No newline at end of file diff --git a/snippets/database-next/read-and-write/rtdb_social_star_increment.js b/snippets/database-next/read-and-write/rtdb_social_star_increment.js new file mode 100644 index 00000000..dce5e8a9 --- /dev/null +++ b/snippets/database-next/read-and-write/rtdb_social_star_increment.js @@ -0,0 +1,19 @@ +// This snippet file was generated by processing the source file: +// ./database-next/read-and-write.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START rtdb_social_star_increment_modular] +function addStar(uid, key) { + import { getDatabase, increment, ref, update } from "firebase/database"; + const dbRef = ref(getDatabase()); + + const updates = {}; + updates[`posts/${key}/stars/${uid}`] = true; + updates[`posts/${key}/starCount`] = increment(1); + updates[`user-posts/${key}/stars/${uid}`] = true; + updates[`user-posts/${key}/starCount`] = increment(1); + update(dbRef, updates); +} +// [END rtdb_social_star_increment_modular] \ No newline at end of file diff --git a/snippets/database-next/read-and-write/rtdb_social_star_transaction.js b/snippets/database-next/read-and-write/rtdb_social_star_transaction.js new file mode 100644 index 00000000..bf837c92 --- /dev/null +++ b/snippets/database-next/read-and-write/rtdb_social_star_transaction.js @@ -0,0 +1,30 @@ +// This snippet file was generated by processing the source file: +// ./database-next/read-and-write.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START rtdb_social_star_transaction_modular] +import { getDatabase, ref, runTransaction } from "firebase/database"; + +function toggleStar(uid) { + const db = getDatabase(); + const postRef = ref(db, '/posts/foo-bar-123'); + + runTransaction(postRef, (post) => { + if (post) { + if (post.stars && post.stars[uid]) { + post.starCount--; + post.stars[uid] = null; + } else { + post.starCount++; + if (!post.stars) { + post.stars = {}; + } + post.stars[uid] = true; + } + } + return post; + }); +} +// [END rtdb_social_star_transaction_modular] \ No newline at end of file diff --git a/snippets/database-next/read-and-write/rtdb_social_write_fan_out.js b/snippets/database-next/read-and-write/rtdb_social_write_fan_out.js new file mode 100644 index 00000000..783eca2b --- /dev/null +++ b/snippets/database-next/read-and-write/rtdb_social_write_fan_out.js @@ -0,0 +1,33 @@ +// This snippet file was generated by processing the source file: +// ./database-next/read-and-write.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START rtdb_social_write_fan_out_modular] +import { getDatabase, ref, child, push, update } from "firebase/database"; + +function writeNewPost(uid, username, picture, title, body) { + const db = getDatabase(); + + // A post entry. + const postData = { + author: username, + uid: uid, + body: body, + title: title, + starCount: 0, + authorPic: picture + }; + + // Get a key for a new Post. + const newPostKey = push(child(ref(db), 'posts')).key; + + // Write the new post's data simultaneously in the posts list and the user's post list. + const updates = {}; + updates['/posts/' + newPostKey] = postData; + updates['/user-posts/' + uid + '/' + newPostKey] = postData; + + return update(ref(db), updates); +} +// [END rtdb_social_write_fan_out_modular] \ No newline at end of file diff --git a/snippets/database-next/read-and-write/rtdb_write_new_user.js b/snippets/database-next/read-and-write/rtdb_write_new_user.js new file mode 100644 index 00000000..eef24086 --- /dev/null +++ b/snippets/database-next/read-and-write/rtdb_write_new_user.js @@ -0,0 +1,18 @@ +// This snippet file was generated by processing the source file: +// ./database-next/read-and-write.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START rtdb_write_new_user_modular] +import { getDatabase, ref, set } from "firebase/database"; + +function writeUserData(userId, name, email, imageUrl) { + const db = getDatabase(); + set(ref(db, 'users/' + userId), { + username: name, + email: email, + profile_picture : imageUrl + }); +} +// [END rtdb_write_new_user_modular] \ No newline at end of file diff --git a/snippets/database-next/read-and-write/rtdb_write_new_user_completion.js b/snippets/database-next/read-and-write/rtdb_write_new_user_completion.js new file mode 100644 index 00000000..8bb5878e --- /dev/null +++ b/snippets/database-next/read-and-write/rtdb_write_new_user_completion.js @@ -0,0 +1,22 @@ +// This snippet file was generated by processing the source file: +// ./database-next/read-and-write.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START rtdb_write_new_user_completion_modular] +import { getDatabase, ref, set } from "firebase/database"; + +const db = getDatabase(); +set(ref(db, 'users/' + userId), { + username: name, + email: email, + profile_picture : imageUrl +}) +.then(() => { + // Data saved successfully! +}) +.catch((error) => { + // The write failed... +}); +// [END rtdb_write_new_user_completion_modular] \ No newline at end of file diff --git a/snippets/database-next/sharding/rtdb_multiple_instances.js b/snippets/database-next/sharding/rtdb_multiple_instances.js new file mode 100644 index 00000000..fe66cbc8 --- /dev/null +++ b/snippets/database-next/sharding/rtdb_multiple_instances.js @@ -0,0 +1,24 @@ +// This snippet file was generated by processing the source file: +// ./database-next/sharding.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START rtdb_multiple_instances_modular] +import { initializeApp } from "firebase/app"; +import { getDatabase } from "firebase/database"; + +const app1 = initializeApp({ + databaseURL: "https://testapp-1234-1.firebaseio.com" +}); + +const app2 = initializeApp({ + databaseURL: "https://testapp-1234-2.firebaseio.com" +}, 'app2'); + +// Get the default database instance for an app1 +const database1 = getDatabase(app1); + +// Get a database instance for app2 +const database2 = getDatabase(app2); +// [END rtdb_multiple_instances_modular] \ No newline at end of file diff --git a/snippets/firebaseapp-next/firebaseapp/app_default_init_options.js b/snippets/firebaseapp-next/firebaseapp/app_default_init_options.js new file mode 100644 index 00000000..7cebaaf3 --- /dev/null +++ b/snippets/firebaseapp-next/firebaseapp/app_default_init_options.js @@ -0,0 +1,24 @@ +// This snippet file was generated by processing the source file: +// ./firebaseapp-next/firebaseapp.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START app_default_init_options_modular] +import { initializeApp } from "firebase/app"; +import { getStorage } from "firebase/storage"; +import { getFirestore } from "firebase/firestore"; + +// Initialize Firebase with a "default" Firebase project +const defaultProject = initializeApp(firebaseConfig); + +console.log(defaultProject.name); // "[DEFAULT]" + +// Option 1: Access Firebase services via the defaultProject variable +let defaultStorage = getStorage(defaultProject); +let defaultFirestore = getFirestore(defaultProject); + +// Option 2: Access Firebase services using shorthand notation +defaultStorage = getStorage(); +defaultFirestore = getFirestore(); +// [END app_default_init_options_modular] \ No newline at end of file diff --git a/snippets/firebaseapp-next/firebaseapp/app_multi_project_init_options.js b/snippets/firebaseapp-next/firebaseapp/app_multi_project_init_options.js new file mode 100644 index 00000000..415f8813 --- /dev/null +++ b/snippets/firebaseapp-next/firebaseapp/app_multi_project_init_options.js @@ -0,0 +1,28 @@ +// This snippet file was generated by processing the source file: +// ./firebaseapp-next/firebaseapp.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START app_multi_project_init_options_modular] +import { initializeApp, getApp } from "firebase/app"; +import { getStorage } from "firebase/storage"; +import { getFirestore } from "firebase/firestore"; + +// Initialize Firebase with a default Firebase project +initializeApp(firebaseConfig); + +// Initialize Firebase with a second Firebase project +const otherProject = initializeApp(otherProjectFirebaseConfig, "other"); + +console.log(getApp().name); // "[DEFAULT]" +console.log(otherProject.name); // "otherProject" + +// Use the shorthand notation to access the default project's Firebase services +const defaultStorage = getStorage(); +const defaultFirestore = getFirestore(); + +// Use the otherProject variable to access the second project's Firebase services +const otherStorage = getStorage(otherProject); +const otherFirestore = getFirestore(otherProject); +// [END app_multi_project_init_options_modular] \ No newline at end of file diff --git a/snippets/firebaseapp-next/firebaseapp/firebase_options.js b/snippets/firebaseapp-next/firebaseapp/firebase_options.js new file mode 100644 index 00000000..f951d4d7 --- /dev/null +++ b/snippets/firebaseapp-next/firebaseapp/firebase_options.js @@ -0,0 +1,21 @@ +// This snippet file was generated by processing the source file: +// ./firebaseapp-next/firebaseapp.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START firebase_options_modular] +import { initializeApp } from "firebase/app"; + +// The following fields are REQUIRED: +// - Project ID +// - App ID +// - API Key +const secondaryAppConfig = { + projectId: "", + appId: "", + apiKey: "", + // databaseURL: "...", + // storageBucket: "...", +}; +// [END firebase_options_modular] \ No newline at end of file diff --git a/snippets/firebaseapp-next/firebaseapp/firebase_secondary.js b/snippets/firebaseapp-next/firebaseapp/firebase_secondary.js new file mode 100644 index 00000000..2dd04f0f --- /dev/null +++ b/snippets/firebaseapp-next/firebaseapp/firebase_secondary.js @@ -0,0 +1,12 @@ +// This snippet file was generated by processing the source file: +// ./firebaseapp-next/firebaseapp.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START firebase_secondary_modular] +// Initialize another app with a different config +const secondaryApp = initializeApp(secondaryAppConfig, "secondary"); +// Access services, such as the Realtime Database +// getDatabase(secondaryApp) +// [END firebase_secondary_modular] \ No newline at end of file diff --git a/snippets/firestore-next/emulator-suite/fs_emulator_connect.js b/snippets/firestore-next/emulator-suite/fs_emulator_connect.js new file mode 100644 index 00000000..d70fbb68 --- /dev/null +++ b/snippets/firestore-next/emulator-suite/fs_emulator_connect.js @@ -0,0 +1,13 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/emulator-suite.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START fs_emulator_connect_modular] +import { getFirestore, connectFirestoreEmulator } from "firebase/firestore"; + +// firebaseApps previously initialized using initializeApp() +const db = getFirestore(); +connectFirestoreEmulator(db, '127.0.0.1', 8080); +// [END fs_emulator_connect_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/add_ada_lovelace.js b/snippets/firestore-next/test-firestore/add_ada_lovelace.js new file mode 100644 index 00000000..ae9e168a --- /dev/null +++ b/snippets/firestore-next/test-firestore/add_ada_lovelace.js @@ -0,0 +1,20 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START add_ada_lovelace_modular] +import { collection, addDoc } from "firebase/firestore"; + +try { + const docRef = await addDoc(collection(db, "users"), { + first: "Ada", + last: "Lovelace", + born: 1815 + }); + console.log("Document written with ID: ", docRef.id); +} catch (e) { + console.error("Error adding document: ", e); +} +// [END add_ada_lovelace_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/add_alan_turing.js b/snippets/firestore-next/test-firestore/add_alan_turing.js new file mode 100644 index 00000000..0c7abaaf --- /dev/null +++ b/snippets/firestore-next/test-firestore/add_alan_turing.js @@ -0,0 +1,23 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START add_alan_turing_modular] +// Add a second document with a generated ID. +import { addDoc, collection } from "firebase/firestore"; + +try { + const docRef = await addDoc(collection(db, "users"), { + first: "Alan", + middle: "Mathison", + last: "Turing", + born: 1912 + }); + + console.log("Document written with ID: ", docRef.id); +} catch (e) { + console.error("Error adding document: ", e); +} +// [END add_alan_turing_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/add_document.js b/snippets/firestore-next/test-firestore/add_document.js new file mode 100644 index 00000000..ddfea007 --- /dev/null +++ b/snippets/firestore-next/test-firestore/add_document.js @@ -0,0 +1,16 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START add_document_modular] +import { collection, addDoc } from "firebase/firestore"; + +// Add a new document with a generated id. +const docRef = await addDoc(collection(db, "cities"), { + name: "Tokyo", + country: "Japan" +}); +console.log("Document written with ID: ", docRef.id); +// [END add_document_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/add_rating_transaction.js b/snippets/firestore-next/test-firestore/add_rating_transaction.js new file mode 100644 index 00000000..7d698806 --- /dev/null +++ b/snippets/firestore-next/test-firestore/add_rating_transaction.js @@ -0,0 +1,36 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START add_rating_transaction_modular] +import { collection, doc, runTransaction } from "firebase/firestore"; + +async function addRating(restaurantRef, rating) { + // Create a reference for a new rating, for use inside the transaction + const ratingRef = doc(collection(restaurantRef, 'ratings')); + + // In a transaction, add the new rating and update the aggregate totals + await runTransaction(db, async (transaction) => { + const res = await transaction.get(restaurantRef); + if (!res.exists()) { + throw "Document does not exist!"; + } + + // Compute new number of ratings + const newNumRatings = res.data().numRatings + 1; + + // Compute new average rating + const oldRatingTotal = res.data().avgRating * res.data().numRatings; + const newAvgRating = (oldRatingTotal + rating) / newNumRatings; + + // Commit to Firestore + transaction.update(restaurantRef, { + numRatings: newNumRatings, + avgRating: newAvgRating + }); + transaction.set(ratingRef, { rating: rating }); + }); +} +// [END add_rating_transaction_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/array_contains_any_filter.js b/snippets/firestore-next/test-firestore/array_contains_any_filter.js new file mode 100644 index 00000000..3ee6e4d9 --- /dev/null +++ b/snippets/firestore-next/test-firestore/array_contains_any_filter.js @@ -0,0 +1,12 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START array_contains_any_filter_modular] +import { query, where } from "firebase/firestore"; + +const q = query(citiesRef, + where('regions', 'array-contains-any', ['west_coast', 'east_coast'])); +// [END array_contains_any_filter_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/array_contains_filter.js b/snippets/firestore-next/test-firestore/array_contains_filter.js new file mode 100644 index 00000000..4576a193 --- /dev/null +++ b/snippets/firestore-next/test-firestore/array_contains_filter.js @@ -0,0 +1,10 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START array_contains_filter_modular] +import { query, where } from "firebase/firestore"; +const q = query(citiesRef, where("regions", "array-contains", "west_coast")); +// [END array_contains_filter_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/chain_filters.js b/snippets/firestore-next/test-firestore/chain_filters.js new file mode 100644 index 00000000..cbf3322f --- /dev/null +++ b/snippets/firestore-next/test-firestore/chain_filters.js @@ -0,0 +1,12 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START chain_filters_modular] +import { query, where } from "firebase/firestore"; + +const q1 = query(citiesRef, where("state", "==", "CO"), where("name", "==", "Denver")); +const q2 = query(citiesRef, where("state", "==", "CA"), where("population", "<", 1000000)); +// [END chain_filters_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/cities_document_set.js b/snippets/firestore-next/test-firestore/cities_document_set.js new file mode 100644 index 00000000..4b85b25d --- /dev/null +++ b/snippets/firestore-next/test-firestore/cities_document_set.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START cities_document_set_modular] +import { doc, setDoc } from "firebase/firestore"; + +await setDoc(doc(db, "cities", "new-city-id"), data); +// [END cities_document_set_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/city_custom_object.js b/snippets/firestore-next/test-firestore/city_custom_object.js new file mode 100644 index 00000000..c8e4b80c --- /dev/null +++ b/snippets/firestore-next/test-firestore/city_custom_object.js @@ -0,0 +1,33 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START city_custom_object_modular] +class City { + constructor (name, state, country ) { + this.name = name; + this.state = state; + this.country = country; + } + toString() { + return this.name + ', ' + this.state + ', ' + this.country; + } +} + +// Firestore data converter +const cityConverter = { + toFirestore: (city) => { + return { + name: city.name, + state: city.state, + country: city.country + }; + }, + fromFirestore: (snapshot, options) => { + const data = snapshot.data(options); + return new City(data.name, data.state, data.country); + } +}; +// [END city_custom_object_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/collection_reference.js b/snippets/firestore-next/test-firestore/collection_reference.js new file mode 100644 index 00000000..d69f17e4 --- /dev/null +++ b/snippets/firestore-next/test-firestore/collection_reference.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START collection_reference_modular] +import { collection } from "firebase/firestore"; + +const usersCollectionRef = collection(db, 'users'); +// [END collection_reference_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/count_aggregate_collection.js b/snippets/firestore-next/test-firestore/count_aggregate_collection.js new file mode 100644 index 00000000..7dca7e37 --- /dev/null +++ b/snippets/firestore-next/test-firestore/count_aggregate_collection.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START count_aggregate_collection_modular] +const coll = collection(db, "cities"); +const snapshot = await getCountFromServer(coll); +console.log('count: ', snapshot.data().count); +// [END count_aggregate_collection_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/count_aggregate_query.js b/snippets/firestore-next/test-firestore/count_aggregate_query.js new file mode 100644 index 00000000..37a27458 --- /dev/null +++ b/snippets/firestore-next/test-firestore/count_aggregate_query.js @@ -0,0 +1,12 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START count_aggregate_query_modular] +const coll = collection(db, "cities"); +const q = query(coll, where("state", "==", "CA")); +const snapshot = await getCountFromServer(q); +console.log('count: ', snapshot.data().count); +// [END count_aggregate_query_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/data_types.js b/snippets/firestore-next/test-firestore/data_types.js new file mode 100644 index 00000000..9218ae6b --- /dev/null +++ b/snippets/firestore-next/test-firestore/data_types.js @@ -0,0 +1,25 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START data_types_modular] +import { doc, setDoc, Timestamp } from "firebase/firestore"; + +const docData = { + stringExample: "Hello world!", + booleanExample: true, + numberExample: 3.14159265, + dateExample: Timestamp.fromDate(new Date("December 10, 1815")), + arrayExample: [5, true, "hello"], + nullExample: null, + objectExample: { + a: 5, + b: { + nested: "foo" + } + } +}; +await setDoc(doc(db, "data", "one"), docData); +// [END data_types_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/delete_collection.js b/snippets/firestore-next/test-firestore/delete_collection.js new file mode 100644 index 00000000..4adef6f8 --- /dev/null +++ b/snippets/firestore-next/test-firestore/delete_collection.js @@ -0,0 +1,49 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START delete_collection_modular] +/** + * Delete a collection, in batches of batchSize. Note that this does + * not recursively delete subcollections of documents in the collection + */ +import { collection, query, orderBy, limit, getDocs, writeBatch } from "firebase/firestore"; + +function deleteCollection(db, collectionRef, batchSize) { + const q = query(collectionRef, orderBy('__name__'), limit(batchSize)); + + return new Promise((resolve) => { + deleteQueryBatch(db, q, batchSize, resolve); + }); +} + +async function deleteQueryBatch(db, query, batchSize, resolve) { + const snapshot = await getDocs(query); + + // When there are no documents left, we are done + let numDeleted = 0; + if (snapshot.size > 0) { + // Delete documents in a batch + const batch = writeBatch(db); + snapshot.docs.forEach((doc) => { + batch.delete(doc.ref); + }); + + await batch.commit(); + numDeleted = snapshot.size; + } + + if (numDeleted < batchSize) { + resolve(); + return; + } + + // Recurse on the next process tick, to avoid + // exploding the stack. + setTimeout(() => { + deleteQueryBatch(db, query, batchSize, resolve); + }, 0); +} +// [END delete_collection_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/delete_document.js b/snippets/firestore-next/test-firestore/delete_document.js new file mode 100644 index 00000000..b00ae490 --- /dev/null +++ b/snippets/firestore-next/test-firestore/delete_document.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START delete_document_modular] +import { doc, deleteDoc } from "firebase/firestore"; + +await deleteDoc(doc(db, "cities", "DC")); +// [END delete_document_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/detach_listener.js b/snippets/firestore-next/test-firestore/detach_listener.js new file mode 100644 index 00000000..cd921018 --- /dev/null +++ b/snippets/firestore-next/test-firestore/detach_listener.js @@ -0,0 +1,19 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START detach_listener_modular] +import { collection, onSnapshot } from "firebase/firestore"; + +const unsubscribe = onSnapshot(collection(db, "cities"), () => { + // Respond to data + // ... +}); + +// Later ... + +// Stop listening to changes +unsubscribe(); +// [END detach_listener_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/disable_network.js b/snippets/firestore-next/test-firestore/disable_network.js new file mode 100644 index 00000000..efa1fc27 --- /dev/null +++ b/snippets/firestore-next/test-firestore/disable_network.js @@ -0,0 +1,16 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START disable_network_modular] +import { disableNetwork } from "firebase/firestore"; + +await disableNetwork(db); +console.log("Network disabled!"); +// Do offline actions +// [START_EXCLUDE] +console.log("Network disabled!"); +// [END_EXCLUDE] +// [END disable_network_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/doc_reference.js b/snippets/firestore-next/test-firestore/doc_reference.js new file mode 100644 index 00000000..c5380539 --- /dev/null +++ b/snippets/firestore-next/test-firestore/doc_reference.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START doc_reference_modular] +import { doc } from "firebase/firestore"; + +const alovelaceDocumentRef = doc(db, 'users', 'alovelace'); +// [END doc_reference_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/doc_reference_alternative.js b/snippets/firestore-next/test-firestore/doc_reference_alternative.js new file mode 100644 index 00000000..c88294a9 --- /dev/null +++ b/snippets/firestore-next/test-firestore/doc_reference_alternative.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START doc_reference_alternative_modular] +import { doc } from "firebase/firestore"; + +const alovelaceDocumentRef = doc(db, 'users/alovelace'); +// [END doc_reference_alternative_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/enable_network.js b/snippets/firestore-next/test-firestore/enable_network.js new file mode 100644 index 00000000..2df9171b --- /dev/null +++ b/snippets/firestore-next/test-firestore/enable_network.js @@ -0,0 +1,15 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START enable_network_modular] +import { enableNetwork } from "firebase/firestore"; + +await enableNetwork(db); +// Do online actions +// [START_EXCLUDE] +console.log("Network enabled!"); +// [END_EXCLUDE] +// [END enable_network_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/example_data.js b/snippets/firestore-next/test-firestore/example_data.js new file mode 100644 index 00000000..cb7e92aa --- /dev/null +++ b/snippets/firestore-next/test-firestore/example_data.js @@ -0,0 +1,32 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START example_data_modular] +import { collection, doc, setDoc } from "firebase/firestore"; + +const citiesRef = collection(db, "cities"); + +await setDoc(doc(citiesRef, "SF"), { + name: "San Francisco", state: "CA", country: "USA", + capital: false, population: 860000, + regions: ["west_coast", "norcal"] }); +await setDoc(doc(citiesRef, "LA"), { + name: "Los Angeles", state: "CA", country: "USA", + capital: false, population: 3900000, + regions: ["west_coast", "socal"] }); +await setDoc(doc(citiesRef, "DC"), { + name: "Washington, D.C.", state: null, country: "USA", + capital: true, population: 680000, + regions: ["east_coast"] }); +await setDoc(doc(citiesRef, "TOK"), { + name: "Tokyo", state: null, country: "Japan", + capital: true, population: 9000000, + regions: ["kanto", "honshu"] }); +await setDoc(doc(citiesRef, "BJ"), { + name: "Beijing", state: null, country: "China", + capital: true, population: 21500000, + regions: ["jingjinji", "hebei"] }); +// [END example_data_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/example_filters.js b/snippets/firestore-next/test-firestore/example_filters.js new file mode 100644 index 00000000..b481e5a8 --- /dev/null +++ b/snippets/firestore-next/test-firestore/example_filters.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START example_filters_modular] +const stateQuery = query(citiesRef, where("state", "==", "CA")); +const populationQuery = query(citiesRef, where("population", "<", 100000)); +const nameQuery = query(citiesRef, where("name", ">=", "San Francisco")); +// [END example_filters_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/filter_and_order.js b/snippets/firestore-next/test-firestore/filter_and_order.js new file mode 100644 index 00000000..b8c03705 --- /dev/null +++ b/snippets/firestore-next/test-firestore/filter_and_order.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START filter_and_order_modular] +import { query, where, orderBy, limit } from "firebase/firestore"; + +const q = query(citiesRef, where("population", ">", 100000), orderBy("population"), limit(2)); +// [END filter_and_order_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/firestore_query_subcollection.js b/snippets/firestore-next/test-firestore/firestore_query_subcollection.js new file mode 100644 index 00000000..76a1b847 --- /dev/null +++ b/snippets/firestore-next/test-firestore/firestore_query_subcollection.js @@ -0,0 +1,15 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START firestore_query_subcollection_modular] +import { collection, getDocs } from "firebase/firestore"; +// Query a reference to a subcollection +const querySnapshot = await getDocs(collection(db, "cities", "SF", "landmarks")); +querySnapshot.forEach((doc) => { + // doc.data() is never undefined for query doc snapshots + console.log(doc.id, " => ", doc.data()); +}); +// [END firestore_query_subcollection_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/fs_collection_group_query.js b/snippets/firestore-next/test-firestore/fs_collection_group_query.js new file mode 100644 index 00000000..3468017b --- /dev/null +++ b/snippets/firestore-next/test-firestore/fs_collection_group_query.js @@ -0,0 +1,15 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START fs_collection_group_query_modular] +import { collectionGroup, query, where, getDocs } from "firebase/firestore"; + +const museums = query(collectionGroup(db, 'landmarks'), where('type', '==', 'museum')); +const querySnapshot = await getDocs(museums); +querySnapshot.forEach((doc) => { + console.log(doc.id, ' => ', doc.data()); +}); +// [END fs_collection_group_query_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/fs_collection_group_query_data_setup.js b/snippets/firestore-next/test-firestore/fs_collection_group_query_data_setup.js new file mode 100644 index 00000000..f06ea0f8 --- /dev/null +++ b/snippets/firestore-next/test-firestore/fs_collection_group_query_data_setup.js @@ -0,0 +1,54 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START fs_collection_group_query_data_setup_modular] +import { collection, addDoc } from "firebase/firestore"; + +const citiesRef = collection(db, 'cities'); + +await Promise.all([ + addDoc(collection(citiesRef, 'SF', 'landmarks'), { + name: 'Golden Gate Bridge', + type: 'bridge' + }), + addDoc(collection(citiesRef, 'SF', 'landmarks'), { + name: 'Legion of Honor', + type: 'museum' + }), + addDoc(collection(citiesRef, 'LA', 'landmarks'), { + name: 'Griffith Park', + type: 'park' + }), + addDoc(collection(citiesRef, 'LA', 'landmarks'), { + name: 'The Getty', + type: 'museum' + }), + addDoc(collection(citiesRef, 'DC', 'landmarks'), { + name: 'Lincoln Memorial', + type: 'memorial' + }), + addDoc(collection(citiesRef, 'DC', 'landmarks'), { + name: 'National Air and Space Museum', + type: 'museum' + }), + addDoc(collection(citiesRef, 'TOK', 'landmarks'), { + name: 'Ueno Park', + type: 'park' + }), + addDoc(collection(citiesRef, 'TOK', 'landmarks'), { + name: 'National Museum of Nature and Science', + type: 'museum' + }), + addDoc(collection(citiesRef, 'BJ', 'landmarks'), { + name: 'Jingshan Park', + type: 'park' + }), + addDoc(collection(citiesRef, 'BJ', 'landmarks'), { + name: 'Beijing Ancient Observatory', + type: 'museum' + }) +]); +// [END fs_collection_group_query_data_setup_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/fs_setup_cache.js b/snippets/firestore-next/test-firestore/fs_setup_cache.js new file mode 100644 index 00000000..8429da4c --- /dev/null +++ b/snippets/firestore-next/test-firestore/fs_setup_cache.js @@ -0,0 +1,13 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START fs_setup_cache_modular] +import { initializeFirestore, CACHE_SIZE_UNLIMITED } from "firebase/firestore"; + +const firestoreDb = initializeFirestore(app, { + cacheSizeBytes: CACHE_SIZE_UNLIMITED +}); +// [END fs_setup_cache_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/get_all_users.js b/snippets/firestore-next/test-firestore/get_all_users.js new file mode 100644 index 00000000..59dd760a --- /dev/null +++ b/snippets/firestore-next/test-firestore/get_all_users.js @@ -0,0 +1,14 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START get_all_users_modular] +import { collection, getDocs } from "firebase/firestore"; + +const querySnapshot = await getDocs(collection(db, "users")); +querySnapshot.forEach((doc) => { + console.log(`${doc.id} => ${doc.data()}`); +}); +// [END get_all_users_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/get_custom_object.js b/snippets/firestore-next/test-firestore/get_custom_object.js new file mode 100644 index 00000000..e20a6839 --- /dev/null +++ b/snippets/firestore-next/test-firestore/get_custom_object.js @@ -0,0 +1,20 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START get_custom_object_modular] +import { doc, getDoc} from "firebase/firestore"; + +const ref = doc(db, "cities", "LA").withConverter(cityConverter); +const docSnap = await getDoc(ref); +if (docSnap.exists()) { + // Convert to City object + const city = docSnap.data(); + // Use a City instance method + console.log(city.toString()); +} else { + console.log("No such document!"); +} +// [END get_custom_object_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/get_document.js b/snippets/firestore-next/test-firestore/get_document.js new file mode 100644 index 00000000..1653b61a --- /dev/null +++ b/snippets/firestore-next/test-firestore/get_document.js @@ -0,0 +1,19 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START get_document_modular] +import { doc, getDoc } from "firebase/firestore"; + +const docRef = doc(db, "cities", "SF"); +const docSnap = await getDoc(docRef); + +if (docSnap.exists()) { + console.log("Document data:", docSnap.data()); +} else { + // docSnap.data() will be undefined in this case + console.log("No such document!"); +} +// [END get_document_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/get_document_options.js b/snippets/firestore-next/test-firestore/get_document_options.js new file mode 100644 index 00000000..3a708a0d --- /dev/null +++ b/snippets/firestore-next/test-firestore/get_document_options.js @@ -0,0 +1,22 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START get_document_options_modular] +import { doc, getDocFromCache } from "firebase/firestore"; + +const docRef = doc(db, "cities", "SF"); + +// Get a document, forcing the SDK to fetch from the offline cache. +try { + const doc = await getDocFromCache(docRef); + + // Document was found in the cache. If no cached document exists, + // an error will be returned to the 'catch' block below. + console.log("Cached document data:", doc.data()); +} catch (e) { + console.log("Error getting cached document:", e); +} +// [END get_document_options_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/get_multiple.js b/snippets/firestore-next/test-firestore/get_multiple.js new file mode 100644 index 00000000..59f8f9c3 --- /dev/null +++ b/snippets/firestore-next/test-firestore/get_multiple.js @@ -0,0 +1,17 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START get_multiple_modular] +import { collection, query, where, getDocs } from "firebase/firestore"; + +const q = query(collection(db, "cities"), where("capital", "==", true)); + +const querySnapshot = await getDocs(q); +querySnapshot.forEach((doc) => { + // doc.data() is never undefined for query doc snapshots + console.log(doc.id, " => ", doc.data()); +}); +// [END get_multiple_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/get_multiple_all.js b/snippets/firestore-next/test-firestore/get_multiple_all.js new file mode 100644 index 00000000..721f222e --- /dev/null +++ b/snippets/firestore-next/test-firestore/get_multiple_all.js @@ -0,0 +1,15 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START get_multiple_all_modular] +import { collection, getDocs } from "firebase/firestore"; + +const querySnapshot = await getDocs(collection(db, "cities")); +querySnapshot.forEach((doc) => { + // doc.data() is never undefined for query doc snapshots + console.log(doc.id, " => ", doc.data()); +}); +// [END get_multiple_all_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/handle_listen_errors.js b/snippets/firestore-next/test-firestore/handle_listen_errors.js new file mode 100644 index 00000000..c330b67b --- /dev/null +++ b/snippets/firestore-next/test-firestore/handle_listen_errors.js @@ -0,0 +1,18 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START handle_listen_errors_modular] +import { collection, onSnapshot } from "firebase/firestore"; + +const unsubscribe = onSnapshot( + collection(db, "cities"), + (snapshot) => { + // ... + }, + (error) => { + // ... + }); +// [END handle_listen_errors_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/in_filter.js b/snippets/firestore-next/test-firestore/in_filter.js new file mode 100644 index 00000000..4a041397 --- /dev/null +++ b/snippets/firestore-next/test-firestore/in_filter.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START in_filter_modular] +import { query, where } from "firebase/firestore"; + +const q = query(citiesRef, where('country', 'in', ['USA', 'Japan'])); +// [END in_filter_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/in_filter_with_array.js b/snippets/firestore-next/test-firestore/in_filter_with_array.js new file mode 100644 index 00000000..190e9dfe --- /dev/null +++ b/snippets/firestore-next/test-firestore/in_filter_with_array.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START in_filter_with_array_modular] +import { query, where } from "firebase/firestore"; + +const q = query(citiesRef, where('regions', 'in', [['west_coast'], ['east_coast']])); +// [END in_filter_with_array_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/initialize_persistence.js b/snippets/firestore-next/test-firestore/initialize_persistence.js new file mode 100644 index 00000000..acd5e988 --- /dev/null +++ b/snippets/firestore-next/test-firestore/initialize_persistence.js @@ -0,0 +1,23 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START initialize_persistence_modular] +import { enableIndexedDbPersistence } from "firebase/firestore"; + +enableIndexedDbPersistence(db) + .catch((err) => { + if (err.code == 'failed-precondition') { + // Multiple tabs open, persistence can only be enabled + // in one tab at a a time. + // ... + } else if (err.code == 'unimplemented') { + // The current browser does not support all of the + // features required to enable persistence + // ... + } + }); +// Subsequent queries will use persistence, if it was enabled successfully +// [END initialize_persistence_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/invalid_filter_and_order.js b/snippets/firestore-next/test-firestore/invalid_filter_and_order.js new file mode 100644 index 00000000..708c9cd1 --- /dev/null +++ b/snippets/firestore-next/test-firestore/invalid_filter_and_order.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START invalid_filter_and_order_modular] +import { query, where, orderBy } from "firebase/firestore"; + +const q = query(citiesRef, where("population", ">", 100000), orderBy("country")); +// [END invalid_filter_and_order_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/invalid_range_filters.js b/snippets/firestore-next/test-firestore/invalid_range_filters.js new file mode 100644 index 00000000..b6236824 --- /dev/null +++ b/snippets/firestore-next/test-firestore/invalid_range_filters.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START invalid_range_filters_modular] +import { query, where } from "firebase/firestore"; + +const q = query(citiesRef, where("state", ">=", "CA"), where("population", ">", 100000)); +// [END invalid_range_filters_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/listen_diffs.js b/snippets/firestore-next/test-firestore/listen_diffs.js new file mode 100644 index 00000000..531c6f1e --- /dev/null +++ b/snippets/firestore-next/test-firestore/listen_diffs.js @@ -0,0 +1,24 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START listen_diffs_modular] +import { collection, query, where, onSnapshot } from "firebase/firestore"; + +const q = query(collection(db, "cities"), where("state", "==", "CA")); +const unsubscribe = onSnapshot(q, (snapshot) => { + snapshot.docChanges().forEach((change) => { + if (change.type === "added") { + console.log("New city: ", change.doc.data()); + } + if (change.type === "modified") { + console.log("Modified city: ", change.doc.data()); + } + if (change.type === "removed") { + console.log("Removed city: ", change.doc.data()); + } + }); +}); +// [END listen_diffs_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/listen_document.js b/snippets/firestore-next/test-firestore/listen_document.js new file mode 100644 index 00000000..47b65229 --- /dev/null +++ b/snippets/firestore-next/test-firestore/listen_document.js @@ -0,0 +1,13 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START listen_document_modular] +import { doc, onSnapshot } from "firebase/firestore"; + +const unsub = onSnapshot(doc(db, "cities", "SF"), (doc) => { + console.log("Current data: ", doc.data()); +}); +// [END listen_document_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/listen_document_local.js b/snippets/firestore-next/test-firestore/listen_document_local.js new file mode 100644 index 00000000..cdb4e63b --- /dev/null +++ b/snippets/firestore-next/test-firestore/listen_document_local.js @@ -0,0 +1,14 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START listen_document_local_modular] +import { doc, onSnapshot } from "firebase/firestore"; + +const unsub = onSnapshot(doc(db, "cities", "SF"), (doc) => { + const source = doc.metadata.hasPendingWrites ? "Local" : "Server"; + console.log(source, " data: ", doc.data()); +}); +// [END listen_document_local_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/listen_for_users.js b/snippets/firestore-next/test-firestore/listen_for_users.js new file mode 100644 index 00000000..5a519349 --- /dev/null +++ b/snippets/firestore-next/test-firestore/listen_for_users.js @@ -0,0 +1,17 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START listen_for_users_modular] +import { collection, where, query, onSnapshot } from "firebase/firestore"; + +const q = query(collection(db, "users"), where("born", "<", 1900)); +const unsubscribe = onSnapshot(q, (snapshot) => { + console.log("Current users born before 1900:"); + snapshot.forEach((userSnapshot) => { + console.log(userSnapshot.data()); + }); +}); +// [END listen_for_users_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/listen_multiple.js b/snippets/firestore-next/test-firestore/listen_multiple.js new file mode 100644 index 00000000..cf30c178 --- /dev/null +++ b/snippets/firestore-next/test-firestore/listen_multiple.js @@ -0,0 +1,18 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START listen_multiple_modular] +import { collection, query, where, onSnapshot } from "firebase/firestore"; + +const q = query(collection(db, "cities"), where("state", "==", "CA")); +const unsubscribe = onSnapshot(q, (querySnapshot) => { + const cities = []; + querySnapshot.forEach((doc) => { + cities.push(doc.data().name); + }); + console.log("Current cities in CA: ", cities.join(", ")); +}); +// [END listen_multiple_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/listen_with_metadata.js b/snippets/firestore-next/test-firestore/listen_with_metadata.js new file mode 100644 index 00000000..693c1dc1 --- /dev/null +++ b/snippets/firestore-next/test-firestore/listen_with_metadata.js @@ -0,0 +1,16 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START listen_with_metadata_modular] +import { doc, onSnapshot } from "firebase/firestore"; + +const unsub = onSnapshot( + doc(db, "cities", "SF"), + { includeMetadataChanges: true }, + (doc) => { + // ... + }); +// [END listen_with_metadata_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/new_document.js b/snippets/firestore-next/test-firestore/new_document.js new file mode 100644 index 00000000..219efce7 --- /dev/null +++ b/snippets/firestore-next/test-firestore/new_document.js @@ -0,0 +1,15 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START new_document_modular] +import { collection, doc, setDoc } from "firebase/firestore"; + +// Add a new document with a generated id +const newCityRef = doc(collection(db, "cities")); + +// later... +await setDoc(newCityRef, data); +// [END new_document_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/not_in_filter.js b/snippets/firestore-next/test-firestore/not_in_filter.js new file mode 100644 index 00000000..70e82480 --- /dev/null +++ b/snippets/firestore-next/test-firestore/not_in_filter.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START not_in_filter_modular] +import { query, where } from "firebase/firestore"; + +const q = query(citiesRef, where('country', 'not-in', ['USA', 'Japan'])); +// [END not_in_filter_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/order_and_end.js b/snippets/firestore-next/test-firestore/order_and_end.js new file mode 100644 index 00000000..7c68047f --- /dev/null +++ b/snippets/firestore-next/test-firestore/order_and_end.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START order_and_end_modular] +import { query, orderBy, endAt } from "firebase/firestore"; + +const q = query(citiesRef, orderBy("population"), endAt(1000000)); +// [END order_and_end_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/order_and_limit.js b/snippets/firestore-next/test-firestore/order_and_limit.js new file mode 100644 index 00000000..6ff03737 --- /dev/null +++ b/snippets/firestore-next/test-firestore/order_and_limit.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START order_and_limit_modular] +import { query, orderBy, limit } from "firebase/firestore"; + +const q = query(citiesRef, orderBy("name"), limit(3)); +// [END order_and_limit_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/order_and_limit_desc.js b/snippets/firestore-next/test-firestore/order_and_limit_desc.js new file mode 100644 index 00000000..725983de --- /dev/null +++ b/snippets/firestore-next/test-firestore/order_and_limit_desc.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START order_and_limit_desc_modular] +import { query, orderBy, limit } from "firebase/firestore"; + +const q = query(citiesRef, orderBy("name", "desc"), limit(3)); +// [END order_and_limit_desc_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/order_and_start.js b/snippets/firestore-next/test-firestore/order_and_start.js new file mode 100644 index 00000000..35cccc84 --- /dev/null +++ b/snippets/firestore-next/test-firestore/order_and_start.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START order_and_start_modular] +import { query, orderBy, startAt } from "firebase/firestore"; + +const q = query(citiesRef, orderBy("population"), startAt(1000000)); +// [END order_and_start_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/order_multiple.js b/snippets/firestore-next/test-firestore/order_multiple.js new file mode 100644 index 00000000..7e6e5ad6 --- /dev/null +++ b/snippets/firestore-next/test-firestore/order_multiple.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START order_multiple_modular] +import { query, orderBy } from "firebase/firestore"; + +const q = query(citiesRef, orderBy("state"), orderBy("population", "desc")); +// [END order_multiple_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/paginate.js b/snippets/firestore-next/test-firestore/paginate.js new file mode 100644 index 00000000..cdc146ca --- /dev/null +++ b/snippets/firestore-next/test-firestore/paginate.js @@ -0,0 +1,24 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START paginate_modular] +import { collection, query, orderBy, startAfter, limit, getDocs } from "firebase/firestore"; + +// Query the first page of docs +const first = query(collection(db, "cities"), orderBy("population"), limit(25)); +const documentSnapshots = await getDocs(first); + +// Get the last visible document +const lastVisible = documentSnapshots.docs[documentSnapshots.docs.length-1]; +console.log("last", lastVisible); + +// Construct a new query starting at this document, +// get the next 25 cities. +const next = query(collection(db, "cities"), + orderBy("population"), + startAfter(lastVisible), + limit(25)); +// [END paginate_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/server_timestamp_resolution_options.js b/snippets/firestore-next/test-firestore/server_timestamp_resolution_options.js new file mode 100644 index 00000000..ac0ce5cb --- /dev/null +++ b/snippets/firestore-next/test-firestore/server_timestamp_resolution_options.js @@ -0,0 +1,27 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START server_timestamp_resolution_options_modular] +import { doc, updateDoc, serverTimestamp, onSnapshot } from "firebase/firestore"; +// Perform an update followed by an immediate read without +// waiting for the update to complete. Due to the snapshot +// options we will get two results: one with an estimate +// timestamp and one with the resolved server timestamp. +const docRef = doc(db, 'objects', 'some-id'); +updateDoc(docRef, { + timestamp: serverTimestamp() +}); + +onSnapshot(docRef, (snapshot) => { + const data = snapshot.data({ + // Options: 'estimate', 'previous', or 'none' + serverTimestamps: "estimate" + }); + console.log( + 'Timestamp: ' + data.timestamp + + ', pending: ' + snapshot.metadata.hasPendingWrites); +}); +// [END server_timestamp_resolution_options_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/set_custom_object.js b/snippets/firestore-next/test-firestore/set_custom_object.js new file mode 100644 index 00000000..e27d3dd4 --- /dev/null +++ b/snippets/firestore-next/test-firestore/set_custom_object.js @@ -0,0 +1,13 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START set_custom_object_modular] +import { doc, setDoc } from "firebase/firestore"; + +// Set with cityConverter +const ref = doc(db, "cities", "LA").withConverter(cityConverter); +await setDoc(ref, new City("Los Angeles", "CA", "USA")); +// [END set_custom_object_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/set_document.js b/snippets/firestore-next/test-firestore/set_document.js new file mode 100644 index 00000000..a4c9bdb4 --- /dev/null +++ b/snippets/firestore-next/test-firestore/set_document.js @@ -0,0 +1,16 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START set_document_modular] +import { doc, setDoc } from "firebase/firestore"; + +// Add a new document in collection "cities" +await setDoc(doc(db, "cities", "LA"), { + name: "Los Angeles", + state: "CA", + country: "USA" +}); +// [END set_document_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/set_with_merge.js b/snippets/firestore-next/test-firestore/set_with_merge.js new file mode 100644 index 00000000..f33def30 --- /dev/null +++ b/snippets/firestore-next/test-firestore/set_with_merge.js @@ -0,0 +1,12 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START set_with_merge_modular] +import { doc, setDoc } from "firebase/firestore"; + +const cityRef = doc(db, 'cities', 'BJ'); +setDoc(cityRef, { capital: true }, { merge: true }); +// [END set_with_merge_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/simple_queries.js b/snippets/firestore-next/test-firestore/simple_queries.js new file mode 100644 index 00000000..9eede831 --- /dev/null +++ b/snippets/firestore-next/test-firestore/simple_queries.js @@ -0,0 +1,14 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START simple_queries_modular] +// Create a reference to the cities collection +import { collection, query, where } from "firebase/firestore"; +const citiesRef = collection(db, "cities"); + +// Create a query against the collection. +const q = query(citiesRef, where("state", "==", "CA")); +// [END simple_queries_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/simple_queries_again.js b/snippets/firestore-next/test-firestore/simple_queries_again.js new file mode 100644 index 00000000..bb2ebc83 --- /dev/null +++ b/snippets/firestore-next/test-firestore/simple_queries_again.js @@ -0,0 +1,12 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START simple_queries_again_modular] +import { collection, query, where } from "firebase/firestore"; +const citiesRef = collection(db, "cities"); + +const q = query(citiesRef, where("capital", "==", true)); +// [END simple_queries_again_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/simple_query_not_equal.js b/snippets/firestore-next/test-firestore/simple_query_not_equal.js new file mode 100644 index 00000000..d2bef734 --- /dev/null +++ b/snippets/firestore-next/test-firestore/simple_query_not_equal.js @@ -0,0 +1,9 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START simple_query_not_equal_modular] +const notCapitalQuery = query(citiesRef, where("capital", "!=", false)); +// [END simple_query_not_equal_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/start_doc.js b/snippets/firestore-next/test-firestore/start_doc.js new file mode 100644 index 00000000..88448fcd --- /dev/null +++ b/snippets/firestore-next/test-firestore/start_doc.js @@ -0,0 +1,16 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START start_doc_modular] +import { collection, doc, getDoc, query, orderBy, startAt } from "firebase/firestore"; +const citiesRef = collection(db, "cities"); + +const docSnap = await getDoc(doc(citiesRef, "SF")); + +// Get all cities with a population bigger than San Francisco +const biggerThanSf = query(citiesRef, orderBy("population"), startAt(docSnap)); +// ... +// [END start_doc_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/start_multiple_orderby.js b/snippets/firestore-next/test-firestore/start_multiple_orderby.js new file mode 100644 index 00000000..88133d16 --- /dev/null +++ b/snippets/firestore-next/test-firestore/start_multiple_orderby.js @@ -0,0 +1,20 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START start_multiple_orderby_modular] +// Will return all Springfields +import { collection, query, orderBy, startAt } from "firebase/firestore"; +const q1 = query(collection(db, "cities"), + orderBy("name"), + orderBy("state"), + startAt("Springfield")); + +// Will return "Springfield, Missouri" and "Springfield, Wisconsin" +const q2 = query(collection(db, "cities"), + orderBy("name"), + orderBy("state"), + startAt("Springfield", "Missouri")); +// [END start_multiple_orderby_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/subcollection_reference.js b/snippets/firestore-next/test-firestore/subcollection_reference.js new file mode 100644 index 00000000..997714f1 --- /dev/null +++ b/snippets/firestore-next/test-firestore/subcollection_reference.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START subcollection_reference_modular] +import { doc } from "firebase/firestore"; + +const messageRef = doc(db, "rooms", "roomA", "messages", "message1"); +// [END subcollection_reference_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/transaction.js b/snippets/firestore-next/test-firestore/transaction.js new file mode 100644 index 00000000..727c5f41 --- /dev/null +++ b/snippets/firestore-next/test-firestore/transaction.js @@ -0,0 +1,24 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START transaction_modular] +import { runTransaction } from "firebase/firestore"; + +try { + await runTransaction(db, async (transaction) => { + const sfDoc = await transaction.get(sfDocRef); + if (!sfDoc.exists()) { + throw "Document does not exist!"; + } + + const newPopulation = sfDoc.data().population + 1; + transaction.update(sfDocRef, { population: newPopulation }); + }); + console.log("Transaction successfully committed!"); +} catch (e) { + console.log("Transaction failed: ", e); +} +// [END transaction_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/transaction_promise.js b/snippets/firestore-next/test-firestore/transaction_promise.js new file mode 100644 index 00000000..94792933 --- /dev/null +++ b/snippets/firestore-next/test-firestore/transaction_promise.js @@ -0,0 +1,34 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START transaction_promise_modular] +import { doc, runTransaction } from "firebase/firestore"; + +// Create a reference to the SF doc. +const sfDocRef = doc(db, "cities", "SF"); + +try { + const newPopulation = await runTransaction(db, async (transaction) => { + const sfDoc = await transaction.get(sfDocRef); + if (!sfDoc.exists()) { + throw "Document does not exist!"; + } + + const newPop = sfDoc.data().population + 1; + if (newPop <= 1000000) { + transaction.update(sfDocRef, { population: newPop }); + return newPop; + } else { + return Promise.reject("Sorry! Population is too big"); + } + }); + + console.log("Population increased to ", newPopulation); +} catch (e) { + // This will be a "population is too big" error. + console.error(e); +} +// [END transaction_promise_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/update_delete_field.js b/snippets/firestore-next/test-firestore/update_delete_field.js new file mode 100644 index 00000000..fc6f1f94 --- /dev/null +++ b/snippets/firestore-next/test-firestore/update_delete_field.js @@ -0,0 +1,16 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START update_delete_field_modular] +import { doc, updateDoc, deleteField } from "firebase/firestore"; + +const cityRef = doc(db, 'cities', 'BJ'); + +// Remove the 'capital' field from the document +await updateDoc(cityRef, { + capital: deleteField() +}); +// [END update_delete_field_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/update_document.js b/snippets/firestore-next/test-firestore/update_document.js new file mode 100644 index 00000000..1073482a --- /dev/null +++ b/snippets/firestore-next/test-firestore/update_document.js @@ -0,0 +1,16 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START update_document_modular] +import { doc, updateDoc } from "firebase/firestore"; + +const washingtonRef = doc(db, "cities", "DC"); + +// Set the "capital" field of the city 'DC' +await updateDoc(washingtonRef, { + capital: true +}); +// [END update_document_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/update_document_array.js b/snippets/firestore-next/test-firestore/update_document_array.js new file mode 100644 index 00000000..29a5c04e --- /dev/null +++ b/snippets/firestore-next/test-firestore/update_document_array.js @@ -0,0 +1,21 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START update_document_array_modular] +import { doc, updateDoc, arrayUnion, arrayRemove } from "firebase/firestore"; + +const washingtonRef = doc(db, "cities", "DC"); + +// Atomically add a new region to the "regions" array field. +await updateDoc(washingtonRef, { + regions: arrayUnion("greater_virginia") +}); + +// Atomically remove a region from the "regions" array field. +await updateDoc(washingtonRef, { + regions: arrayRemove("east_coast") +}); +// [END update_document_array_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/update_document_increment.js b/snippets/firestore-next/test-firestore/update_document_increment.js new file mode 100644 index 00000000..b8555bcf --- /dev/null +++ b/snippets/firestore-next/test-firestore/update_document_increment.js @@ -0,0 +1,16 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START update_document_increment_modular] +import { doc, updateDoc, increment } from "firebase/firestore"; + +const washingtonRef = doc(db, "cities", "DC"); + +// Atomically increment the population of the city by 50. +await updateDoc(washingtonRef, { + population: increment(50) +}); +// [END update_document_increment_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/update_document_nested.js b/snippets/firestore-next/test-firestore/update_document_nested.js new file mode 100644 index 00000000..be66b031 --- /dev/null +++ b/snippets/firestore-next/test-firestore/update_document_nested.js @@ -0,0 +1,23 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START update_document_nested_modular] +import { doc, setDoc, updateDoc } from "firebase/firestore"; + +// Create an initial document to update. +const frankDocRef = doc(db, "users", "frank"); +await setDoc(frankDocRef, { + name: "Frank", + favorites: { food: "Pizza", color: "Blue", subject: "recess" }, + age: 12 +}); + +// To update age and favorite color: +await updateDoc(frankDocRef, { + "age": 13, + "favorites.color": "Red" +}); +// [END update_document_nested_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/update_with_server_timestamp.js b/snippets/firestore-next/test-firestore/update_with_server_timestamp.js new file mode 100644 index 00000000..e761f11f --- /dev/null +++ b/snippets/firestore-next/test-firestore/update_with_server_timestamp.js @@ -0,0 +1,16 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START update_with_server_timestamp_modular] +import { updateDoc, serverTimestamp } from "firebase/firestore"; + +const docRef = doc(db, 'objects', 'some-id'); + +// Update the timestamp field with the value from the server +const updateTimestamp = await updateDoc(docRef, { + timestamp: serverTimestamp() +}); +// [END update_with_server_timestamp_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/use_from_cache.js b/snippets/firestore-next/test-firestore/use_from_cache.js new file mode 100644 index 00000000..e9f257da --- /dev/null +++ b/snippets/firestore-next/test-firestore/use_from_cache.js @@ -0,0 +1,21 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START use_from_cache_modular] +import { collection, onSnapshot, where, query } from "firebase/firestore"; + +const q = query(collection(db, "cities"), where("state", "==", "CA")); +onSnapshot(q, { includeMetadataChanges: true }, (snapshot) => { + snapshot.docChanges().forEach((change) => { + if (change.type === "added") { + console.log("New city: ", change.doc.data()); + } + + const source = snapshot.metadata.fromCache ? "local cache" : "server"; + console.log("Data came from " + source); + }); +}); +// [END use_from_cache_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/valid_filter_and_order.js b/snippets/firestore-next/test-firestore/valid_filter_and_order.js new file mode 100644 index 00000000..d75524e9 --- /dev/null +++ b/snippets/firestore-next/test-firestore/valid_filter_and_order.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START valid_filter_and_order_modular] +import { query, where, orderBy } from "firebase/firestore"; + +const q = query(citiesRef, where("population", ">", 100000), orderBy("population")); +// [END valid_filter_and_order_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/valid_range_filters.js b/snippets/firestore-next/test-firestore/valid_range_filters.js new file mode 100644 index 00000000..9a7f5350 --- /dev/null +++ b/snippets/firestore-next/test-firestore/valid_range_filters.js @@ -0,0 +1,12 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START valid_range_filters_modular] +import { query, where } from "firebase/firestore"; + +const q1 = query(citiesRef, where("state", ">=", "CA"), where("state", "<=", "IN")); +const q2 = query(citiesRef, where("state", "==", "CA"), where("population", ">", 1000000)); +// [END valid_range_filters_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-firestore/write_batch.js b/snippets/firestore-next/test-firestore/write_batch.js new file mode 100644 index 00000000..38f41f47 --- /dev/null +++ b/snippets/firestore-next/test-firestore/write_batch.js @@ -0,0 +1,27 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.firestore.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START write_batch_modular] +import { writeBatch, doc } from "firebase/firestore"; + +// Get a new write batch +const batch = writeBatch(db); + +// Set the value of 'NYC' +const nycRef = doc(db, "cities", "NYC"); +batch.set(nycRef, {name: "New York City"}); + +// Update the population of 'SF' +const sfRef = doc(db, "cities", "SF"); +batch.update(sfRef, {"population": 1000000}); + +// Delete the city 'LA' +const laRef = doc(db, "cities", "LA"); +batch.delete(laRef); + +// Commit the batch +await batch.commit(); +// [END write_batch_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-solution-aggregation/get_collection_ratings.js b/snippets/firestore-next/test-solution-aggregation/get_collection_ratings.js new file mode 100644 index 00000000..bc83cf8b --- /dev/null +++ b/snippets/firestore-next/test-solution-aggregation/get_collection_ratings.js @@ -0,0 +1,12 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.solution-aggregation.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START get_collection_ratings_modular] +import { collection, getDocs } from "firebase/firestore"; + +const ratingsRef = collection(db, "restaurants", "arinell-pizza", "ratings"); +const ratingsDocs = await getDocs(ratingsRef); +// [END get_collection_ratings_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-solution-aggregation/sample_doc.js b/snippets/firestore-next/test-solution-aggregation/sample_doc.js new file mode 100644 index 00000000..4cec6773 --- /dev/null +++ b/snippets/firestore-next/test-solution-aggregation/sample_doc.js @@ -0,0 +1,13 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.solution-aggregation.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START sample_doc_modular] +const arinellDoc = { + name: 'Arinell Pizza', + avgRating: 4.65, + numRatings: 683 +}; +// [END sample_doc_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-solution-arrays/post_with_array.js b/snippets/firestore-next/test-solution-arrays/post_with_array.js new file mode 100644 index 00000000..eb336d14 --- /dev/null +++ b/snippets/firestore-next/test-solution-arrays/post_with_array.js @@ -0,0 +1,17 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.solution-arrays.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START post_with_array_modular] +// Sample document in the 'posts' collection. +{ + title: "My great post", + categories: [ + "technology", + "opinion", + "cats" + ] +} +// [END post_with_array_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-solution-arrays/post_with_map.js b/snippets/firestore-next/test-solution-arrays/post_with_map.js new file mode 100644 index 00000000..16313ace --- /dev/null +++ b/snippets/firestore-next/test-solution-arrays/post_with_map.js @@ -0,0 +1,17 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.solution-arrays.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START post_with_map_modular] +// Sample document in the 'posts' collection +{ + title: "My great post", + categories: { + "technology": true, + "opinion": true, + "cats": true + } +} +// [END post_with_map_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-solution-arrays/post_with_map_advanced.js b/snippets/firestore-next/test-solution-arrays/post_with_map_advanced.js new file mode 100644 index 00000000..4cb176db --- /dev/null +++ b/snippets/firestore-next/test-solution-arrays/post_with_map_advanced.js @@ -0,0 +1,17 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.solution-arrays.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START post_with_map_advanced_modular] +// The value of each entry in 'categories' is a unix timestamp +{ + title: "My great post", + categories: { + technology: 1502144665, + opinion: 1502144665, + cats: 1502144665 + } +} +// [END post_with_map_advanced_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-solution-arrays/query_in_category.js b/snippets/firestore-next/test-solution-arrays/query_in_category.js new file mode 100644 index 00000000..3a13d2cf --- /dev/null +++ b/snippets/firestore-next/test-solution-arrays/query_in_category.js @@ -0,0 +1,15 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.solution-arrays.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START query_in_category_modular] +import { collection, getDocs, query, where } from "firebase/firestore"; + +// Find all documents in the 'posts' collection that are +// in the 'cats' category. +const q = query(collection(db, "posts"), where("categories.cats", "==", true)); +const docs = await getDocs(q); +// ... +// [END query_in_category_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-solution-arrays/query_in_category_timestamp.js b/snippets/firestore-next/test-solution-arrays/query_in_category_timestamp.js new file mode 100644 index 00000000..87dd9c6f --- /dev/null +++ b/snippets/firestore-next/test-solution-arrays/query_in_category_timestamp.js @@ -0,0 +1,13 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.solution-arrays.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START query_in_category_timestamp_modular] +import { collection, query, where, orderBy } from "firebase/firestore"; + +const q = query(collection(db, "posts"), + where("categories.cats", ">", 0), + orderBy("categories.cats")); +// [END query_in_category_timestamp_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-solution-arrays/query_in_category_timestamp_invalid.js b/snippets/firestore-next/test-solution-arrays/query_in_category_timestamp_invalid.js new file mode 100644 index 00000000..f50dc17c --- /dev/null +++ b/snippets/firestore-next/test-solution-arrays/query_in_category_timestamp_invalid.js @@ -0,0 +1,13 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.solution-arrays.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START query_in_category_timestamp_invalid_modular] +import { collection, query, where, orderBy, Firestore } from "firebase/firestore"; + +const q = query(collection(db, "posts"), + where("categories.cats", "==", true), + orderBy("timestamp")); +// [END query_in_category_timestamp_invalid_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-solution-bundles/fs_bundle_load.js b/snippets/firestore-next/test-solution-bundles/fs_bundle_load.js new file mode 100644 index 00000000..e61315d2 --- /dev/null +++ b/snippets/firestore-next/test-solution-bundles/fs_bundle_load.js @@ -0,0 +1,25 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.solution-bundles.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START fs_bundle_load_modular] +import { loadBundle, namedQuery, getDocsFromCache } from "firebase/firestore"; + +async function fetchFromBundle() { + // Fetch the bundle from Firebase Hosting, if the CDN cache is hit the 'X-Cache' + // response header will be set to 'HIT' + const resp = await fetch('/createBundle'); + + // Load the bundle contents into the Firestore SDK + await loadBundle(db, resp.body); + + // Query the results from the cache + const query = await namedQuery(db, 'latest-stories-query'); + const storiesSnap = await getDocsFromCache(query); + + // Use the results + // ... +} +// [END fs_bundle_load_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-solution-counters/create_counter.js b/snippets/firestore-next/test-solution-counters/create_counter.js new file mode 100644 index 00000000..11dfb3d8 --- /dev/null +++ b/snippets/firestore-next/test-solution-counters/create_counter.js @@ -0,0 +1,25 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.solution-counters.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START create_counter_modular] +function createCounter(ref, num_shards) { + import { doc, writeBatch } from "firebase/firestore"; + + const batch = writeBatch(db); + + // Initialize the counter document + batch.set(ref, { num_shards: num_shards }); + + // Initialize each shard with count=0 + for (let i = 0; i < num_shards; i++) { + const shardRef = doc(ref, 'shards', i.toString()); + batch.set(shardRef, { count: 0 }); + } + + // Commit the write batch + return batch.commit(); +} +// [END create_counter_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-solution-counters/get_count.js b/snippets/firestore-next/test-solution-counters/get_count.js new file mode 100644 index 00000000..7dec78f1 --- /dev/null +++ b/snippets/firestore-next/test-solution-counters/get_count.js @@ -0,0 +1,21 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.solution-counters.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START get_count_modular] +async function getCount(ref) { + import { collection, getDocs } from "firebase/firestore"; + + // Sum the count of each shard in the subcollection + const snapshot = await getDocs(collection(ref, 'shards')); + + let totalCount = 0; + snapshot.forEach((doc) => { + totalCount += doc.data().count; + }); + + return totalCount; +} +// [END get_count_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-solution-counters/increment_counter.js b/snippets/firestore-next/test-solution-counters/increment_counter.js new file mode 100644 index 00000000..22defe9c --- /dev/null +++ b/snippets/firestore-next/test-solution-counters/increment_counter.js @@ -0,0 +1,18 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.solution-counters.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START increment_counter_modular] +function incrementCounter(db, ref, num_shards) { + import { doc, updateDoc, increment } from "firebase/firestore"; + + // Select a shard of the counter at random + const shardId = Math.floor(Math.random() * num_shards).toString(); + const shardRef = doc(ref, 'shards', shardId); + + // Update count + return updateDoc(shardRef, "count", increment(1)); +} +// [END increment_counter_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-solution-geoqueries/fs_geo_add_hash.js b/snippets/firestore-next/test-solution-geoqueries/fs_geo_add_hash.js new file mode 100644 index 00000000..29caa3d5 --- /dev/null +++ b/snippets/firestore-next/test-solution-geoqueries/fs_geo_add_hash.js @@ -0,0 +1,23 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.solution-geoqueries.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START fs_geo_add_hash_modular] +import { doc, updateDoc } from 'firebase/firestore'; + +// Compute the GeoHash for a lat/lng point +const lat = 51.5074; +const lng = 0.1278; +const hash = geofire.geohashForLocation([lat, lng]); + +// Add the hash and the lat/lng to the document. We will use the hash +// for queries and the lat/lng for distance comparisons. +const londonRef = doc(db, 'cities', 'LON'); +await updateDoc(londonRef, { + geohash: hash, + lat: lat, + lng: lng +}); +// [END fs_geo_add_hash_modular] \ No newline at end of file diff --git a/snippets/firestore-next/test-solution-geoqueries/fs_geo_query_hashes.js b/snippets/firestore-next/test-solution-geoqueries/fs_geo_query_hashes.js new file mode 100644 index 00000000..95a8863c --- /dev/null +++ b/snippets/firestore-next/test-solution-geoqueries/fs_geo_query_hashes.js @@ -0,0 +1,47 @@ +// This snippet file was generated by processing the source file: +// ./firestore-next/test.solution-geoqueries.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START fs_geo_query_hashes_modular] +import { collection, query, orderBy, startAt, endAt, getDocs } from 'firebase/firestore'; + +// Find cities within 50km of London +const center = [51.5074, 0.1278]; +const radiusInM = 50 * 1000; + +// Each item in 'bounds' represents a startAt/endAt pair. We have to issue +// a separate query for each pair. There can be up to 9 pairs of bounds +// depending on overlap, but in most cases there are 4. +const bounds = geofire.geohashQueryBounds(center, radiusInM); +const promises = []; +for (const b of bounds) { + const q = query( + collection(db, 'cities'), + orderBy('geohash'), + startAt(b[0]), + endAt(b[1])); + + promises.push(getDocs(q)); +} + +// Collect all the query results together into a single list +const snapshots = await Promise.all(promises); + +const matchingDocs = []; +for (const snap of snapshots) { + for (const doc of snap.docs) { + const lat = doc.get('lat'); + const lng = doc.get('lng'); + + // We have to filter out a few false positives due to GeoHash + // accuracy, but most will match + const distanceInKm = geofire.distanceBetween([lat, lng], center); + const distanceInM = distanceInKm * 1000; + if (distanceInM <= radiusInM) { + matchingDocs.push(doc); + } + } +} +// [END fs_geo_query_hashes_modular] \ No newline at end of file diff --git a/snippets/functions-next/callable/fb_functions_call_add_message.js b/snippets/functions-next/callable/fb_functions_call_add_message.js new file mode 100644 index 00000000..67c01eff --- /dev/null +++ b/snippets/functions-next/callable/fb_functions_call_add_message.js @@ -0,0 +1,19 @@ +// This snippet file was generated by processing the source file: +// ./functions-next/callable.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START fb_functions_call_add_message_modular] +import { getFunctions, httpsCallable } from "firebase/functions"; + +const functions = getFunctions(); +const addMessage = httpsCallable(functions, 'addMessage'); +addMessage({ text: messageText }) + .then((result) => { + // Read result of the Cloud Function. + /** @type {any} */ + const data = result.data; + const sanitizedMessage = data.text; + }); +// [END fb_functions_call_add_message_modular] \ No newline at end of file diff --git a/snippets/functions-next/callable/fb_functions_call_add_message_error.js b/snippets/functions-next/callable/fb_functions_call_add_message_error.js new file mode 100644 index 00000000..e87afbdd --- /dev/null +++ b/snippets/functions-next/callable/fb_functions_call_add_message_error.js @@ -0,0 +1,26 @@ +// This snippet file was generated by processing the source file: +// ./functions-next/callable.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START fb_functions_call_add_message_error_modular] +import { getFunctions, httpsCallable } from "firebase/functions"; + +const functions = getFunctions(); +const addMessage = httpsCallable(functions, 'addMessage'); +addMessage({ text: messageText }) + .then((result) => { + // Read result of the Cloud Function. + /** @type {any} */ + const data = result.data; + const sanitizedMessage = data.text; + }) + .catch((error) => { + // Getting the Error details. + const code = error.code; + const message = error.message; + const details = error.details; + // ... + }); +// [END fb_functions_call_add_message_error_modular] \ No newline at end of file diff --git a/snippets/functions-next/callable/fb_functions_initialize.js b/snippets/functions-next/callable/fb_functions_initialize.js new file mode 100644 index 00000000..d03268c8 --- /dev/null +++ b/snippets/functions-next/callable/fb_functions_initialize.js @@ -0,0 +1,20 @@ +// This snippet file was generated by processing the source file: +// ./functions-next/callable.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START fb_functions_initialize_modular] +import { initializeApp } from "firebase/app"; +import { getFunctions } from "firebase/functions"; + +initializeApp({ + // Your Firebase Web SDK configuration + // [START_EXCLUDE] + projectId: "", + apiKey: "", + // [END_EXCLUDE] +}); + +const functions = getFunctions(); +// [END fb_functions_initialize_modular] \ No newline at end of file diff --git a/snippets/functions-next/emulator-suite/fb_functions_callable_call.js b/snippets/functions-next/emulator-suite/fb_functions_callable_call.js new file mode 100644 index 00000000..e547c3d3 --- /dev/null +++ b/snippets/functions-next/emulator-suite/fb_functions_callable_call.js @@ -0,0 +1,19 @@ +// This snippet file was generated by processing the source file: +// ./functions-next/emulator-suite.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START fb_functions_callable_call_modular] +import { getApp } from "firebase/app"; +import { getFunctions, httpsCallable } from "firebase/functions"; + +const functions = getFunctions(getApp()); +const addMessage = httpsCallable(functions, 'addMessage'); + +const result = await addMessage({ text: ''}); +/** @type {any} */ +const data = result.data; +const sanitizedMessage = data.text; +// ... +// [END fb_functions_callable_call_modular] \ No newline at end of file diff --git a/snippets/functions-next/emulator-suite/fb_functions_emulator_connect.js b/snippets/functions-next/emulator-suite/fb_functions_emulator_connect.js new file mode 100644 index 00000000..835a2c60 --- /dev/null +++ b/snippets/functions-next/emulator-suite/fb_functions_emulator_connect.js @@ -0,0 +1,13 @@ +// This snippet file was generated by processing the source file: +// ./functions-next/emulator-suite.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START fb_functions_emulator_connect_modular] +import { getApp } from "firebase/app"; +import { getFunctions, connectFunctionsEmulator } from "firebase/functions"; + +const functions = getFunctions(getApp()); +connectFunctionsEmulator(functions, "127.0.0.1", 5001); +// [END fb_functions_emulator_connect_modular] \ No newline at end of file diff --git a/snippets/messaging-next/index/messaging_delete_token.js b/snippets/messaging-next/index/messaging_delete_token.js new file mode 100644 index 00000000..2e27ab2a --- /dev/null +++ b/snippets/messaging-next/index/messaging_delete_token.js @@ -0,0 +1,17 @@ +// This snippet file was generated by processing the source file: +// ./messaging-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START messaging_delete_token_modular] +import { getMessaging, deleteToken } from "firebase/messaging"; + +const messaging = getMessaging(); +deleteToken(messaging).then(() => { + console.log('Token deleted.'); + // ... +}).catch((err) => { + console.log('Unable to delete token. ', err); +}); +// [END messaging_delete_token_modular] \ No newline at end of file diff --git a/snippets/messaging-next/index/messaging_get_messaging_object.js b/snippets/messaging-next/index/messaging_get_messaging_object.js new file mode 100644 index 00000000..1768bdd2 --- /dev/null +++ b/snippets/messaging-next/index/messaging_get_messaging_object.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./messaging-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START messaging_get_messaging_object_modular] +import { getMessaging } from "firebase/messaging"; + +const messaging = getMessaging(); +// [END messaging_get_messaging_object_modular] \ No newline at end of file diff --git a/snippets/messaging-next/index/messaging_get_token.js b/snippets/messaging-next/index/messaging_get_token.js new file mode 100644 index 00000000..fe571340 --- /dev/null +++ b/snippets/messaging-next/index/messaging_get_token.js @@ -0,0 +1,26 @@ +// This snippet file was generated by processing the source file: +// ./messaging-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START messaging_get_token_modular] +import { getMessaging, getToken } from "firebase/messaging"; + +// Get registration token. Initially this makes a network call, once retrieved +// subsequent calls to getToken will return from cache. +const messaging = getMessaging(); +getToken(messaging, { vapidKey: '' }).then((currentToken) => { + if (currentToken) { + // Send the token to your server and update the UI if necessary + // ... + } else { + // Show permission request UI + console.log('No registration token available. Request permission to generate one.'); + // ... + } +}).catch((err) => { + console.log('An error occurred while retrieving token. ', err); + // ... +}); +// [END messaging_get_token_modular] \ No newline at end of file diff --git a/snippets/messaging-next/index/messaging_receive_message.js b/snippets/messaging-next/index/messaging_receive_message.js new file mode 100644 index 00000000..63825c00 --- /dev/null +++ b/snippets/messaging-next/index/messaging_receive_message.js @@ -0,0 +1,19 @@ +// This snippet file was generated by processing the source file: +// ./messaging-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START messaging_receive_message_modular] +// Handle incoming messages. Called when: +// - a message is received while the app has focus +// - the user clicks on an app notification created by a service worker +// `messaging.onBackgroundMessage` handler. +import { getMessaging, onMessage } from "firebase/messaging"; + +const messaging = getMessaging(); +onMessage(messaging, (payload) => { + console.log('Message received. ', payload); + // ... +}); +// [END messaging_receive_message_modular] \ No newline at end of file diff --git a/snippets/messaging-next/index/messaging_request_permission.js b/snippets/messaging-next/index/messaging_request_permission.js new file mode 100644 index 00000000..c6653037 --- /dev/null +++ b/snippets/messaging-next/index/messaging_request_permission.js @@ -0,0 +1,17 @@ +// This snippet file was generated by processing the source file: +// ./messaging-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START messaging_request_permission_modular] +Notification.requestPermission().then((permission) => { + if (permission === 'granted') { + console.log('Notification permission granted.'); + // TODO(developer): Retrieve a registration token for use with FCM. + // ... + } else { + console.log('Unable to get permission to notify.'); + } +}); +// [END messaging_request_permission_modular] \ No newline at end of file diff --git a/snippets/messaging-next/service-worker/messaging_init_in_sw.js b/snippets/messaging-next/service-worker/messaging_init_in_sw.js new file mode 100644 index 00000000..42f0f526 --- /dev/null +++ b/snippets/messaging-next/service-worker/messaging_init_in_sw.js @@ -0,0 +1,28 @@ +// This snippet file was generated by processing the source file: +// ./messaging-next/service-worker.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START messaging_init_in_sw_modular] +import { initializeApp } from "firebase/app"; +import { getMessaging } from "firebase/messaging/sw"; + +// Initialize the Firebase app in the service worker by passing in +// your app's Firebase config object. +// https://firebase.google.com/docs/web/setup#config-object +const firebaseApp = initializeApp({ + apiKey: 'api-key', + authDomain: 'project-id.firebaseapp.com', + databaseURL: 'https://project-id.firebaseio.com', + projectId: 'project-id', + storageBucket: 'project-id.appspot.com', + messagingSenderId: 'sender-id', + appId: 'app-id', + measurementId: 'G-measurement-id', +}); + +// Retrieve an instance of Firebase Messaging so that it can handle background +// messages. +const messaging = getMessaging(firebaseApp); +// [END messaging_init_in_sw_modular] \ No newline at end of file diff --git a/snippets/messaging-next/service-worker/messaging_on_background_message.js b/snippets/messaging-next/service-worker/messaging_on_background_message.js new file mode 100644 index 00000000..79c17eea --- /dev/null +++ b/snippets/messaging-next/service-worker/messaging_on_background_message.js @@ -0,0 +1,24 @@ +// This snippet file was generated by processing the source file: +// ./messaging-next/service-worker.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START messaging_on_background_message_modular] +import { getMessaging } from "firebase/messaging/sw"; +import { onBackgroundMessage } from "firebase/messaging/sw"; + +const messaging = getMessaging(); +onBackgroundMessage(messaging, (payload) => { + console.log('[firebase-messaging-sw.js] Received background message ', payload); + // Customize notification here + const notificationTitle = 'Background Message Title'; + const notificationOptions = { + body: 'Background Message body.', + icon: '/firebase-logo.png' + }; + + self.registration.showNotification(notificationTitle, + notificationOptions); +}); +// [END messaging_on_background_message_modular] \ No newline at end of file diff --git a/snippets/perf-next/index/perf_add_custom_attributes.js b/snippets/perf-next/index/perf_add_custom_attributes.js new file mode 100644 index 00000000..a1a7b13d --- /dev/null +++ b/snippets/perf-next/index/perf_add_custom_attributes.js @@ -0,0 +1,24 @@ +// This snippet file was generated by processing the source file: +// ./perf-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START perf_add_custom_attributes_modular] +import { trace } from "firebase/performance"; + +const t = trace(perf, "test_trace"); +t.putAttribute("experiment", "A"); + +// Update scenario +t.putAttribute("experiment", "B"); + +// Reading scenario +const experimentValue = t.getAttribute("experiment"); + +// Delete scenario +t.removeAttribute("experiment"); + +// Read attributes +const traceAttributes = t.getAttributes(); +// [END perf_add_custom_attributes_modular] \ No newline at end of file diff --git a/snippets/perf-next/index/perf_add_custom_metrics.js b/snippets/perf-next/index/perf_add_custom_metrics.js new file mode 100644 index 00000000..86dd674b --- /dev/null +++ b/snippets/perf-next/index/perf_add_custom_metrics.js @@ -0,0 +1,23 @@ +// This snippet file was generated by processing the source file: +// ./perf-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START perf_add_custom_metrics_modular] +import { trace } from "firebase/performance"; + +async function getInventory(inventoryIds) { + const t = trace(perf, "inventoryRetrieval"); + + // Tracks the number of IDs fetched (the metric could help you to optimize in the future) + t.incrementMetric("numberOfIds", inventoryIds.length); + + // Measures the time it takes to request inventory based on the amount of inventory + t.start(); + const inventoryData = await retrieveInventory(inventoryIds); + t.stop(); + + return inventoryData; +} +// [END perf_add_custom_metrics_modular] \ No newline at end of file diff --git a/snippets/perf-next/index/perf_add_custom_trace.js b/snippets/perf-next/index/perf_add_custom_trace.js new file mode 100644 index 00000000..6fd54e49 --- /dev/null +++ b/snippets/perf-next/index/perf_add_custom_trace.js @@ -0,0 +1,17 @@ +// This snippet file was generated by processing the source file: +// ./perf-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START perf_add_custom_trace_modular] +import { trace } from "firebase/performance"; + +const t = trace(perf, "CUSTOM_TRACE_NAME"); +t.start(); + +// Code that you want to trace +// ... + +t.stop(); +// [END perf_add_custom_trace_modular] \ No newline at end of file diff --git a/snippets/perf-next/index/perf_get_instance.js b/snippets/perf-next/index/perf_get_instance.js new file mode 100644 index 00000000..8b62eb72 --- /dev/null +++ b/snippets/perf-next/index/perf_get_instance.js @@ -0,0 +1,10 @@ +// This snippet file was generated by processing the source file: +// ./perf-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START perf_get_instance_modular] +import { getPerformance } from "firebase/performance"; +const perf = getPerformance(); +// [END perf_get_instance_modular] \ No newline at end of file diff --git a/snippets/perf-next/index/perf_import.js b/snippets/perf-next/index/perf_import.js new file mode 100644 index 00000000..326afe91 --- /dev/null +++ b/snippets/perf-next/index/perf_import.js @@ -0,0 +1,9 @@ +// This snippet file was generated by processing the source file: +// ./perf-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START perf_import_modular] +import { getPerformance } from "firebase/performance"; +// [END perf_import_modular] \ No newline at end of file diff --git a/snippets/perf-next/index/perf_import_app.js b/snippets/perf-next/index/perf_import_app.js new file mode 100644 index 00000000..c2b640b9 --- /dev/null +++ b/snippets/perf-next/index/perf_import_app.js @@ -0,0 +1,9 @@ +// This snippet file was generated by processing the source file: +// ./perf-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START perf_import_app_modular] +import { initializeApp } from "firebase/app"; +// [END perf_import_app_modular] \ No newline at end of file diff --git a/snippets/perf-next/index/perf_initialize_app.js b/snippets/perf-next/index/perf_initialize_app.js new file mode 100644 index 00000000..94e0c966 --- /dev/null +++ b/snippets/perf-next/index/perf_initialize_app.js @@ -0,0 +1,16 @@ +// This snippet file was generated by processing the source file: +// ./perf-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START perf_initialize_app_modular] +// TODO: Replace the following with your app's Firebase project configuration +// See: https://firebase.google.com/docs/web/learn-more#config-object +const firebaseConfig = { + // ... +}; + +// Initialize Firebase +const app = initializeApp(firebaseConfig); +// [END perf_initialize_app_modular] \ No newline at end of file diff --git a/snippets/perf-next/index/perf_singleton.js b/snippets/perf-next/index/perf_singleton.js new file mode 100644 index 00000000..e070b52b --- /dev/null +++ b/snippets/perf-next/index/perf_singleton.js @@ -0,0 +1,10 @@ +// This snippet file was generated by processing the source file: +// ./perf-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START perf_singleton_modular] +// Initialize Performance Monitoring and get a reference to the service +const perf = getPerformance(app); +// [END perf_singleton_modular] \ No newline at end of file diff --git a/snippets/perf-next/index/perf_user_timing_marks.js b/snippets/perf-next/index/perf_user_timing_marks.js new file mode 100644 index 00000000..8450370f --- /dev/null +++ b/snippets/perf-next/index/perf_user_timing_marks.js @@ -0,0 +1,17 @@ +// This snippet file was generated by processing the source file: +// ./perf-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START perf_user_timing_marks_modular] +const performance = window.performance; + +performance.mark("measurementStart"); + +// Code that you want to trace +// ... + +performance.mark("measurementStop"); +performance.measure("customTraceName", "measurementStart", "measurementStop"); +// [END perf_user_timing_marks_modular] \ No newline at end of file diff --git a/snippets/placeholder/coming_soon.js b/snippets/placeholder/coming_soon.js new file mode 100644 index 00000000..0477645f --- /dev/null +++ b/snippets/placeholder/coming_soon.js @@ -0,0 +1,9 @@ +// This snippet file was generated by processing the source file: +// ./placeholder.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START coming_soon_modular] +// TODO: Snippet coming soon! +// [END coming_soon_modular] \ No newline at end of file diff --git a/snippets/remoteconfig-next/index/rc_fetch_config_callback.js b/snippets/remoteconfig-next/index/rc_fetch_config_callback.js new file mode 100644 index 00000000..3cb39de0 --- /dev/null +++ b/snippets/remoteconfig-next/index/rc_fetch_config_callback.js @@ -0,0 +1,17 @@ +// This snippet file was generated by processing the source file: +// ./remoteconfig-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START rc_fetch_config_callback_modular] +import { fetchAndActivate } from "firebase/remote-config"; + +fetchAndActivate(remoteConfig) + .then(() => { + // ... + }) + .catch((err) => { + // ... + }); +// [END rc_fetch_config_callback_modular] \ No newline at end of file diff --git a/snippets/remoteconfig-next/index/rc_get_instance.js b/snippets/remoteconfig-next/index/rc_get_instance.js new file mode 100644 index 00000000..0bf952b8 --- /dev/null +++ b/snippets/remoteconfig-next/index/rc_get_instance.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./remoteconfig-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START rc_get_instance_modular] +import { getRemoteConfig } from "firebase/remote-config"; + +const remoteConfig = getRemoteConfig(); +// [END rc_get_instance_modular] \ No newline at end of file diff --git a/snippets/remoteconfig-next/index/rc_get_values.js b/snippets/remoteconfig-next/index/rc_get_values.js new file mode 100644 index 00000000..67018e3b --- /dev/null +++ b/snippets/remoteconfig-next/index/rc_get_values.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./remoteconfig-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START rc_get_values_modular] +import { getValue } from "firebase/remote-config"; + +const val = getValue(remoteConfig, "welcome_messsage"); +// [END rc_get_values_modular] \ No newline at end of file diff --git a/snippets/remoteconfig-next/index/rc_set_default_values.js b/snippets/remoteconfig-next/index/rc_set_default_values.js new file mode 100644 index 00000000..80e0e8c2 --- /dev/null +++ b/snippets/remoteconfig-next/index/rc_set_default_values.js @@ -0,0 +1,11 @@ +// This snippet file was generated by processing the source file: +// ./remoteconfig-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START rc_set_default_values_modular] +remoteConfig.defaultConfig = { + "welcome_message": "Welcome" +}; +// [END rc_set_default_values_modular] \ No newline at end of file diff --git a/snippets/remoteconfig-next/index/rc_set_minimum_fetch_time.js b/snippets/remoteconfig-next/index/rc_set_minimum_fetch_time.js new file mode 100644 index 00000000..77aa9f45 --- /dev/null +++ b/snippets/remoteconfig-next/index/rc_set_minimum_fetch_time.js @@ -0,0 +1,10 @@ +// This snippet file was generated by processing the source file: +// ./remoteconfig-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START rc_set_minimum_fetch_time_modular] +// The default and recommended production fetch interval for Remote Config is 12 hours +remoteConfig.settings.minimumFetchIntervalMillis = 3600000; +// [END rc_set_minimum_fetch_time_modular] \ No newline at end of file diff --git a/snippets/storage-next/create-reference/storage_create_ref.js b/snippets/storage-next/create-reference/storage_create_ref.js new file mode 100644 index 00000000..761a634a --- /dev/null +++ b/snippets/storage-next/create-reference/storage_create_ref.js @@ -0,0 +1,15 @@ +// This snippet file was generated by processing the source file: +// ./storage-next/create-reference.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START storage_create_ref_modular] +import { getStorage, ref } from "firebase/storage"; + +// Get a reference to the storage service, which is used to create references in your storage bucket +const storage = getStorage(); + +// Create a storage reference from our storage service +const storageRef = ref(storage); +// [END storage_create_ref_modular] \ No newline at end of file diff --git a/snippets/storage-next/create-reference/storage_create_ref_child.js b/snippets/storage-next/create-reference/storage_create_ref_child.js new file mode 100644 index 00000000..5e16407a --- /dev/null +++ b/snippets/storage-next/create-reference/storage_create_ref_child.js @@ -0,0 +1,20 @@ +// This snippet file was generated by processing the source file: +// ./storage-next/create-reference.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START storage_create_ref_child_modular] +import { getStorage, ref } from "firebase/storage"; + +const storage = getStorage(); + +// Create a child reference +const imagesRef = ref(storage, 'images'); +// imagesRef now points to 'images' + +// Child references can also take paths delimited by '/' +const spaceRef = ref(storage, 'images/space.jpg'); +// spaceRef now points to "images/space.jpg" +// imagesRef still points to "images" +// [END storage_create_ref_child_modular] \ No newline at end of file diff --git a/snippets/storage-next/create-reference/storage_navigate_ref.js b/snippets/storage-next/create-reference/storage_navigate_ref.js new file mode 100644 index 00000000..ee279c72 --- /dev/null +++ b/snippets/storage-next/create-reference/storage_navigate_ref.js @@ -0,0 +1,20 @@ +// This snippet file was generated by processing the source file: +// ./storage-next/create-reference.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START storage_navigate_ref_modular] +import { getStorage, ref } from "firebase/storage"; + +const storage = getStorage(); +const spaceRef = ref(storage, 'images/space.jpg'); + +// Parent allows us to move to the parent of a reference +const imagesRef = spaceRef.parent; +// imagesRef now points to 'images' + +// Root allows us to move all the way back to the top of our bucket +const rootRef = spaceRef.root; +// rootRef now points to the root +// [END storage_navigate_ref_modular] \ No newline at end of file diff --git a/snippets/storage-next/create-reference/storage_navigate_ref_chain.js b/snippets/storage-next/create-reference/storage_navigate_ref_chain.js new file mode 100644 index 00000000..d5e13752 --- /dev/null +++ b/snippets/storage-next/create-reference/storage_navigate_ref_chain.js @@ -0,0 +1,19 @@ +// This snippet file was generated by processing the source file: +// ./storage-next/create-reference.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START storage_navigate_ref_chain_modular] +import { getStorage, ref } from "firebase/storage"; + +const storage = getStorage(); +const spaceRef = ref(storage, 'images/space.jpg'); + +// References can be chained together multiple times +const earthRef = ref(spaceRef.parent, 'earth.jpg'); +// earthRef points to 'images/earth.jpg' + +// nullRef is null, since the parent of root is null +const nullRef = spaceRef.root.parent; +// [END storage_navigate_ref_chain_modular] \ No newline at end of file diff --git a/snippets/storage-next/create-reference/storage_ref_full_example.js b/snippets/storage-next/create-reference/storage_ref_full_example.js new file mode 100644 index 00000000..e61f4594 --- /dev/null +++ b/snippets/storage-next/create-reference/storage_ref_full_example.js @@ -0,0 +1,31 @@ +// This snippet file was generated by processing the source file: +// ./storage-next/create-reference.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START storage_ref_full_example_modular] +import { getStorage, ref } from "firebase/storage"; + +const storage = getStorage(); + +// Points to the root reference +const storageRef = ref(storage); + +// Points to 'images' +const imagesRef = ref(storageRef, 'images'); + +// Points to 'images/space.jpg' +// Note that you can use variables to create child values +const fileName = 'space.jpg'; +const spaceRef = ref(imagesRef, fileName); + +// File path is 'images/space.jpg' +const path = spaceRef.fullPath; + +// File name is 'space.jpg' +const name = spaceRef.name; + +// Points to 'images' +const imagesRefAgain = spaceRef.parent; +// [END storage_ref_full_example_modular] \ No newline at end of file diff --git a/snippets/storage-next/create-reference/storage_ref_properties.js b/snippets/storage-next/create-reference/storage_ref_properties.js new file mode 100644 index 00000000..c95a5b13 --- /dev/null +++ b/snippets/storage-next/create-reference/storage_ref_properties.js @@ -0,0 +1,23 @@ +// This snippet file was generated by processing the source file: +// ./storage-next/create-reference.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START storage_ref_properties_modular] +import { getStorage, ref } from "firebase/storage"; + +const storage = getStorage(); +const spaceRef = ref(storage, 'images/space.jpg'); + +// Reference's path is: 'images/space.jpg' +// This is analogous to a file path on disk +spaceRef.fullPath; + +// Reference's name is the last segment of the full path: 'space.jpg' +// This is analogous to the file name +spaceRef.name; + +// Reference's bucket is the name of the storage bucket where files are stored +spaceRef.bucket; +// [END storage_ref_properties_modular] \ No newline at end of file diff --git a/snippets/storage-next/delete-files/storage_delete_file.js b/snippets/storage-next/delete-files/storage_delete_file.js new file mode 100644 index 00000000..c851c447 --- /dev/null +++ b/snippets/storage-next/delete-files/storage_delete_file.js @@ -0,0 +1,21 @@ +// This snippet file was generated by processing the source file: +// ./storage-next/delete-files.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START storage_delete_file_modular] +import { getStorage, ref, deleteObject } from "firebase/storage"; + +const storage = getStorage(); + +// Create a reference to the file to delete +const desertRef = ref(storage, 'images/desert.jpg'); + +// Delete the file +deleteObject(desertRef).then(() => { + // File deleted successfully +}).catch((error) => { + // Uh-oh, an error occurred! +}); +// [END storage_delete_file_modular] \ No newline at end of file diff --git a/snippets/storage-next/download-files/storage_download_create_ref.js b/snippets/storage-next/download-files/storage_download_create_ref.js new file mode 100644 index 00000000..8b1345b7 --- /dev/null +++ b/snippets/storage-next/download-files/storage_download_create_ref.js @@ -0,0 +1,20 @@ +// This snippet file was generated by processing the source file: +// ./storage-next/download-files.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START storage_download_create_ref_modular] +import { getStorage, ref } from "firebase/storage"; + +// Create a reference with an initial file path and name +const storage = getStorage(); +const pathReference = ref(storage, 'images/stars.jpg'); + +// Create a reference from a Google Cloud Storage URI +const gsReference = ref(storage, 'gs://bucket/images/stars.jpg'); + +// Create a reference from an HTTPS URL +// Note that in the URL, characters are URL escaped! +const httpsReference = ref(storage, 'https://firebasestorage.googleapis.com/b/bucket/o/images%20stars.jpg'); +// [END storage_download_create_ref_modular] \ No newline at end of file diff --git a/snippets/storage-next/download-files/storage_download_full_example.js b/snippets/storage-next/download-files/storage_download_full_example.js new file mode 100644 index 00000000..cdd12166 --- /dev/null +++ b/snippets/storage-next/download-files/storage_download_full_example.js @@ -0,0 +1,40 @@ +// This snippet file was generated by processing the source file: +// ./storage-next/download-files.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START storage_download_full_example_modular] +import { getStorage, ref, getDownloadURL } from "firebase/storage"; + +// Create a reference to the file we want to download +const storage = getStorage(); +const starsRef = ref(storage, 'images/stars.jpg'); + +// Get the download URL +getDownloadURL(starsRef) + .then((url) => { + // Insert url into an tag to "download" + }) + .catch((error) => { + // A full list of error codes is available at + // https://firebase.google.com/docs/storage/web/handle-errors + switch (error.code) { + case 'storage/object-not-found': + // File doesn't exist + break; + case 'storage/unauthorized': + // User doesn't have permission to access the object + break; + case 'storage/canceled': + // User canceled the upload + break; + + // ... + + case 'storage/unknown': + // Unknown error occurred, inspect the server response + break; + } + }); +// [END storage_download_full_example_modular] \ No newline at end of file diff --git a/snippets/storage-next/download-files/storage_download_via_url.js b/snippets/storage-next/download-files/storage_download_via_url.js new file mode 100644 index 00000000..c90e235c --- /dev/null +++ b/snippets/storage-next/download-files/storage_download_via_url.js @@ -0,0 +1,31 @@ +// This snippet file was generated by processing the source file: +// ./storage-next/download-files.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START storage_download_via_url_modular] +import { getStorage, ref, getDownloadURL } from "firebase/storage"; + +const storage = getStorage(); +getDownloadURL(ref(storage, 'images/stars.jpg')) + .then((url) => { + // `url` is the download URL for 'images/stars.jpg' + + // This can be downloaded directly: + const xhr = new XMLHttpRequest(); + xhr.responseType = 'blob'; + xhr.onload = (event) => { + const blob = xhr.response; + }; + xhr.open('GET', url); + xhr.send(); + + // Or inserted into an element + const img = document.getElementById('myimg'); + img.setAttribute('src', url); + }) + .catch((error) => { + // Handle any errors + }); +// [END storage_download_via_url_modular] \ No newline at end of file diff --git a/snippets/storage-next/emulator-suite/storage_emulator_connect.js b/snippets/storage-next/emulator-suite/storage_emulator_connect.js new file mode 100644 index 00000000..4b247d17 --- /dev/null +++ b/snippets/storage-next/emulator-suite/storage_emulator_connect.js @@ -0,0 +1,15 @@ +// This snippet file was generated by processing the source file: +// ./storage-next/emulator-suite.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START storage_emulator_connect_modular] +import { getStorage, connectStorageEmulator } from "firebase/storage"; + +const storage = getStorage(); +if (location.hostname === "localhost") { + // Point to the Storage emulator running on localhost. + connectStorageEmulator(storage, "127.0.0.1", 9199); +} +// [END storage_emulator_connect_modular] \ No newline at end of file diff --git a/snippets/storage-next/file-metadata/storage_custom_metadata.js b/snippets/storage-next/file-metadata/storage_custom_metadata.js new file mode 100644 index 00000000..d6fb5ab4 --- /dev/null +++ b/snippets/storage-next/file-metadata/storage_custom_metadata.js @@ -0,0 +1,14 @@ +// This snippet file was generated by processing the source file: +// ./storage-next/file-metadata.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START storage_custom_metadata_modular] +const metadata = { + customMetadata: { + 'location': 'Yosemite, CA, USA', + 'activity': 'Hiking' + } +}; +// [END storage_custom_metadata_modular] \ No newline at end of file diff --git a/snippets/storage-next/file-metadata/storage_delete_metadata.js b/snippets/storage-next/file-metadata/storage_delete_metadata.js new file mode 100644 index 00000000..71db946e --- /dev/null +++ b/snippets/storage-next/file-metadata/storage_delete_metadata.js @@ -0,0 +1,25 @@ +// This snippet file was generated by processing the source file: +// ./storage-next/file-metadata.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START storage_delete_metadata_modular] +import { getStorage, ref, updateMetadata } from "firebase/storage"; + +const storage = getStorage(); +const forestRef = ref(storage, 'images/forest.jpg'); + +// Create file metadata with property to delete +const deleteMetadata = { + contentType: null +}; + +// Delete the metadata property +updateMetadata(forestRef, deleteMetadata) + .then((metadata) => { + // metadata.contentType should be null + }).catch((error) => { + // Uh-oh, an error occurred! + }); +// [END storage_delete_metadata_modular] \ No newline at end of file diff --git a/snippets/storage-next/file-metadata/storage_get_metadata.js b/snippets/storage-next/file-metadata/storage_get_metadata.js new file mode 100644 index 00000000..b2c96f1a --- /dev/null +++ b/snippets/storage-next/file-metadata/storage_get_metadata.js @@ -0,0 +1,22 @@ +// This snippet file was generated by processing the source file: +// ./storage-next/file-metadata.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START storage_get_metadata_modular] +import { getStorage, ref, getMetadata } from "firebase/storage"; + +// Create a reference to the file whose metadata we want to retrieve +const storage = getStorage(); +const forestRef = ref(storage, 'images/forest.jpg'); + +// Get metadata properties +getMetadata(forestRef) + .then((metadata) => { + // Metadata now contains the metadata for 'images/forest.jpg' + }) + .catch((error) => { + // Uh-oh, an error occurred! + }); +// [END storage_get_metadata_modular] \ No newline at end of file diff --git a/snippets/storage-next/file-metadata/storage_update_metadata.js b/snippets/storage-next/file-metadata/storage_update_metadata.js new file mode 100644 index 00000000..2435983b --- /dev/null +++ b/snippets/storage-next/file-metadata/storage_update_metadata.js @@ -0,0 +1,27 @@ +// This snippet file was generated by processing the source file: +// ./storage-next/file-metadata.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START storage_update_metadata_modular] +import { getStorage, ref, updateMetadata } from "firebase/storage"; + +// Create a reference to the file whose metadata we want to change +const storage = getStorage(); +const forestRef = ref(storage, 'images/forest.jpg'); + +// Create file metadata to update +const newMetadata = { + cacheControl: 'public,max-age=300', + contentType: 'image/jpeg' +}; + +// Update metadata properties +updateMetadata(forestRef, newMetadata) + .then((metadata) => { + // Updated metadata for 'images/forest.jpg' is returned in the Promise + }).catch((error) => { + // Uh-oh, an error occurred! + }); +// [END storage_update_metadata_modular] \ No newline at end of file diff --git a/snippets/storage-next/index/storage_custom_app.js b/snippets/storage-next/index/storage_custom_app.js new file mode 100644 index 00000000..7196c252 --- /dev/null +++ b/snippets/storage-next/index/storage_custom_app.js @@ -0,0 +1,15 @@ +// This snippet file was generated by processing the source file: +// ./storage-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START storage_custom_app_modular] +import { getStorage } from "firebase/storage"; + +// Get the default bucket from a custom firebase.app.App +const storage1 = getStorage(customApp); + +// Get a non-default bucket from a custom firebase.app.App +const storage2 = getStorage(customApp, "gs://my-custom-bucket"); +// [END storage_custom_app_modular] \ No newline at end of file diff --git a/snippets/storage-next/index/storage_initialize.js b/snippets/storage-next/index/storage_initialize.js new file mode 100644 index 00000000..422df3dd --- /dev/null +++ b/snippets/storage-next/index/storage_initialize.js @@ -0,0 +1,23 @@ +// This snippet file was generated by processing the source file: +// ./storage-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START storage_initialize_modular] +import { initializeApp } from "firebase/app"; +import { getStorage } from "firebase/storage"; + +// Set the configuration for your app +// TODO: Replace with your app's config object +const firebaseConfig = { + apiKey: '', + authDomain: '', + databaseURL: '', + storageBucket: '' +}; +const firebaseApp = initializeApp(firebaseConfig); + +// Get a reference to the storage service, which is used to create references in your storage bucket +const storage = getStorage(firebaseApp); +// [END storage_initialize_modular] \ No newline at end of file diff --git a/snippets/storage-next/index/storage_multiple_buckets.js b/snippets/storage-next/index/storage_multiple_buckets.js new file mode 100644 index 00000000..426dd4f4 --- /dev/null +++ b/snippets/storage-next/index/storage_multiple_buckets.js @@ -0,0 +1,14 @@ +// This snippet file was generated by processing the source file: +// ./storage-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START storage_multiple_buckets_modular] +import { getApp } from "firebase/app"; +import { getStorage } from "firebase/storage"; + +// Get a non-default Storage bucket +const firebaseApp = getApp(); +const storage = getStorage(firebaseApp, "gs://my-custom-bucket"); +// [END storage_multiple_buckets_modular] \ No newline at end of file diff --git a/snippets/storage-next/index/storage_on_complete.js b/snippets/storage-next/index/storage_on_complete.js new file mode 100644 index 00000000..e5d792f3 --- /dev/null +++ b/snippets/storage-next/index/storage_on_complete.js @@ -0,0 +1,25 @@ +// This snippet file was generated by processing the source file: +// ./storage-next/index.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START storage_on_complete_modular] +import { getStorage, ref, uploadBytesResumable, getDownloadURL } from "firebase/storage"; + +const storage = getStorage(); +const imageRef = ref(storage, 'images/' + file.name); +uploadBytesResumable(imageRef, file, metadata) + .then((snapshot) => { + console.log('Uploaded', snapshot.totalBytes, 'bytes.'); + console.log('File metadata:', snapshot.metadata); + // Let's get a download URL for the file. + getDownloadURL(snapshot.ref).then((url) => { + console.log('File available at', url); + // ... + }); + }).catch((error) => { + console.error('Upload failed', error); + // ... + }); +// [END storage_on_complete_modular] \ No newline at end of file diff --git a/snippets/storage-next/list-files/storage_list_all.js b/snippets/storage-next/list-files/storage_list_all.js new file mode 100644 index 00000000..11e72e9e --- /dev/null +++ b/snippets/storage-next/list-files/storage_list_all.js @@ -0,0 +1,28 @@ +// This snippet file was generated by processing the source file: +// ./storage-next/list-files.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START storage_list_all_modular] +import { getStorage, ref, listAll } from "firebase/storage"; + +const storage = getStorage(); + +// Create a reference under which you want to list +const listRef = ref(storage, 'files/uid'); + +// Find all the prefixes and items. +listAll(listRef) + .then((res) => { + res.prefixes.forEach((folderRef) => { + // All the prefixes under listRef. + // You may call listAll() recursively on them. + }); + res.items.forEach((itemRef) => { + // All the items under listRef. + }); + }).catch((error) => { + // Uh-oh, an error occurred! + }); +// [END storage_list_all_modular] \ No newline at end of file diff --git a/snippets/storage-next/list-files/storage_list_paginate.js b/snippets/storage-next/list-files/storage_list_paginate.js new file mode 100644 index 00000000..c7dd314e --- /dev/null +++ b/snippets/storage-next/list-files/storage_list_paginate.js @@ -0,0 +1,32 @@ +// This snippet file was generated by processing the source file: +// ./storage-next/list-files.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START storage_list_paginate_modular] +import { getStorage, ref, list } from "firebase/storage"; + +async function pageTokenExample(){ + // Create a reference under which you want to list + const storage = getStorage(); + const listRef = ref(storage, 'files/uid'); + + // Fetch the first page of 100. + const firstPage = await list(listRef, { maxResults: 100 }); + + // Use the result. + // processItems(firstPage.items) + // processPrefixes(firstPage.prefixes) + + // Fetch the second page if there are more elements. + if (firstPage.nextPageToken) { + const secondPage = await list(listRef, { + maxResults: 100, + pageToken: firstPage.nextPageToken, + }); + // processItems(secondPage.items) + // processPrefixes(secondPage.prefixes) + } +} +// [END storage_list_paginate_modular] \ No newline at end of file diff --git a/snippets/storage-next/upload-files/storage_manage_uploads.js b/snippets/storage-next/upload-files/storage_manage_uploads.js new file mode 100644 index 00000000..0eaf008a --- /dev/null +++ b/snippets/storage-next/upload-files/storage_manage_uploads.js @@ -0,0 +1,24 @@ +// This snippet file was generated by processing the source file: +// ./storage-next/upload-files.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START storage_manage_uploads_modular] +import { getStorage, ref, uploadBytesResumable } from "firebase/storage"; + +const storage = getStorage(); +const storageRef = ref(storage, 'images/mountains.jpg'); + +// Upload the file and metadata +const uploadTask = uploadBytesResumable(storageRef, file); + +// Pause the upload +uploadTask.pause(); + +// Resume the upload +uploadTask.resume(); + +// Cancel the upload +uploadTask.cancel(); +// [END storage_manage_uploads_modular] \ No newline at end of file diff --git a/snippets/storage-next/upload-files/storage_monitor_upload.js b/snippets/storage-next/upload-files/storage_monitor_upload.js new file mode 100644 index 00000000..cf035832 --- /dev/null +++ b/snippets/storage-next/upload-files/storage_monitor_upload.js @@ -0,0 +1,45 @@ +// This snippet file was generated by processing the source file: +// ./storage-next/upload-files.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START storage_monitor_upload_modular] +import { getStorage, ref, uploadBytesResumable, getDownloadURL } from "firebase/storage"; + +const storage = getStorage(); +const storageRef = ref(storage, 'images/rivers.jpg'); + +const uploadTask = uploadBytesResumable(storageRef, file); + +// Register three observers: +// 1. 'state_changed' observer, called any time the state changes +// 2. Error observer, called on failure +// 3. Completion observer, called on successful completion +uploadTask.on('state_changed', + (snapshot) => { + // Observe state change events such as progress, pause, and resume + // Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded + const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100; + console.log('Upload is ' + progress + '% done'); + switch (snapshot.state) { + case 'paused': + console.log('Upload is paused'); + break; + case 'running': + console.log('Upload is running'); + break; + } + }, + (error) => { + // Handle unsuccessful uploads + }, + () => { + // Handle successful uploads on complete + // For instance, get the download URL: https://firebasestorage.googleapis.com/... + getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => { + console.log('File available at', downloadURL); + }); + } +); +// [END storage_monitor_upload_modular] \ No newline at end of file diff --git a/snippets/storage-next/upload-files/storage_upload_blob.js b/snippets/storage-next/upload-files/storage_upload_blob.js new file mode 100644 index 00000000..5ad22f8e --- /dev/null +++ b/snippets/storage-next/upload-files/storage_upload_blob.js @@ -0,0 +1,17 @@ +// This snippet file was generated by processing the source file: +// ./storage-next/upload-files.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START storage_upload_blob_modular] +import { getStorage, ref, uploadBytes } from "firebase/storage"; + +const storage = getStorage(); +const storageRef = ref(storage, 'some-child'); + +// 'file' comes from the Blob or File API +uploadBytes(storageRef, file).then((snapshot) => { + console.log('Uploaded a blob or file!'); +}); +// [END storage_upload_blob_modular] \ No newline at end of file diff --git a/snippets/storage-next/upload-files/storage_upload_bytes.js b/snippets/storage-next/upload-files/storage_upload_bytes.js new file mode 100644 index 00000000..82acac25 --- /dev/null +++ b/snippets/storage-next/upload-files/storage_upload_bytes.js @@ -0,0 +1,17 @@ +// This snippet file was generated by processing the source file: +// ./storage-next/upload-files.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START storage_upload_bytes_modular] +import { getStorage, ref, uploadBytes } from "firebase/storage"; + +const storage = getStorage(); +const storageRef = ref(storage, 'some-child'); + +const bytes = new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21]); +uploadBytes(storageRef, bytes).then((snapshot) => { + console.log('Uploaded an array!'); +}); +// [END storage_upload_bytes_modular] \ No newline at end of file diff --git a/snippets/storage-next/upload-files/storage_upload_handle_error.js b/snippets/storage-next/upload-files/storage_upload_handle_error.js new file mode 100644 index 00000000..bf50c6b5 --- /dev/null +++ b/snippets/storage-next/upload-files/storage_upload_handle_error.js @@ -0,0 +1,62 @@ +// This snippet file was generated by processing the source file: +// ./storage-next/upload-files.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START storage_upload_handle_error_modular] +import { getStorage, ref, uploadBytesResumable, getDownloadURL } from "firebase/storage"; + +const storage = getStorage(); + +// Create the file metadata +/** @type {any} */ +const metadata = { + contentType: 'image/jpeg' +}; + +// Upload file and metadata to the object 'images/mountains.jpg' +const storageRef = ref(storage, 'images/' + file.name); +const uploadTask = uploadBytesResumable(storageRef, file, metadata); + +// Listen for state changes, errors, and completion of the upload. +uploadTask.on('state_changed', + (snapshot) => { + // Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded + const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100; + console.log('Upload is ' + progress + '% done'); + switch (snapshot.state) { + case 'paused': + console.log('Upload is paused'); + break; + case 'running': + console.log('Upload is running'); + break; + } + }, + (error) => { + // A full list of error codes is available at + // https://firebase.google.com/docs/storage/web/handle-errors + switch (error.code) { + case 'storage/unauthorized': + // User doesn't have permission to access the object + break; + case 'storage/canceled': + // User canceled the upload + break; + + // ... + + case 'storage/unknown': + // Unknown error occurred, inspect error.serverResponse + break; + } + }, + () => { + // Upload completed successfully, now we can get the download URL + getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => { + console.log('File available at', downloadURL); + }); + } +); +// [END storage_upload_handle_error_modular] \ No newline at end of file diff --git a/snippets/storage-next/upload-files/storage_upload_metadata.js b/snippets/storage-next/upload-files/storage_upload_metadata.js new file mode 100644 index 00000000..3dd304b4 --- /dev/null +++ b/snippets/storage-next/upload-files/storage_upload_metadata.js @@ -0,0 +1,21 @@ +// This snippet file was generated by processing the source file: +// ./storage-next/upload-files.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START storage_upload_metadata_modular] +import { getStorage, ref, uploadBytes } from "firebase/storage"; + +const storage = getStorage(); +const storageRef = ref(storage, 'images/mountains.jpg'); + +// Create file metadata including the content type +/** @type {any} */ +const metadata = { + contentType: 'image/jpeg', +}; + +// Upload the file and metadata +const uploadTask = uploadBytes(storageRef, file, metadata); +// [END storage_upload_metadata_modular] \ No newline at end of file diff --git a/snippets/storage-next/upload-files/storage_upload_ref.js b/snippets/storage-next/upload-files/storage_upload_ref.js new file mode 100644 index 00000000..ef565bf2 --- /dev/null +++ b/snippets/storage-next/upload-files/storage_upload_ref.js @@ -0,0 +1,22 @@ +// This snippet file was generated by processing the source file: +// ./storage-next/upload-files.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START storage_upload_ref_modular] +import { getStorage, ref } from "firebase/storage"; + +// Create a root reference +const storage = getStorage(); + +// Create a reference to 'mountains.jpg' +const mountainsRef = ref(storage, 'mountains.jpg'); + +// Create a reference to 'images/mountains.jpg' +const mountainImagesRef = ref(storage, 'images/mountains.jpg'); + +// While the file names are the same, the references point to different files +mountainsRef.name === mountainImagesRef.name; // true +mountainsRef.fullPath === mountainImagesRef.fullPath; // false +// [END storage_upload_ref_modular] \ No newline at end of file diff --git a/snippets/storage-next/upload-files/storage_upload_string.js b/snippets/storage-next/upload-files/storage_upload_string.js new file mode 100644 index 00000000..6450a566 --- /dev/null +++ b/snippets/storage-next/upload-files/storage_upload_string.js @@ -0,0 +1,36 @@ +// This snippet file was generated by processing the source file: +// ./storage-next/upload-files.js +// +// To update the snippets in this file, edit the source and then run +// 'npm run snippets'. + +// [START storage_upload_string_modular] +import { getStorage, ref, uploadString } from "firebase/storage"; + +const storage = getStorage(); +const storageRef = ref(storage, 'some-child'); + +// Raw string is the default if no format is provided +const message = 'This is my message.'; +uploadString(storageRef, message).then((snapshot) => { + console.log('Uploaded a raw string!'); +}); + +// Base64 formatted string +const message2 = '5b6p5Y+344GX44G+44GX44Gf77yB44GK44KB44Gn44Go44GG77yB'; +uploadString(storageRef, message2, 'base64').then((snapshot) => { + console.log('Uploaded a base64 string!'); +}); + +// Base64url formatted string +const message3 = '5b6p5Y-344GX44G-44GX44Gf77yB44GK44KB44Gn44Go44GG77yB'; +uploadString(storageRef, message3, 'base64url').then((snapshot) => { + console.log('Uploaded a base64url string!'); +}); + +// Data URL string +const message4 = 'data:text/plain;base64,5b6p5Y+344GX44G+44GX44Gf77yB44GK44KB44Gn44Go44GG77yB'; +uploadString(storageRef, message4, 'data_url').then((snapshot) => { + console.log('Uploaded a data_url string!'); +}); +// [END storage_upload_string_modular] \ No newline at end of file diff --git a/storage-next/create-reference.js b/storage-next/create-reference.js new file mode 100644 index 00000000..47a6e227 --- /dev/null +++ b/storage-next/create-reference.js @@ -0,0 +1,112 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +function createRef() { + // [START storage_create_ref] + const { getStorage, ref } = require("firebase/storage"); + + // Get a reference to the storage service, which is used to create references in your storage bucket + const storage = getStorage(); + + // Create a storage reference from our storage service + const storageRef = ref(storage); + // [END storage_create_ref] +} + +function createRefChild() { + // [START storage_create_ref_child] + const { getStorage, ref } = require("firebase/storage"); + + const storage = getStorage(); + + // Create a child reference + const imagesRef = ref(storage, 'images'); + // imagesRef now points to 'images' + + // Child references can also take paths delimited by '/' + const spaceRef = ref(storage, 'images/space.jpg'); + // spaceRef now points to "images/space.jpg" + // imagesRef still points to "images" + // [END storage_create_ref_child] +} + +function navigateRef() { + // [START storage_navigate_ref] + const { getStorage, ref } = require("firebase/storage"); + + const storage = getStorage(); + const spaceRef = ref(storage, 'images/space.jpg'); + + // Parent allows us to move to the parent of a reference + const imagesRef = spaceRef.parent; + // imagesRef now points to 'images' + + // Root allows us to move all the way back to the top of our bucket + const rootRef = spaceRef.root; + // rootRef now points to the root + // [END storage_navigate_ref] +} + +function navigateRefChain() { + // [START storage_navigate_ref_chain] + const { getStorage, ref } = require("firebase/storage"); + + const storage = getStorage(); + const spaceRef = ref(storage, 'images/space.jpg'); + + // References can be chained together multiple times + const earthRef = ref(spaceRef.parent, 'earth.jpg'); + // earthRef points to 'images/earth.jpg' + + // nullRef is null, since the parent of root is null + const nullRef = spaceRef.root.parent; + // [END storage_navigate_ref_chain] +} + +function refProperties() { + // [START storage_ref_properties] + const { getStorage, ref } = require("firebase/storage"); + + const storage = getStorage(); + const spaceRef = ref(storage, 'images/space.jpg'); + + // Reference's path is: 'images/space.jpg' + // This is analogous to a file path on disk + spaceRef.fullPath; + + // Reference's name is the last segment of the full path: 'space.jpg' + // This is analogous to the file name + spaceRef.name; + + // Reference's bucket is the name of the storage bucket where files are stored + spaceRef.bucket; + // [END storage_ref_properties] +} + +function refFullExample() { + // [START storage_ref_full_example] + const { getStorage, ref } = require("firebase/storage"); + + const storage = getStorage(); + + // Points to the root reference + const storageRef = ref(storage); + + // Points to 'images' + const imagesRef = ref(storageRef, 'images'); + + // Points to 'images/space.jpg' + // Note that you can use variables to create child values + const fileName = 'space.jpg'; + const spaceRef = ref(imagesRef, fileName); + + // File path is 'images/space.jpg' + const path = spaceRef.fullPath; + + // File name is 'space.jpg' + const name = spaceRef.name; + + // Points to 'images' + const imagesRefAgain = spaceRef.parent; + // [END storage_ref_full_example] +} diff --git a/storage-next/delete-files.js b/storage-next/delete-files.js new file mode 100644 index 00000000..767a26f2 --- /dev/null +++ b/storage-next/delete-files.js @@ -0,0 +1,20 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +function deleteFile() { + // [START storage_delete_file] + const { getStorage, ref, deleteObject } = require("firebase/storage"); + + const storage = getStorage(); + + // Create a reference to the file to delete + const desertRef = ref(storage, 'images/desert.jpg'); + + // Delete the file + deleteObject(desertRef).then(() => { + // File deleted successfully + }).catch((error) => { + // Uh-oh, an error occurred! + }); + // [END storage_delete_file] +} diff --git a/storage-next/download-files.js b/storage-next/download-files.js new file mode 100644 index 00000000..6e94e63a --- /dev/null +++ b/storage-next/download-files.js @@ -0,0 +1,84 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +function downloadCreateRef() { + // [START storage_download_create_ref] + const { getStorage, ref } = require("firebase/storage"); + + // Create a reference with an initial file path and name + const storage = getStorage(); + const pathReference = ref(storage, 'images/stars.jpg'); + + // Create a reference from a Google Cloud Storage URI + const gsReference = ref(storage, 'gs://bucket/images/stars.jpg'); + + // Create a reference from an HTTPS URL + // Note that in the URL, characters are URL escaped! + const httpsReference = ref(storage, 'https://firebasestorage.googleapis.com/b/bucket/o/images%20stars.jpg'); + // [END storage_download_create_ref] +} + +function downloadViaUrl() { + // [START storage_download_via_url] + const { getStorage, ref, getDownloadURL } = require("firebase/storage"); + + const storage = getStorage(); + getDownloadURL(ref(storage, 'images/stars.jpg')) + .then((url) => { + // `url` is the download URL for 'images/stars.jpg' + + // This can be downloaded directly: + const xhr = new XMLHttpRequest(); + xhr.responseType = 'blob'; + xhr.onload = (event) => { + const blob = xhr.response; + }; + xhr.open('GET', url); + xhr.send(); + + // Or inserted into an element + const img = document.getElementById('myimg'); + img.setAttribute('src', url); + }) + .catch((error) => { + // Handle any errors + }); + // [END storage_download_via_url] +} + +function downloadFullExample() { + // [START storage_download_full_example] + const { getStorage, ref, getDownloadURL } = require("firebase/storage"); + + // Create a reference to the file we want to download + const storage = getStorage(); + const starsRef = ref(storage, 'images/stars.jpg'); + + // Get the download URL + getDownloadURL(starsRef) + .then((url) => { + // Insert url into an tag to "download" + }) + .catch((error) => { + // A full list of error codes is available at + // https://firebase.google.com/docs/storage/web/handle-errors + switch (error.code) { + case 'storage/object-not-found': + // File doesn't exist + break; + case 'storage/unauthorized': + // User doesn't have permission to access the object + break; + case 'storage/canceled': + // User canceled the upload + break; + + // ... + + case 'storage/unknown': + // Unknown error occurred, inspect the server response + break; + } + }); + // [END storage_download_full_example] +} diff --git a/storage-next/emulator-suite.js b/storage-next/emulator-suite.js new file mode 100644 index 00000000..78891a4f --- /dev/null +++ b/storage-next/emulator-suite.js @@ -0,0 +1,14 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +function onDocumentReady() { + // [START storage_emulator_connect] + const { getStorage, connectStorageEmulator } = require("firebase/storage"); + + const storage = getStorage(); + if (location.hostname === "localhost") { + // Point to the Storage emulator running on localhost. + connectStorageEmulator(storage, "127.0.0.1", 9199); + } + // [END storage_emulator_connect] +} diff --git a/storage-next/file-metadata.js b/storage-next/file-metadata.js new file mode 100644 index 00000000..c45827cd --- /dev/null +++ b/storage-next/file-metadata.js @@ -0,0 +1,78 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +function getMetadata() { + // [START storage_get_metadata] + const { getStorage, ref, getMetadata } = require("firebase/storage"); + + // Create a reference to the file whose metadata we want to retrieve + const storage = getStorage(); + const forestRef = ref(storage, 'images/forest.jpg'); + + // Get metadata properties + getMetadata(forestRef) + .then((metadata) => { + // Metadata now contains the metadata for 'images/forest.jpg' + }) + .catch((error) => { + // Uh-oh, an error occurred! + }); + // [END storage_get_metadata] +} + +function updateMetadata() { + // [START storage_update_metadata] + const { getStorage, ref, updateMetadata } = require("firebase/storage"); + + // Create a reference to the file whose metadata we want to change + const storage = getStorage(); + const forestRef = ref(storage, 'images/forest.jpg'); + + // Create file metadata to update + const newMetadata = { + cacheControl: 'public,max-age=300', + contentType: 'image/jpeg' + }; + + // Update metadata properties + updateMetadata(forestRef, newMetadata) + .then((metadata) => { + // Updated metadata for 'images/forest.jpg' is returned in the Promise + }).catch((error) => { + // Uh-oh, an error occurred! + }); + // [END storage_update_metadata] +} + +function deleteMetadata() { + // [START storage_delete_metadata] + const { getStorage, ref, updateMetadata } = require("firebase/storage"); + + const storage = getStorage(); + const forestRef = ref(storage, 'images/forest.jpg'); + + // Create file metadata with property to delete + const deleteMetadata = { + contentType: null + }; + + // Delete the metadata property + updateMetadata(forestRef, deleteMetadata) + .then((metadata) => { + // metadata.contentType should be null + }).catch((error) => { + // Uh-oh, an error occurred! + }); + // [END storage_delete_metadata] +} + +function customMetadata() { + // [START storage_custom_metadata] + const metadata = { + customMetadata: { + 'location': 'Yosemite, CA, USA', + 'activity': 'Hiking' + } + }; + // [END storage_custom_metadata] +} diff --git a/storage-next/index.js b/storage-next/index.js new file mode 100644 index 00000000..15b07e5f --- /dev/null +++ b/storage-next/index.js @@ -0,0 +1,86 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +function initialize() { + // [START storage_initialize] + const { initializeApp } = require("firebase/app"); + const { getStorage } = require("firebase/storage"); + + // Set the configuration for your app + // TODO: Replace with your app's config object + const firebaseConfig = { + apiKey: '', + authDomain: '', + databaseURL: '', + storageBucket: '' + }; + const firebaseApp = initializeApp(firebaseConfig); + + // Get a reference to the storage service, which is used to create references in your storage bucket + const storage = getStorage(firebaseApp); + // [END storage_initialize] +} + +function multipleBuckets() { + // [START storage_multiple_buckets] + const { getApp } = require("firebase/app"); + const { getStorage } = require("firebase/storage"); + + // Get a non-default Storage bucket + const firebaseApp = getApp(); + const storage = getStorage(firebaseApp, "gs://my-custom-bucket"); + // [END storage_multiple_buckets] +} + +function storageCustomApp() { + const { initializeApp } = require("firebase/app"); + + const customApp = initializeApp({ + // ... custom stuff + }); + + // [START storage_custom_app] + const { getStorage } = require("firebase/storage"); + + // Get the default bucket from a custom firebase.app.App + const storage1 = getStorage(customApp); + + // Get a non-default bucket from a custom firebase.app.App + const storage2 = getStorage(customApp, "gs://my-custom-bucket"); + // [END storage_custom_app] +} + +/** + * @param {File} file + */ +function storageOnComplete(file) { + // The file param would be a File object from a file selection event in the browser. + // See: + // - https://developer.mozilla.org/en-US/docs/Web/API/File/Using_files_from_web_applications + // - https://developer.mozilla.org/en-US/docs/Web/API/File + + /** @type {any} */ + const metadata = { + 'contentType': file.type + }; + + // [START storage_on_complete] + const { getStorage, ref, uploadBytesResumable, getDownloadURL } = require("firebase/storage"); + + const storage = getStorage(); + const imageRef = ref(storage, 'images/' + file.name); + uploadBytesResumable(imageRef, file, metadata) + .then((snapshot) => { + console.log('Uploaded', snapshot.totalBytes, 'bytes.'); + console.log('File metadata:', snapshot.metadata); + // Let's get a download URL for the file. + getDownloadURL(snapshot.ref).then((url) => { + console.log('File available at', url); + // ... + }); + }).catch((error) => { + console.error('Upload failed', error); + // ... + }); + // [END storage_on_complete] +} diff --git a/storage-next/list-files.js b/storage-next/list-files.js new file mode 100644 index 00000000..493972d2 --- /dev/null +++ b/storage-next/list-files.js @@ -0,0 +1,56 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +function listAll() { + // [START storage_list_all] + const { getStorage, ref, listAll } = require("firebase/storage"); + + const storage = getStorage(); + + // Create a reference under which you want to list + const listRef = ref(storage, 'files/uid'); + + // Find all the prefixes and items. + listAll(listRef) + .then((res) => { + res.prefixes.forEach((folderRef) => { + // All the prefixes under listRef. + // You may call listAll() recursively on them. + }); + res.items.forEach((itemRef) => { + // All the items under listRef. + }); + }).catch((error) => { + // Uh-oh, an error occurred! + }); + // [END storage_list_all] +} + +function listPaginate() { + // [START storage_list_paginate] + const { getStorage, ref, list } = require("firebase/storage"); + + async function pageTokenExample(){ + // Create a reference under which you want to list + const storage = getStorage(); + const listRef = ref(storage, 'files/uid'); + + // Fetch the first page of 100. + const firstPage = await list(listRef, { maxResults: 100 }); + + // Use the result. + // processItems(firstPage.items) + // processPrefixes(firstPage.prefixes) + + // Fetch the second page if there are more elements. + if (firstPage.nextPageToken) { + const secondPage = await list(listRef, { + maxResults: 100, + pageToken: firstPage.nextPageToken, + }); + // processItems(secondPage.items) + // processPrefixes(secondPage.prefixes) + } + } + // [END storage_list_paginate] +} diff --git a/storage-next/package.json b/storage-next/package.json new file mode 100644 index 00000000..b1c449ab --- /dev/null +++ b/storage-next/package.json @@ -0,0 +1,11 @@ +{ + "name": "storage-next", + "version": "1.0.0", + "scripts": { + "compile": "cp ../tsconfig.json.template ./tsconfig.json && tsc" + }, + "license": "Apache-2.0", + "dependencies": { + "firebase": "^10.0.0" + } +} diff --git a/storage-next/upload-files.js b/storage-next/upload-files.js new file mode 100644 index 00000000..98b93225 --- /dev/null +++ b/storage-next/upload-files.js @@ -0,0 +1,237 @@ +// [SNIPPET_REGISTRY disabled] +// [SNIPPETS_SEPARATION enabled] + +function uploadRef() { + // [START storage_upload_ref] + const { getStorage, ref } = require("firebase/storage"); + + // Create a root reference + const storage = getStorage(); + + // Create a reference to 'mountains.jpg' + const mountainsRef = ref(storage, 'mountains.jpg'); + + // Create a reference to 'images/mountains.jpg' + const mountainImagesRef = ref(storage, 'images/mountains.jpg'); + + // While the file names are the same, the references point to different files + mountainsRef.name === mountainImagesRef.name; // true + mountainsRef.fullPath === mountainImagesRef.fullPath; // false + // [END storage_upload_ref] +} + +/** + * @param {File} file + */ +function uploadBlob(file) { + // [START storage_upload_blob] + const { getStorage, ref, uploadBytes } = require("firebase/storage"); + + const storage = getStorage(); + const storageRef = ref(storage, 'some-child'); + + // 'file' comes from the Blob or File API + uploadBytes(storageRef, file).then((snapshot) => { + console.log('Uploaded a blob or file!'); + }); + // [END storage_upload_blob] +} + +function uploadBytes() { + // [START storage_upload_bytes] + const { getStorage, ref, uploadBytes } = require("firebase/storage"); + + const storage = getStorage(); + const storageRef = ref(storage, 'some-child'); + + const bytes = new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21]); + uploadBytes(storageRef, bytes).then((snapshot) => { + console.log('Uploaded an array!'); + }); + // [END storage_upload_bytes] +} + +function uploadString() { + // [START storage_upload_string] + const { getStorage, ref, uploadString } = require("firebase/storage"); + + const storage = getStorage(); + const storageRef = ref(storage, 'some-child'); + + // Raw string is the default if no format is provided + const message = 'This is my message.'; + uploadString(storageRef, message).then((snapshot) => { + console.log('Uploaded a raw string!'); + }); + + // Base64 formatted string + const message2 = '5b6p5Y+344GX44G+44GX44Gf77yB44GK44KB44Gn44Go44GG77yB'; + uploadString(storageRef, message2, 'base64').then((snapshot) => { + console.log('Uploaded a base64 string!'); + }); + + // Base64url formatted string + const message3 = '5b6p5Y-344GX44G-44GX44Gf77yB44GK44KB44Gn44Go44GG77yB'; + uploadString(storageRef, message3, 'base64url').then((snapshot) => { + console.log('Uploaded a base64url string!'); + }); + + // Data URL string + const message4 = 'data:text/plain;base64,5b6p5Y+344GX44G+44GX44Gf77yB44GK44KB44Gn44Go44GG77yB'; + uploadString(storageRef, message4, 'data_url').then((snapshot) => { + console.log('Uploaded a data_url string!'); + }); + // [END storage_upload_string] +} + +/** + * @param {File} file + */ +function uploadMetadata(file) { + // [START storage_upload_metadata] + const { getStorage, ref, uploadBytes } = require("firebase/storage"); + + const storage = getStorage(); + const storageRef = ref(storage, 'images/mountains.jpg'); + + // Create file metadata including the content type + /** @type {any} */ + const metadata = { + contentType: 'image/jpeg', + }; + + // Upload the file and metadata + const uploadTask = uploadBytes(storageRef, file, metadata); + // [END storage_upload_metadata] +} + +/** + * @param {File} file + */ +function manageUploads(file) { + // [START storage_manage_uploads] + const { getStorage, ref, uploadBytesResumable } = require("firebase/storage"); + + const storage = getStorage(); + const storageRef = ref(storage, 'images/mountains.jpg'); + + // Upload the file and metadata + const uploadTask = uploadBytesResumable(storageRef, file); + + // Pause the upload + uploadTask.pause(); + + // Resume the upload + uploadTask.resume(); + + // Cancel the upload + uploadTask.cancel(); + // [END storage_manage_uploads] +} + +/** + * @param {File} file + */ +function monitorUpload(file) { + // [START storage_monitor_upload] + const { getStorage, ref, uploadBytesResumable, getDownloadURL } = require("firebase/storage"); + + const storage = getStorage(); + const storageRef = ref(storage, 'images/rivers.jpg'); + + const uploadTask = uploadBytesResumable(storageRef, file); + + // Register three observers: + // 1. 'state_changed' observer, called any time the state changes + // 2. Error observer, called on failure + // 3. Completion observer, called on successful completion + uploadTask.on('state_changed', + (snapshot) => { + // Observe state change events such as progress, pause, and resume + // Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded + const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100; + console.log('Upload is ' + progress + '% done'); + switch (snapshot.state) { + case 'paused': + console.log('Upload is paused'); + break; + case 'running': + console.log('Upload is running'); + break; + } + }, + (error) => { + // Handle unsuccessful uploads + }, + () => { + // Handle successful uploads on complete + // For instance, get the download URL: https://firebasestorage.googleapis.com/... + getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => { + console.log('File available at', downloadURL); + }); + } + ); + // [END storage_monitor_upload] +} + +/** + * @param {File} file + */ +function uploadHandleError(file) { + // [START storage_upload_handle_error] + const { getStorage, ref, uploadBytesResumable, getDownloadURL } = require("firebase/storage"); + + const storage = getStorage(); + + // Create the file metadata + /** @type {any} */ + const metadata = { + contentType: 'image/jpeg' + }; + + // Upload file and metadata to the object 'images/mountains.jpg' + const storageRef = ref(storage, 'images/' + file.name); + const uploadTask = uploadBytesResumable(storageRef, file, metadata); + + // Listen for state changes, errors, and completion of the upload. + uploadTask.on('state_changed', + (snapshot) => { + // Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded + const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100; + console.log('Upload is ' + progress + '% done'); + switch (snapshot.state) { + case 'paused': + console.log('Upload is paused'); + break; + case 'running': + console.log('Upload is running'); + break; + } + }, + (error) => { + // A full list of error codes is available at + // https://firebase.google.com/docs/storage/web/handle-errors + switch (error.code) { + case 'storage/unauthorized': + // User doesn't have permission to access the object + break; + case 'storage/canceled': + // User canceled the upload + break; + + // ... + + case 'storage/unknown': + // Unknown error occurred, inspect error.serverResponse + break; + } + }, + () => { + // Upload completed successfully, now we can get the download URL + getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => { + console.log('File available at', downloadURL); + }); + } + ); + // [END storage_upload_handle_error] +} diff --git a/storage/create-reference.js b/storage/create-reference.js new file mode 100644 index 00000000..09794832 --- /dev/null +++ b/storage/create-reference.js @@ -0,0 +1,95 @@ +import firebase from "firebase/app"; +import "firebase/storage"; + +function createRef() { + // [START storage_create_ref] + // Get a reference to the storage service, which is used to create references in your storage bucket + var storage = firebase.storage(); + + // Create a storage reference from our storage service + var storageRef = storage.ref(); + // [END storage_create_ref] +} + +function createRefChild() { + const storageRef = firebase.storage().ref(); + + // [START storage_create_ref_child] + // Create a child reference + var imagesRef = storageRef.child('images'); + // imagesRef now points to 'images' + + // Child references can also take paths delimited by '/' + var spaceRef = storageRef.child('images/space.jpg'); + // spaceRef now points to "images/space.jpg" + // imagesRef still points to "images" + // [END storage_create_ref_child] +} + +function navigateRef() { + const spaceRef = firebase.storage().ref().child('images/space.jpg'); + + // [START storage_navigate_ref] + // Parent allows us to move to the parent of a reference + var imagesRef = spaceRef.parent; + // imagesRef now points to 'images' + + // Root allows us to move all the way back to the top of our bucket + var rootRef = spaceRef.root; + // rootRef now points to the root + // [END storage_navigate_ref] +} + +function navigateRefChain() { + const spaceRef = firebase.storage().ref().child('images/space.jpg'); + + // [START storage_navigate_ref_chain] + // References can be chained together multiple times + var earthRef = spaceRef.parent.child('earth.jpg'); + // earthRef points to 'images/earth.jpg' + + // nullRef is null, since the parent of root is null + var nullRef = spaceRef.root.parent; + // [END storage_navigate_ref_chain] +} + +function refProperties() { + const spaceRef = firebase.storage().ref().child('images/space.jpg'); + + // [START storage_ref_properties] + // Reference's path is: 'images/space.jpg' + // This is analogous to a file path on disk + spaceRef.fullPath; + + // Reference's name is the last segment of the full path: 'space.jpg' + // This is analogous to the file name + spaceRef.name; + + // Reference's bucket is the name of the storage bucket where files are stored + spaceRef.bucket; + // [END storage_ref_properties] +} + +function refFullExample() { + // [START storage_ref_full_example] + // Points to the root reference + var storageRef = firebase.storage().ref(); + + // Points to 'images' + var imagesRef = storageRef.child('images'); + + // Points to 'images/space.jpg' + // Note that you can use variables to create child values + var fileName = 'space.jpg'; + var spaceRef = imagesRef.child(fileName); + + // File path is 'images/space.jpg' + var path = spaceRef.fullPath; + + // File name is 'space.jpg' + var name = spaceRef.name; + + // Points to 'images' + var imagesRef = spaceRef.parent; + // [END storage_ref_full_example] +} diff --git a/storage/delete-files.js b/storage/delete-files.js new file mode 100644 index 00000000..73b84904 --- /dev/null +++ b/storage/delete-files.js @@ -0,0 +1,18 @@ +import firebase from "firebase/app"; +import "firebase/storage"; + +function deleteFile() { + const storageRef = firebase.storage().ref(); + + // [START storage_delete_file] + // Create a reference to the file to delete + var desertRef = storageRef.child('images/desert.jpg'); + + // Delete the file + desertRef.delete().then(() => { + // File deleted successfully + }).catch((error) => { + // Uh-oh, an error occurred! + }); + // [END storage_delete_file] +} diff --git a/storage/download-files.js b/storage/download-files.js new file mode 100644 index 00000000..cb2af375 --- /dev/null +++ b/storage/download-files.js @@ -0,0 +1,80 @@ +import firebase from "firebase/app"; +import "firebase/storage"; + +function downloadCreateRef() { + // [START storage_download_create_ref] + // Create a reference with an initial file path and name + var storage = firebase.storage(); + var pathReference = storage.ref('images/stars.jpg'); + + // Create a reference from a Google Cloud Storage URI + var gsReference = storage.refFromURL('gs://bucket/images/stars.jpg'); + + // Create a reference from an HTTPS URL + // Note that in the URL, characters are URL escaped! + var httpsReference = storage.refFromURL('https://firebasestorage.googleapis.com/b/bucket/o/images%20stars.jpg'); + // [END storage_download_create_ref] +} + +function downloadViaUrl() { + const storageRef = firebase.storage().ref(); + + // [START storage_download_via_url] + storageRef.child('images/stars.jpg').getDownloadURL() + .then((url) => { + // `url` is the download URL for 'images/stars.jpg' + + // This can be downloaded directly: + var xhr = new XMLHttpRequest(); + xhr.responseType = 'blob'; + xhr.onload = (event) => { + var blob = xhr.response; + }; + xhr.open('GET', url); + xhr.send(); + + // Or inserted into an element + var img = document.getElementById('myimg'); + img.setAttribute('src', url); + }) + .catch((error) => { + // Handle any errors + }); + // [END storage_download_via_url] +} + +function downloadFullExample() { + const storageRef = firebase.storage().ref(); + + // [START storage_download_full_example] + // Create a reference to the file we want to download + var starsRef = storageRef.child('images/stars.jpg'); + + // Get the download URL + starsRef.getDownloadURL() + .then((url) => { + // Insert url into an tag to "download" + }) + .catch((error) => { + // A full list of error codes is available at + // https://firebase.google.com/docs/storage/web/handle-errors + switch (error.code) { + case 'storage/object-not-found': + // File doesn't exist + break; + case 'storage/unauthorized': + // User doesn't have permission to access the object + break; + case 'storage/canceled': + // User canceled the upload + break; + + // ... + + case 'storage/unknown': + // Unknown error occurred, inspect the server response + break; + } + }); + // [END storage_download_full_example] +} diff --git a/storage/emulator-suite.js b/storage/emulator-suite.js new file mode 100644 index 00000000..be1f2dd4 --- /dev/null +++ b/storage/emulator-suite.js @@ -0,0 +1,16 @@ +// These samples are intended for Web so this import would normally be +// done in HTML however using modules here is more convenient for +// ensuring sample correctness offline. +import firebase from "firebase/app"; +import "firebase/storage"; + +function onDocumentReady() { + // [START storage_emulator_connect] + var storage = firebase.storage(); + if (location.hostname === "localhost") { + // Point to the Storage emulator running on localhost. + storage.useEmulator("127.0.0.1", 9199); + } + // [END storage_emulator_connect] +} + diff --git a/storage/file-metadata.js b/storage/file-metadata.js new file mode 100644 index 00000000..cef42364 --- /dev/null +++ b/storage/file-metadata.js @@ -0,0 +1,75 @@ +import firebase from "firebase/app"; +import "firebase/storage"; + +function getMetadata() { + const storageRef = firebase.storage().ref(); + + // [START storage_get_metadata] + // Create a reference to the file whose metadata we want to retrieve + var forestRef = storageRef.child('images/forest.jpg'); + + // Get metadata properties + forestRef.getMetadata() + .then((metadata) => { + // Metadata now contains the metadata for 'images/forest.jpg' + }) + .catch((error) => { + // Uh-oh, an error occurred! + }); + // [END storage_get_metadata] +} + +function updateMetadata() { + const storageRef = firebase.storage().ref(); + + // [START storage_update_metadata] + // Create a reference to the file whose metadata we want to change + var forestRef = storageRef.child('images/forest.jpg'); + + // Create file metadata to update + var newMetadata = { + cacheControl: 'public,max-age=300', + contentType: 'image/jpeg' + }; + + // Update metadata properties + forestRef.updateMetadata(newMetadata) + .then((metadata) => { + // Updated metadata for 'images/forest.jpg' is returned in the Promise + }).catch((error) => { + // Uh-oh, an error occurred! + }); + // [END storage_update_metadata] +} + +function deleteMetadata() { + const storageRef = firebase.storage().ref(); + const forestRef = storageRef.child('images/forest.jpg'); + + // [START storage_delete_metadata] + + // Create file metadata with property to delete + var deleteMetadata = { + contentType: null + }; + + // Delete the metadata property + forestRef.updateMetadata(deleteMetadata) + .then((metadata) => { + // metadata.contentType should be null + }).catch((error) => { + // Uh-oh, an error occurred! + }); + // [END storage_delete_metadata] +} + +function customMetadata() { + // [START storage_custom_metadata] + var metadata = { + customMetadata: { + 'location': 'Yosemite, CA, USA', + 'activity': 'Hiking' + } + }; + // [END storage_custom_metadata] +} diff --git a/storage/index.js b/storage/index.js new file mode 100644 index 00000000..7a7db0df --- /dev/null +++ b/storage/index.js @@ -0,0 +1,71 @@ +import firebase from "firebase/app"; +import "firebase/storage"; + +function initialize() { + // [START storage_initialize] + // Set the configuration for your app + // TODO: Replace with your app's config object + var firebaseConfig = { + apiKey: '', + authDomain: '', + databaseURL: '', + storageBucket: '' + }; + firebase.initializeApp(firebaseConfig); + + // Get a reference to the storage service, which is used to create references in your storage bucket + var storage = firebase.storage(); + // [END storage_initialize] +} + +function multipleBuckets() { + // [START storage_multiple_buckets] + // Get a non-default Storage bucket + var storage = firebase.app().storage("gs://my-custom-bucket"); + // [END storage_multiple_buckets] +} + +function storageCustomApp() { + const customApp = firebase.initializeApp({ + // ... custom stuff + }); + + // [START storage_custom_app] + // Get the default bucket from a custom firebase.app.App + var storage = customApp.storage(); + + // Get a non-default bucket from a custom firebase.app.App + var storage = customApp.storage("gs://my-custom-bucket"); + // [END storage_custom_app] +} + +/** + * @param {File} file + */ +function storageOnComplete(file) { + // The file param would be a File object from a file selection event in the browser. + // See: + // - https://developer.mozilla.org/en-US/docs/Web/API/File/Using_files_from_web_applications + // - https://developer.mozilla.org/en-US/docs/Web/API/File + + const metadata = { + 'contentType': file.type + }; + + // [START storage_on_complete] + const storageRef = firebase.storage().ref(); + storageRef.child('images/' + file.name).put(file, metadata) + .then((snapshot) => { + console.log('Uploaded', snapshot.totalBytes, 'bytes.'); + console.log('File metadata:', snapshot.metadata); + // Let's get a download URL for the file. + snapshot.ref.getDownloadURL().then((url) => { + console.log('File available at', url); + // ... + }); + }).catch((error) => { + console.error('Upload failed', error); + // ... + }); + // [END storage_on_complete] +} diff --git a/storage/list-files.js b/storage/list-files.js new file mode 100644 index 00000000..ef248da7 --- /dev/null +++ b/storage/list-files.js @@ -0,0 +1,53 @@ +import firebase from "firebase/app"; +import "firebase/storage"; + +function listAll() { + const storageRef = firebase.storage().ref(); + + // [START storage_list_all] + // Create a reference under which you want to list + var listRef = storageRef.child('files/uid'); + + // Find all the prefixes and items. + listRef.listAll() + .then((res) => { + res.prefixes.forEach((folderRef) => { + // All the prefixes under listRef. + // You may call listAll() recursively on them. + }); + res.items.forEach((itemRef) => { + // All the items under listRef. + }); + }).catch((error) => { + // Uh-oh, an error occurred! + }); + // [END storage_list_all] +} + +function listPaginate() { + const storageRef = firebase.storage().ref(); + + // [START storage_list_paginate] + async function pageTokenExample(){ + // Create a reference under which you want to list + var listRef = storageRef.child('files/uid'); + + // Fetch the first page of 100. + var firstPage = await listRef.list({ maxResults: 100}); + + // Use the result. + // processItems(firstPage.items) + // processPrefixes(firstPage.prefixes) + + // Fetch the second page if there are more elements. + if (firstPage.nextPageToken) { + var secondPage = await listRef.list({ + maxResults: 100, + pageToken: firstPage.nextPageToken, + }); + // processItems(secondPage.items) + // processPrefixes(secondPage.prefixes) + } + } + // [END storage_list_paginate] +} diff --git a/storage/package.json b/storage/package.json new file mode 100644 index 00000000..e27166e7 --- /dev/null +++ b/storage/package.json @@ -0,0 +1,11 @@ +{ + "name": "storage", + "version": "1.0.0", + "scripts": { + "compile": "cp ../tsconfig.json.template ./tsconfig.json && tsc" + }, + "license": "Apache-2.0", + "dependencies": { + "firebase": "^8.10.0" + } +} diff --git a/storage/upload-files.js b/storage/upload-files.js new file mode 100644 index 00000000..2f889019 --- /dev/null +++ b/storage/upload-files.js @@ -0,0 +1,212 @@ +import firebase from "firebase/app"; +import "firebase/storage"; + +function uploadRef() { + // [START storage_upload_ref] + // Create a root reference + var storageRef = firebase.storage().ref(); + + // Create a reference to 'mountains.jpg' + var mountainsRef = storageRef.child('mountains.jpg'); + + // Create a reference to 'images/mountains.jpg' + var mountainImagesRef = storageRef.child('images/mountains.jpg'); + + // While the file names are the same, the references point to different files + mountainsRef.name === mountainImagesRef.name; // true + mountainsRef.fullPath === mountainImagesRef.fullPath; // false + // [END storage_upload_ref] +} + +/** + * @param {File} file + */ +function uploadBlob(file) { + const ref = firebase.storage().ref().child('some-child'); + + // [START storage_upload_blob] + // 'file' comes from the Blob or File API + ref.put(file).then((snapshot) => { + console.log('Uploaded a blob or file!'); + }); + // [END storage_upload_blob] +} + +function uploadBytes() { + const ref = firebase.storage().ref().child('some-child'); + + // [START storage_upload_bytes] + var bytes = new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21]); + ref.put(bytes).then((snapshot) => { + console.log('Uploaded an array!'); + }); + // [END storage_upload_bytes] +} + +function uploadString() { + const ref = firebase.storage().ref().child('some-child'); + + // [START storage_upload_string] + // Raw string is the default if no format is provided + var message = 'This is my message.'; + ref.putString(message).then((snapshot) => { + console.log('Uploaded a raw string!'); + }); + + // Base64 formatted string + var message = '5b6p5Y+344GX44G+44GX44Gf77yB44GK44KB44Gn44Go44GG77yB'; + ref.putString(message, 'base64').then((snapshot) => { + console.log('Uploaded a base64 string!'); + }); + + // Base64url formatted string + var message = '5b6p5Y-344GX44G-44GX44Gf77yB44GK44KB44Gn44Go44GG77yB'; + ref.putString(message, 'base64url').then((snapshot) => { + console.log('Uploaded a base64url string!'); + }); + + // Data URL string + var message = 'data:text/plain;base64,5b6p5Y+344GX44G+44GX44Gf77yB44GK44KB44Gn44Go44GG77yB'; + ref.putString(message, 'data_url').then((snapshot) => { + console.log('Uploaded a data_url string!'); + }); + // [END storage_upload_string] +} + +/** + * @param {File} file + */ +function uploadMetadata(file) { + const storageRef = firebase.storage().ref(); + + // [START storage_upload_metadata] + // Create file metadata including the content type + var metadata = { + contentType: 'image/jpeg', + }; + + // Upload the file and metadata + var uploadTask = storageRef.child('images/mountains.jpg').put(file, metadata); + // [END storage_upload_metadata] +} + +/** + * @param {File} file + */ +function manageUploads(file) { + const storageRef = firebase.storage().ref(); + + // [START storage_manage_uploads] + // Upload the file and metadata + var uploadTask = storageRef.child('images/mountains.jpg').put(file); + + // Pause the upload + uploadTask.pause(); + + // Resume the upload + uploadTask.resume(); + + // Cancel the upload + uploadTask.cancel(); + // [END storage_manage_uploads] +} + +/** + * @param {File} file + */ +function monitorUpload(file) { + const storageRef = firebase.storage().ref(); + + // [START storage_monitor_upload] + var uploadTask = storageRef.child('images/rivers.jpg').put(file); + + // Register three observers: + // 1. 'state_changed' observer, called any time the state changes + // 2. Error observer, called on failure + // 3. Completion observer, called on successful completion + uploadTask.on('state_changed', + (snapshot) => { + // Observe state change events such as progress, pause, and resume + // Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded + var progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100; + console.log('Upload is ' + progress + '% done'); + switch (snapshot.state) { + case firebase.storage.TaskState.PAUSED: // or 'paused' + console.log('Upload is paused'); + break; + case firebase.storage.TaskState.RUNNING: // or 'running' + console.log('Upload is running'); + break; + } + }, + (error) => { + // Handle unsuccessful uploads + }, + () => { + // Handle successful uploads on complete + // For instance, get the download URL: https://firebasestorage.googleapis.com/... + uploadTask.snapshot.ref.getDownloadURL().then((downloadURL) => { + console.log('File available at', downloadURL); + }); + } + ); + // [END storage_monitor_upload] +} + +/** + * @param {File} file + */ +function uploadHandleError(file) { + const storageRef = firebase.storage().ref(); + + // [START storage_upload_handle_error] + // Create the file metadata + var metadata = { + contentType: 'image/jpeg' + }; + + // Upload file and metadata to the object 'images/mountains.jpg' + var uploadTask = storageRef.child('images/' + file.name).put(file, metadata); + + // Listen for state changes, errors, and completion of the upload. + uploadTask.on(firebase.storage.TaskEvent.STATE_CHANGED, // or 'state_changed' + (snapshot) => { + // Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded + var progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100; + console.log('Upload is ' + progress + '% done'); + switch (snapshot.state) { + case firebase.storage.TaskState.PAUSED: // or 'paused' + console.log('Upload is paused'); + break; + case firebase.storage.TaskState.RUNNING: // or 'running' + console.log('Upload is running'); + break; + } + }, + (error) => { + // A full list of error codes is available at + // https://firebase.google.com/docs/storage/web/handle-errors + switch (error.code) { + case 'storage/unauthorized': + // User doesn't have permission to access the object + break; + case 'storage/canceled': + // User canceled the upload + break; + + // ... + + case 'storage/unknown': + // Unknown error occurred, inspect error.serverResponse + break; + } + }, + () => { + // Upload completed successfully, now we can get the download URL + uploadTask.snapshot.ref.getDownloadURL().then((downloadURL) => { + console.log('File available at', downloadURL); + }); + } + ); + // [END storage_upload_handle_error] +} diff --git a/tsconfig.json.template b/tsconfig.json.template new file mode 100644 index 00000000..9b4a631b --- /dev/null +++ b/tsconfig.json.template @@ -0,0 +1,37 @@ +{ + "compilerOptions": { + /* Basic Options */ + "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + "lib": ["dom", "webworker"], /* Specify library files to be included in the compilation. */ + "allowJs": true, /* Allow javascript files to be compiled. */ + "checkJs": true, /* Report errors in .js files. */ + "outDir": "./dist", /* Redirect output structure to the directory. */ + "noEmit": true, /* Do not emit outputs. */ + "resolveJsonModule": true, + "typeRoots": [ + "./node_modules/@types" + ], + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + "noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */ + "strictNullChecks": false, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + // See: https://github.com/microsoft/TypeScript/issues/20595 + "skipLibCheck": true, + + /* Additional Checks */ + "noUnusedLocals": false, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + } +}