Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(emulator): add the emulator support on firestore, functions and storage packages #658

Merged
merged 12 commits into from
Jul 2, 2024
7 changes: 7 additions & 0 deletions .changeset/violet-taxis-end.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@capacitor-firebase/firestore': minor
'@capacitor-firebase/functions': minor
'@capacitor-firebase/storage': minor
---

feat: add emulator support
33 changes: 33 additions & 0 deletions packages/firestore/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,13 @@ const disableNetwork = async () => {
await FirebaseFirestore.disableNetwork();
};

const useEmulator = async () => {
await FirebaseFirestore.useEmulator({
host: '10.0.2.2',
port: 9001,
});
};

const addDocumentSnapshotListener = async () => {
const callbackId = await FirebaseFirestore.addDocumentSnapshotListener(
{
Expand Down Expand Up @@ -269,6 +276,7 @@ const removeAllListeners = async () => {
* [`clearPersistence()`](#clearpersistence)
* [`enableNetwork()`](#enablenetwork)
* [`disableNetwork()`](#disablenetwork)
* [`useEmulator(...)`](#useemulator)
* [`addDocumentSnapshotListener(...)`](#adddocumentsnapshotlistener)
* [`addCollectionSnapshotListener(...)`](#addcollectionsnapshotlistener)
* [`addCollectionGroupSnapshotListener(...)`](#addcollectiongroupsnapshotlistener)
Expand Down Expand Up @@ -466,6 +474,23 @@ Disables use of the network.
--------------------


### useEmulator(...)

```typescript
useEmulator(options: UseEmulatorOptions) => Promise<void>
```

Instrument your app to talk to the Firestore emulator.

| Param | Type |
| ------------- | ----------------------------------------------------------------- |
| **`options`** | <code><a href="#useemulatoroptions">UseEmulatorOptions</a></code> |

**Since:** 6.1.0

--------------------


### addDocumentSnapshotListener(...)

```typescript
Expand Down Expand Up @@ -731,6 +756,14 @@ Remove all listeners for this plugin.
| **`queryConstraints`** | <code>QueryNonFilterConstraint[]</code> | Narrow or order the set of documents to retrieve, but do not explicitly filter for document fields. | 5.2.0 |


#### UseEmulatorOptions

| Prop | Type | Description | Default | Since |
| ---------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | ----- |
| **`host`** | <code>string</code> | The emulator host without any port or scheme. Note when using a Android Emulator device: 10.0.2.2 is the special IP address to connect to the 'localhost' of the host computer. | | 6.1.0 |
| **`port`** | <code>number</code> | The emulator port. | <code>8080</code> | 6.1.0 |


#### AddDocumentSnapshotListenerOptions

| Prop | Type | Description | Since |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,10 @@ public void disableNetwork(@NonNull EmptyResultCallback callback) {
);
}

public void useEmulator(@NonNull String host, int port) {
getFirebaseFirestoreInstance().useEmulator(host, port);
}

public void addDocumentSnapshotListener(@NonNull AddDocumentSnapshotListenerOptions options, @NonNull NonEmptyResultCallback callback) {
String reference = options.getReference();
String callbackId = options.getCallbackId();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public class FirebaseFirestorePlugin extends Plugin {

public static final String TAG = "FirebaseFirestore";
public static final String ERROR_REFERENCE_MISSING = "reference must be provided.";
public static final String ERROR_HOST_MISSING = "host must be provided.";
public static final String ERROR_CALLBACK_ID_MISSING = "callbackId must be provided.";
public static final String ERROR_DATA_MISSING = "data must be provided.";
public static final String ERROR_OPERATIONS_MISSING = "operations must be provided.";
Expand Down Expand Up @@ -375,6 +376,24 @@ public void error(Exception exception) {
}
}

@PluginMethod
public void useEmulator(PluginCall call) {
try {
String host = call.getString("host");
if (host == null) {
call.reject(ERROR_HOST_MISSING);
return;
}
int port = call.getInt("port", 8080);

implementation.useEmulator(host, port);
call.resolve();
} catch (Exception exception) {
Logger.error(TAG, exception.getMessage(), exception);
call.reject(exception.getMessage());
}
}

@PluginMethod(returnType = PluginMethod.RETURN_CALLBACK)
public void addDocumentSnapshotListener(PluginCall call) {
try {
Expand Down
7 changes: 7 additions & 0 deletions packages/firestore/ios/Plugin/FirebaseFirestore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,13 @@ import FirebaseFirestore
}
}

@objc func useEmulator(_ host: String, _ port: Int) {
let settings = Firestore.firestore().settings
settings.host = "\(host):\(port)"
settings.isSSLEnabled = false
Firestore.firestore().settings = settings
}

@objc public func addDocumentSnapshotListener(_ options: AddDocumentSnapshotListenerOptions, completion: @escaping (Result?, Error?) -> Void) {
let reference = options.getReference()
let callbackId = options.getCallbackId()
Expand Down
1 change: 1 addition & 0 deletions packages/firestore/ios/Plugin/FirebaseFirestorePlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
CAP_PLUGIN_METHOD(getCollectionGroup, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(enableNetwork, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(disableNetwork, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(useEmulator, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(addDocumentSnapshotListener, CAPPluginReturnCallback);
CAP_PLUGIN_METHOD(addCollectionSnapshotListener, CAPPluginReturnCallback);
CAP_PLUGIN_METHOD(addCollectionGroupSnapshotListener, CAPPluginReturnCallback);
Expand Down
12 changes: 12 additions & 0 deletions packages/firestore/ios/Plugin/FirebaseFirestorePlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public class FirebaseFirestorePlugin: CAPPlugin {
public let errorReferenceMissing = "reference must be provided."
public let errorDataMissing = "data must be provided."
public let errorOperationsMissing = "operations must be provided."
public let errorHostMissing = "host must be provided."
public let errorCallbackIdMissing = "callbackId must be provided."
private var implementation: FirebaseFirestore?
private var pluginCallMap: [String: CAPPluginCall] = [:]
Expand Down Expand Up @@ -221,6 +222,17 @@ public class FirebaseFirestorePlugin: CAPPlugin {
})
}

@objc func useEmulator(_ call: CAPPluginCall) {
guard let host = call.getString("host") else {
call.reject(errorHostMissing)
return
}
let port = call.getInt("port") ?? 8080

implementation?.useEmulator(host, port)
call.resolve()
}

@objc func addDocumentSnapshotListener(_ call: CAPPluginCall) {
call.keepAlive = true

Expand Down
29 changes: 29 additions & 0 deletions packages/firestore/src/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ export interface FirebaseFirestorePlugin {
* @since 5.2.0
*/
disableNetwork(): Promise<void>;
/**
* Instrument your app to talk to the Firestore emulator.
*
* @since 6.1.0
*/
useEmulator(options: UseEmulatorOptions): Promise<void>;
/**
* Adds a listener for document snapshot events.
*
Expand Down Expand Up @@ -336,6 +342,29 @@ export interface GetCollectionGroupResult<T> {
snapshots: DocumentSnapshot<T>[];
}

/**
* @since 6.1.0
*/
export interface UseEmulatorOptions {
/**
* The emulator host without any port or scheme.
*
* Note when using a Android Emulator device: 10.0.2.2 is the special IP address to connect to the 'localhost' of the host computer.
*
* @since 6.1.0
* @example "127.0.0.1"
*/
host: string;
/**
* The emulator port.
*
* @since 6.1.0
* @default 8080
* @example 8080
*/
port?: number;
}

/**
* @since 5.2.0
*/
Expand Down
8 changes: 8 additions & 0 deletions packages/firestore/src/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
clearIndexedDbPersistence,
collection,
collectionGroup,
connectFirestoreEmulator,
deleteDoc,
disableNetwork,
doc,
Expand Down Expand Up @@ -63,6 +64,7 @@ import type {
QueryNonFilterConstraint,
RemoveSnapshotListenerOptions,
SetDocumentOptions,
UseEmulatorOptions,
WriteBatchOptions,
} from './definitions';

Expand Down Expand Up @@ -201,6 +203,12 @@ export class FirebaseFirestoreWeb
await disableNetwork(firestore);
}

public async useEmulator(options: UseEmulatorOptions): Promise<void> {
const firestore = getFirestore();
const port = options.port || 8080;
connectFirestoreEmulator(firestore, options.host, port);
}

public async addDocumentSnapshotListener<
T extends DocumentData = DocumentData,
>(
Expand Down
43 changes: 43 additions & 0 deletions packages/functions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ const callByUrl = async () => {
});
return data;
};

const useEmulator = async () => {
await FirebaseFunctions.useEmulator({
host: '10.0.2.2',
port: 9001,
});
};
```

## API
Expand All @@ -71,6 +78,7 @@ const callByUrl = async () => {

* [`callByName(...)`](#callbyname)
* [`callByUrl(...)`](#callbyurl)
* [`useEmulator(...)`](#useemulator)
* [Interfaces](#interfaces)
* [Type Aliases](#type-aliases)

Expand Down Expand Up @@ -117,6 +125,33 @@ Call a callable function by URL.
--------------------


### useEmulator(...)

```typescript
useEmulator(options: UseEmulatorOptions) => Promise<void>
```

Instrument your app to talk to the Cloud Functions emulator.

On Android, the cleartext traffic must be allowed. On the Capacitor configuration:
```
{
server: {
cleartext: true
}
}
```
**The cleartext traffic is not intended for use in production.**

| Param | Type |
| ------------- | ----------------------------------------------------------------- |
| **`options`** | <code><a href="#useemulatoroptions">UseEmulatorOptions</a></code> |

**Since:** 6.1.0

--------------------


### Interfaces


Expand All @@ -142,6 +177,14 @@ Call a callable function by URL.
| **`url`** | <code>string</code> | The URL of the callable function. | 6.1.0 |


#### UseEmulatorOptions

| Prop | Type | Description | Default | Since |
| ---------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | ----- |
| **`host`** | <code>string</code> | The emulator host without any port or scheme. Note when using a Android Emulator device: 10.0.2.2 is the special IP address to connect to the 'localhost' of the host computer. | | 6.1.0 |
| **`port`** | <code>number</code> | The emulator port. | <code>5001</code> | 6.1.0 |


### Type Aliases


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ public void callByUrl(@NonNull CallByUrlOptions options, @NonNull NonEmptyResult
);
}

public void useEmulator(@NonNull String host, int port) {
getFirebaseFunctionsInstance(null).useEmulator(host, port);
}

private com.google.firebase.functions.FirebaseFunctions getFirebaseFunctionsInstance(@Nullable String regionOrCustomDomain) {
if (regionOrCustomDomain == null) {
return com.google.firebase.functions.FirebaseFunctions.getInstance();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public class FirebaseFunctionsPlugin extends Plugin {
public static final String TAG = "FirebaseFunctions";
public static final String ERROR_NAME_MISSING = "name must be provided.";
public static final String ERROR_URL_MISSING = "url must be provided.";
public static final String ERROR_HOST_MISSING = "host must be provided.";

private FirebaseFunctions implementation;

Expand Down Expand Up @@ -88,4 +89,22 @@ public void error(Exception exception) {
call.reject(exception.getMessage());
}
}

@PluginMethod
public void useEmulator(PluginCall call) {
try {
String host = call.getString("host");
if (host == null) {
call.reject(ERROR_HOST_MISSING);
return;
}
int port = call.getInt("port", 5001);

implementation.useEmulator(host, port);
call.resolve();
} catch (Exception exception) {
Logger.error(TAG, exception.getMessage(), exception);
call.reject(exception.getMessage());
}
}
}
4 changes: 4 additions & 0 deletions packages/functions/ios/Plugin/FirebaseFunctions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,8 @@ import FirebaseFunctions
}
}
}

@objc func useEmulator(_ host: String, _ port: Int) {
Functions.functions().useEmulator(withHost: host, port: port)
}
}
1 change: 1 addition & 0 deletions packages/functions/ios/Plugin/FirebaseFunctionsPlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@
CAP_PLUGIN(FirebaseFunctionsPlugin, "FirebaseFunctions",
CAP_PLUGIN_METHOD(callByName, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(callByUrl, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(useEmulator, CAPPluginReturnPromise);
)
12 changes: 12 additions & 0 deletions packages/functions/ios/Plugin/FirebaseFunctionsPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Capacitor
public class FirebaseFunctionsPlugin: CAPPlugin {
public let tag = "FirebaseFunctions"
public let errorNameMissing = "name must be provided."
public let errorHostMissing = "host must be provided."
private var implementation: FirebaseFunctions?

override public func load() {
Expand Down Expand Up @@ -57,4 +58,15 @@ public class FirebaseFunctionsPlugin: CAPPlugin {
}
})
}

@objc func useEmulator(_ call: CAPPluginCall) {
guard let host = call.getString("host") else {
call.reject(errorHostMissing)
return
}
let port = call.getInt("port") ?? 5001

implementation?.useEmulator(host, port)
call.resolve()
}
}
Loading