Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

emberfire

Package Overview
Dependencies
Maintainers
6
Versions
70
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

emberfire - npm Package Compare versions

Comparing version 3.0.0-rc.5 to 3.0.0-rc.6

addon/mixins/performance-route.js

3

addon/services/firebase-app.js
import Service from '@ember/service';
import { get, set } from '@ember/object';
import { inject as service } from '@ember/service';
import RSVP from 'rsvp';
const { resolve } = RSVP;
import { resolve } from 'rsvp';
const getApp = (service) => {

@@ -7,0 +6,0 @@ const firebase = get(service, 'firebase');

@@ -8,2 +8,3 @@ import Service from '@ember/service';

import { rootCollection as realtimeDatabaseRootCollection } from '../adapters/realtime-database';
import { resolve } from 'rsvp';
const getService = (object) => getOwner(object).lookup('service:realtime-listener');

@@ -48,5 +49,2 @@ const isFastboot = (object) => {

};
function isFirestoreQuery(arg) {
return arg.onSnapshot !== undefined;
}
function isFirestoreDocumentRefernce(arg) {

@@ -58,25 +56,54 @@ return arg.onSnapshot !== undefined;

}) {
subscribe(route, model) {
subscribe(route, model, parentModel, relationship) {
if (!model) {
return;
}
const store = model.store;
const modelName = (model.modelName || model.get('_internalModel.modelName'));
const modelName = (model.get('type.modelName') || model.get('_internalModel.modelName') || model.modelName);
const modelClass = store.modelFor(modelName);
const query = model.get('meta.query');
const ref = model.get('_internalModel._recordData._data._ref');
const ref = (model.get('meta._ref') || model.get('_recordData._data._ref') || model.get('_internalModel._recordData._data._ref'));
const uniqueIdentifier = model.toString();
const serializer = store.serializerFor(modelName); // TODO type
const adapter = store.adapterFor(modelName);
const args = { model, store, modelName, modelClass, uniqueIdentifier, serializer, adapter };
if (query) {
if (isFirestoreQuery(query)) {
// Firestore query
const unsubscribe = runFirestoreCollectionListener(Object.assign({ query }, args));
setRouteSubscription(this, route, uniqueIdentifier, unsubscribe);
const observeRelationships = (internalModel) => {
// HACK HACK HACK
const movedKey = '__original___updatePromiseProxyFor';
const proxyPromiseListenersKey = `_updatePromiseProxyListeners`;
const requestedRelationshipsKey = '_requestedRelationships';
if (!internalModel[requestedRelationshipsKey]) {
internalModel[requestedRelationshipsKey] = [];
}
else {
// RTDB query
const unsubscribe = runRealtimeDatabaseListListener(Object.assign({ ref: query }, args));
setRouteSubscription(this, route, uniqueIdentifier, unsubscribe);
const movedMethod = internalModel[movedKey];
if (!movedMethod) {
internalModel[movedKey] = internalModel._updatePromiseProxyFor;
internalModel[proxyPromiseListenersKey] = [];
internalModel._updatePromiseProxyFor = ((kind, key, args) => {
const proxy = internalModel[movedKey](kind, key, args);
proxy.then((result) => {
if (internalModel[requestedRelationshipsKey].indexOf(key) < 0) {
internalModel[requestedRelationshipsKey] = [...internalModel[requestedRelationshipsKey], key];
internalModel[proxyPromiseListenersKey].forEach((f) => f(kind, key, args, result));
}
});
return proxy;
});
}
internalModel[proxyPromiseListenersKey] = [
...internalModel[proxyPromiseListenersKey],
((_kind, key, _args, result) => {
const triggerdRelationship = modelClass.relationshipsObject[key];
this.subscribe(route, result, model, triggerdRelationship);
})
];
};
let content = model.content || parentModel && get(parentModel, `${relationship.key}.content`);
if (model._internalModel) {
observeRelationships(model._internalModel);
}
else if (ref) {
else if (content) { // TODO find backing content for hasMany
content.forEach((internalModel) => {
observeRelationships(internalModel);
});
}
if (ref) {
if (isFirestoreDocumentRefernce(ref)) {

@@ -115,7 +142,40 @@ // Firestore find

else {
// findAll ditches the metadata :(
if (serializer.constructor.name == 'FirestoreSerializer') {
// Firestore findAll
firestoreRootCollection(adapter, modelName).then(query => {
const unsubscribe = runFirestoreCollectionListener(Object.assign({ query }, args));
const query = model.get('meta.query');
const queryOrRoot = query && resolve(query) || firestoreRootCollection(adapter, modelName);
queryOrRoot.then(query => {
const unsubscribe = query.onSnapshot(snapshot => {
snapshot.docChanges().forEach(change => run(() => {
const normalizedData = serializer.normalizeSingleResponse(store, modelClass, change.doc);
switch (change.type) {
case 'added': {
const current = content.objectAt(change.newIndex);
if (current == null || current.id !== change.doc.id) {
const doc = store.push(normalizedData);
content.insertAt(change.newIndex, doc._internalModel);
}
break;
}
case 'modified': {
const current = content.objectAt(change.oldIndex);
if (current == null || current.id == change.doc.id) {
if (change.newIndex !== change.oldIndex) {
content.removeAt(change.oldIndex);
content.insertAt(change.newIndex, current);
}
}
store.push(normalizedData);
break;
}
case 'removed': {
const current = content.objectAt(change.oldIndex);
if (current && current.id == change.doc.id) {
content.removeAt(change.oldIndex);
}
break;
}
}
}));
});
setRouteSubscription(this, route, uniqueIdentifier, unsubscribe);

@@ -126,4 +186,70 @@ });

// RTDB findAll
realtimeDatabaseRootCollection(adapter, modelName).then(ref => {
const unsubscribe = runRealtimeDatabaseListListener(Object.assign({ ref }, args));
const ref = (model.get('meta.query') || model.get('recordData._data._ref'));
const refOrRoot = ref ? resolve(ref) : realtimeDatabaseRootCollection(adapter, modelName);
refOrRoot.then(ref => {
const onChildAdded = ref.on('child_added', (snapshot, priorKey) => {
run(() => {
if (snapshot) {
const normalizedData = serializer.normalizeSingleResponse(store, modelClass, snapshot);
const doc = store.push(normalizedData);
const existing = content.find((record) => record.id === doc.id);
if (existing) {
content.removeObject(existing);
}
let insertIndex = 0;
if (priorKey) {
const record = content.find((record) => record.id === priorKey);
insertIndex = content.indexOf(record) + 1;
}
const current = content.objectAt(insertIndex);
if (current == null || current.id !== doc.id) {
content.insertAt(insertIndex, doc._internalModel);
}
}
});
});
const onChildRemoved = ref.on('child_removed', snapshot => {
run(() => {
if (snapshot) {
const record = content.find((record) => record.id === snapshot.key);
if (record) {
content.removeObject(record);
}
}
});
});
const onChildChanged = ref.on('child_changed', snapshot => {
run(() => {
if (snapshot) {
const normalizedData = serializer.normalizeSingleResponse(store, modelClass, snapshot);
store.push(normalizedData);
}
});
});
const onChildMoved = ref.on('child_moved', (snapshot, priorKey) => {
run(() => {
if (snapshot) {
const normalizedData = serializer.normalizeSingleResponse(store, modelClass, snapshot);
const doc = store.push(normalizedData);
const existing = content.find((record) => record.id === doc.id);
if (existing) {
content.removeObject(existing);
}
if (priorKey) {
const record = content.find((record) => record.id === priorKey);
const index = content.indexOf(record);
content.insertAt(index + 1, doc._internalModel);
}
else {
content.insertAt(0, doc._internalModel);
}
}
});
});
const unsubscribe = () => {
ref.off('child_added', onChildAdded);
ref.off('child_removed', onChildRemoved);
ref.off('child_changed', onChildChanged);
ref.off('child_moved', onChildMoved);
};
setRouteSubscription(this, route, uniqueIdentifier, unsubscribe);

@@ -138,105 +264,1 @@ });

}
const runFirestoreCollectionListener = ({ query, model, store, serializer, modelClass }) => {
const unsubscribe = query.onSnapshot(snapshot => {
snapshot.docChanges().forEach(change => run(() => {
const normalizedData = serializer.normalizeSingleResponse(store, modelClass, change.doc);
switch (change.type) {
case 'added': {
const current = model.content.objectAt(change.newIndex);
if (current == null || current.id !== change.doc.id) {
const doc = store.push(normalizedData);
model.content.insertAt(change.newIndex, doc._internalModel);
}
break;
}
case 'modified': {
const current = model.content.objectAt(change.oldIndex);
if (current == null || current.id == change.doc.id) {
if (change.newIndex !== change.oldIndex) {
model.content.removeAt(change.oldIndex);
model.content.insertAt(change.newIndex, current);
}
}
store.push(normalizedData);
break;
}
case 'removed': {
const current = model.content.objectAt(change.oldIndex);
if (current && current.id == change.doc.id) {
model.content.removeAt(change.oldIndex);
}
break;
}
}
}));
});
return unsubscribe;
};
const runRealtimeDatabaseListListener = ({ model, ref, serializer, store, modelClass }) => {
const onChildAdded = ref.on('child_added', (snapshot, priorKey) => {
run(() => {
if (snapshot) {
const normalizedData = serializer.normalizeSingleResponse(store, modelClass, snapshot);
const doc = store.push(normalizedData);
const existing = model.content.find((record) => record.id === doc.id);
if (existing) {
model.content.removeObject(existing);
}
let insertIndex = 0;
if (priorKey) {
const record = model.content.find((record) => record.id === priorKey);
insertIndex = model.content.indexOf(record) + 1;
}
const current = model.content.objectAt(insertIndex);
if (current == null || current.id !== doc.id) {
model.content.insertAt(insertIndex, doc._internalModel);
}
}
});
});
const onChildRemoved = ref.on('child_removed', snapshot => {
run(() => {
if (snapshot) {
const record = model.content.find((record) => record.id === snapshot.key);
if (record) {
model.content.removeObject(record);
}
}
});
});
const onChildChanged = ref.on('child_changed', snapshot => {
run(() => {
if (snapshot) {
const normalizedData = serializer.normalizeSingleResponse(store, modelClass, snapshot);
store.push(normalizedData);
}
});
});
const onChildMoved = ref.on('child_moved', (snapshot, priorKey) => {
run(() => {
if (snapshot) {
const normalizedData = serializer.normalizeSingleResponse(store, modelClass, snapshot);
const doc = store.push(normalizedData);
const existing = model.content.find((record) => record.id === doc.id);
if (existing) {
model.content.removeObject(existing);
}
if (priorKey) {
const record = model.content.find((record) => record.id === priorKey);
const index = model.content.indexOf(record);
model.content.insertAt(index + 1, doc._internalModel);
}
else {
model.content.insertAt(0, doc._internalModel);
}
}
});
});
const unsubscribe = () => {
ref.off('child_added', onChildAdded);
ref.off('child_removed', onChildRemoved);
ref.off('child_changed', onChildChanged);
ref.off('child_moved', onChildMoved);
};
return unsubscribe;
};
import Evented from '@ember/object/evented';
import EmberObject from '@ember/object';
import { get, set } from '@ember/object';
import RSVP from 'rsvp';
const { Promise, resolve } = RSVP;
import { Promise, resolve } from 'rsvp';
import { run } from '@ember/runloop';

@@ -7,0 +6,0 @@ import { inject as service } from '@ember/service';

import EmberObject from '@ember/object';
import { inject as service } from '@ember/service';
import { get } from '@ember/object';
import RSVP from 'rsvp';
const { Promise, reject } = RSVP;
import { Promise, reject, resolve } from 'rsvp';
import { run } from '@ember/runloop';
import { resolve } from 'path';
export default class FirebaseToriiAdapter extends EmberObject.extend({

@@ -9,0 +7,0 @@ firebaseApp: service('firebase-app')

import EmberObject from '@ember/object';
import RSVP from 'rsvp';
const { reject } = RSVP;
import { reject } from 'rsvp';
export default class FirebaseToriiProvider extends EmberObject.extend({}) {

@@ -5,0 +4,0 @@ open() {

@@ -1,2 +0,2 @@

# Authentication
# Collect Analytics

@@ -31,2 +31,30 @@ ## Collect Analytics data automatically with the `AnalyticsRouteMixin`

## Collect traces on route transistions automatically with `PerformanceRouteMixin`
```js
import PerformanceRouteMixin from 'emberfire/mixins/performance-route';
import Route from '@ember/routing/route';
export default Route.extend(PerformanceRouteMixin);
```
## Log traces with the `FirebaseApp` Service
```js
import { inject as service } from '@ember/service';
...
firebaseApp: service('firebase-app'),
...
const perf = await firebase.performance();
const trace = perf.trace("some_event");
trace.start();
...
trace.stop()
```
### Continue reading

@@ -33,0 +61,0 @@

@@ -43,3 +43,3 @@ [EmberFire](../README.md) > [FirebaseAppService](../classes/firebaseappservice.md)

*Defined in services/firebase-app.ts:24*
*Defined in services/firebase-app.ts:22*

@@ -53,3 +53,3 @@ ___

*Defined in services/firebase-app.ts:24*
*Defined in services/firebase-app.ts:22*

@@ -63,3 +63,3 @@ ___

*Defined in services/firebase-app.ts:26*
*Defined in services/firebase-app.ts:24*

@@ -76,3 +76,3 @@ ___

*Defined in services/firebase-app.ts:31*
*Defined in services/firebase-app.ts:29*

@@ -88,3 +88,3 @@ **Returns:** `Promise`<`Analytics`>

*Defined in services/firebase-app.ts:30*
*Defined in services/firebase-app.ts:28*

@@ -100,3 +100,3 @@ **Returns:** `Promise`<`Auth`>

*Defined in services/firebase-app.ts:37*
*Defined in services/firebase-app.ts:35*

@@ -118,3 +118,3 @@ **Parameters:**

*Defined in services/firebase-app.ts:28*
*Defined in services/firebase-app.ts:26*

@@ -130,3 +130,3 @@ **Returns:** `Promise`<`any`>

*Defined in services/firebase-app.ts:32*
*Defined in services/firebase-app.ts:30*

@@ -142,3 +142,3 @@ **Returns:** `Promise`<`Firestore`>

*Defined in services/firebase-app.ts:38*
*Defined in services/firebase-app.ts:36*

@@ -160,3 +160,3 @@ **Parameters:**

*Defined in services/firebase-app.ts:41*
*Defined in services/firebase-app.ts:39*

@@ -178,3 +178,3 @@ **Parameters:**

*Defined in services/firebase-app.ts:33*
*Defined in services/firebase-app.ts:31*

@@ -190,3 +190,3 @@ **Returns:** `Promise`<`Messaging`>

*Defined in services/firebase-app.ts:34*
*Defined in services/firebase-app.ts:32*

@@ -202,3 +202,3 @@ **Returns:** `Promise`<`Performance`>

*Defined in services/firebase-app.ts:35*
*Defined in services/firebase-app.ts:33*

@@ -214,3 +214,3 @@ **Returns:** `Promise`<`RemoteConfig`>

*Defined in services/firebase-app.ts:39*
*Defined in services/firebase-app.ts:37*

@@ -217,0 +217,0 @@ **Parameters:**

@@ -34,3 +34,3 @@ [EmberFire](../README.md) > [FirebaseSessionStore](../classes/firebasesessionstore.md)

*Defined in session-stores/firebase.ts:24*
*Defined in session-stores/firebase.ts:19*

@@ -44,3 +44,3 @@ ___

*Defined in session-stores/firebase.ts:20*
*Defined in session-stores/firebase.ts:15*

@@ -54,3 +54,3 @@ ___

*Defined in session-stores/firebase.ts:23*
*Defined in session-stores/firebase.ts:18*

@@ -64,3 +64,3 @@ ___

*Defined in session-stores/firebase.ts:22*
*Defined in session-stores/firebase.ts:17*

@@ -77,3 +77,3 @@ ___

*Defined in session-stores/firebase.ts:26*
*Defined in session-stores/firebase.ts:21*

@@ -80,0 +80,0 @@ **Returns:** `Promise`<`Object`>

@@ -33,3 +33,3 @@ [EmberFire](../README.md) > [FirebaseToriiAdapter](../classes/firebasetoriiadapter.md)

*Defined in torii-adapters/firebase.ts:19*
*Defined in torii-adapters/firebase.ts:14*

@@ -46,3 +46,3 @@ ___

*Defined in torii-adapters/firebase.ts:40*
*Defined in torii-adapters/firebase.ts:35*

@@ -56,5 +56,5 @@ **Returns:** `Promise`<`void`>

▸ **open**(user: *`any`*): `string`
▸ **open**(user: *`any`*): `Promise`<`any`>
*Defined in torii-adapters/firebase.ts:21*
*Defined in torii-adapters/firebase.ts:16*

@@ -67,3 +67,3 @@ **Parameters:**

**Returns:** `string`
**Returns:** `Promise`<`any`>

@@ -77,3 +77,3 @@ ___

*Defined in torii-adapters/firebase.ts:25*
*Defined in torii-adapters/firebase.ts:20*

@@ -80,0 +80,0 @@ **Returns:** `Promise`<`Object`>

@@ -27,3 +27,3 @@ [EmberFire](../README.md) > [FirebaseToriiProvider](../classes/firebasetoriiprovider.md)

*Defined in torii-providers/firebase.ts:9*
*Defined in torii-providers/firebase.ts:7*

@@ -30,0 +30,0 @@ **Returns:** `Promise`<`never`>

@@ -26,5 +26,5 @@ [EmberFire](../README.md) > [RealtimeListenerService](../classes/realtimelistenerservice.md)

▸ **subscribe**(route: *`Object`*, model: *`any`*): `void`
▸ **subscribe**(route: *`Object`*, model: *`any`*, parentModel?: *`any`*, relationship?: *`any`*): `void`
*Defined in services/realtime-listener.ts:83*
*Defined in services/realtime-listener.ts:62*

@@ -37,2 +37,4 @@ **Parameters:**

| model | `any` |
| `Optional` parentModel | `any` |
| `Optional` relationship | `any` |

@@ -48,3 +50,3 @@ **Returns:** `void`

*Defined in services/realtime-listener.ts:149*
*Defined in services/realtime-listener.ts:246*

@@ -51,0 +53,0 @@ **Parameters:**

@@ -282,3 +282,3 @@

*Defined in services/realtime-listener.ts:18*
*Defined in services/realtime-listener.ts:19*

@@ -301,3 +301,3 @@ **Parameters:**

*Defined in services/realtime-listener.ts:19*
*Defined in services/realtime-listener.ts:20*

@@ -304,0 +304,0 @@ **Parameters:**

{
"name": "emberfire",
"version": "3.0.0-rc.5",
"version": "3.0.0-rc.6",
"description": "The officially supported adapter for using Firebase with Ember",

@@ -25,3 +25,3 @@ "keywords": [

"build": "npm run ts:precompile && ember build",
"serve": "npm run build && node ./express.js",
"serve": "ember serve",
"package": "npm run build && npm pack .",

@@ -28,0 +28,0 @@ "test": "npm run ts:precompile && ember test",

@@ -61,4 +61,5 @@ # EmberFire [![Build Status](https://travis-ci.org/firebase/emberfire.svg?branch=master)](https://travis-ci.org/firebase/emberfire) [![Version](https://badge.fury.io/gh/firebase%2Femberfire.svg)](http://badge.fury.io/gh/firebase%2Femberfire) [![Monthly Downloads](http://img.shields.io/npm/dm/emberfire.svg?style=flat)](https://www.npmjs.org/package/emberfire) [![Ember Observer Score](http://emberobserver.com/badges/emberfire.svg)](http://emberobserver.com/addons/emberfire)

import RealtimeRouteMixin from 'emberfire/mixins/realtime-route';
import PerformanceRouteMixin from 'emberfire/mixins/performance-route';
export default Route.extend(RealtimeRouteMixin, {
export default Route.extend(RealtimeRouteMixin, PerformanceRouteMixin, {
model() {

@@ -65,0 +66,0 @@ return this.store.query('article', { orderBy: 'publishedAt' });

@@ -0,5 +1,5 @@

/// <reference types="rsvp" />
import Service from '@ember/service';
import Ember from 'ember';
import FirebaseService from './firebase';
import RSVP from 'rsvp';
declare const FirebaseAppService_base: Readonly<typeof Service> & (new (properties?: object | undefined) => {

@@ -17,11 +17,11 @@ name: undefined;

delete: () => Promise<any>;
auth: () => RSVP.Promise<import("firebase").auth.Auth>;
analytics: () => RSVP.Promise<import("firebase").analytics.Analytics>;
firestore: () => RSVP.Promise<import("firebase").firestore.Firestore>;
messaging: () => RSVP.Promise<import("firebase").messaging.Messaging>;
performance: () => RSVP.Promise<import("firebase").performance.Performance>;
remoteConfig: () => RSVP.Promise<import("firebase").remoteConfig.RemoteConfig>;
database: (url?: string | undefined) => RSVP.Promise<import("firebase").database.Database>;
functions: (region?: string | undefined) => RSVP.Promise<import("firebase").functions.Functions>;
storage: (url?: string | undefined) => RSVP.Promise<import("firebase").storage.Storage>;
auth: () => import("rsvp").default.Promise<import("firebase").auth.Auth>;
analytics: () => import("rsvp").default.Promise<import("firebase").analytics.Analytics>;
firestore: () => import("rsvp").default.Promise<import("firebase").firestore.Firestore>;
messaging: () => import("rsvp").default.Promise<import("firebase").messaging.Messaging>;
performance: () => import("rsvp").default.Promise<import("firebase").performance.Performance>;
remoteConfig: () => import("rsvp").default.Promise<import("firebase").remoteConfig.RemoteConfig>;
database: (url?: string | undefined) => import("rsvp").default.Promise<import("firebase").database.Database>;
functions: (region?: string | undefined) => import("rsvp").default.Promise<import("firebase").functions.Functions>;
storage: (url?: string | undefined) => import("rsvp").default.Promise<import("firebase").storage.Storage>;
init(...args: any[]): void;

@@ -28,0 +28,0 @@ }

@@ -19,3 +19,3 @@ import Service from '@ember/service';

export default class RealtimeListenerService extends RealtimeListenerService_base {
subscribe(route: Object, model: any): void;
subscribe(route: Object, model: any, parentModel?: any, relationship?: any): void;
unsubscribe(route: Object, model?: DS.Model): void;

@@ -22,0 +22,0 @@ }

@@ -0,4 +1,4 @@

/// <reference types="rsvp" />
import Evented from '@ember/object/evented';
import EmberObject from '@ember/object';
import RSVP from 'rsvp';
import Ember from 'ember';

@@ -14,6 +14,6 @@ import FirebaseAppService from '../services/firebase-app';

restoring: boolean;
persist: typeof RSVP.Promise.resolve;
clear: typeof RSVP.Promise.resolve;
restore(): RSVP.Promise<unknown>;
persist: typeof import("rsvp").default.Promise.resolve;
clear: typeof import("rsvp").default.Promise.resolve;
restore(): import("rsvp").default.Promise<unknown>;
}
export {};

@@ -0,3 +1,3 @@

/// <reference types="rsvp" />
import EmberObject from '@ember/object';
import RSVP from 'rsvp';
import Ember from 'ember';

@@ -12,6 +12,6 @@ import FirebaseAppService from '../services/firebase-app';

firebaseApp: Ember.ComputedProperty<FirebaseAppService, FirebaseAppService>;
open(user: any): string;
restore(): RSVP.Promise<unknown>;
close(): RSVP.Promise<void>;
open(user: any): import("rsvp").default.Promise<any>;
restore(): import("rsvp").default.Promise<unknown>;
close(): import("rsvp").default.Promise<void>;
}
export {};

@@ -0,7 +1,7 @@

/// <reference types="rsvp" />
import EmberObject from '@ember/object';
import RSVP from 'rsvp';
declare const FirebaseToriiProvider_base: Readonly<typeof EmberObject> & (new (properties?: object | undefined) => import("@ember/object/-private/types").EmberClassArguments & EmberObject) & (new (...args: any[]) => import("@ember/object/-private/types").EmberClassArguments & EmberObject);
export default class FirebaseToriiProvider extends FirebaseToriiProvider_base {
open(): RSVP.Promise<never>;
open(): import("rsvp").default.Promise<never>;
}
export {};
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc