Giter Club home page Giter Club logo

xero-node's Introduction

xero-node

npm version Github forks Github stars npm

The xero-node SDK makes it easy for developers to access Xero's APIs in their JavaScript code, and build robust applications and software using small business & general ledger accounting data.

Table of Contents


API Client documentation

This SDK supports full method coverage for the following Xero API sets:

API Set Description
Accounting The Accounting API exposes accounting functions of the main Xero application (most commonly used)
Assets The Assets API exposes fixed asset related functions of the Xero Accounting application
Bankfeeds The Bankfeeds API facilitates the flow of transaction and statement data
Files The Files API provides access to the files, folders, and the association of files within a Xero organisation
Projects Xero Projects allows businesses to track time and costs on projects/jobs and report on profitability
Payroll (AU) The (AU) Payroll API exposes payroll related functions of the payroll Xero application
Payroll (NZ) The (NZ) Payroll API exposes payroll related functions of the payroll Xero application
Payroll (UK) The (UK) Payroll API exposes payroll related functions of the payroll Xero application

drawing


Sample Applications

Sample apps can get you started quickly with simple auth flows and advanced usage examples.

Sample App Description Screenshot
starter-app Basic getting started code samples drawing
full-app Complete app with more complex examples drawing
custom-connections-starter Basic app showing Custom Connections - a Xero premium option for building M2M integrations to a single org drawing
xero-node-sso-app App showing Xero Single Sign On - as well as basic setup and usage of the Xero App Store appStoreApi.getSubscription endpoint drawing
xero-node-sso-form App showing Sign up with Xero to Lead drawing

Xero Account Requirements

  • Create a free Xero user account
  • Login to your Xero developer dashboard and create an API application
  • Copy the credentials from your API app and store them using a secure ENV variable strategy
  • Decide the neccesary scopes for your app's functionality

Installation

To install this SDK in your project:

npm install xero-node

Configuration

import { XeroClient } from 'xero-node';

const xero = new XeroClient({
  clientId: 'YOUR_CLIENT_ID',
  clientSecret: 'YOUR_CLIENT_SECRET',
  redirectUris: [`http://localhost:${port}/callback`],
  scopes: 'openid profile email accounting.transactions offline_access'.split(" "),
  state: 'returnPage=my-sweet-dashboard', // custom params (optional)
  httpTimeout: 3000, // ms (optional)
  clockTolerance: 10 // seconds (optional)
});

Authentication

All API requests go through Xero's OAuth2.0 gateway and require a valid access_token to be set on the client which appends the access_token JWT to the header of each request.

If you are making an API call for the first time:

  1. Send the user to the Xero authorization URL
let consentUrl = await xero.buildConsentUrl();
res.redirect(consentUrl);
  1. The user will authorize your application and be sent to your redirect_uri
process.env.REDIRECT_URI
=> /callback?code=xyz123
  1. You exchange the temporary code for a valid token_set
const tokenSet = await xero.apiCallback(req.url);
// save the tokenSet

It is recommended that you store this token set JSON in a datastore in relation to the user who has authenticated the Xero API connection. Each time you want to call the Xero API, you will need to access the previously generated token set, initialize it on the SDK client, and refresh the access_token prior to making API calls.

Token Set

key value description
id_token: "xxx.yyy.zzz" OpenID Connect token returned if openid profile email scopes accepted
access_token: "xxx.yyy.zzz" Bearer token with a 30 minute expiration required for all API calls
expires_in: 1800 Time in seconds till the token expires - 1800s is 30m
refresh_token: "XXXXXXX" Alphanumeric string used to obtain a new Token Set w/ a fresh access_token - 60 day expiry
scope: ["email", "profile", "openid", "accounting.transactions", "offline_access"] The Xero permissions that are embedded in the access_token

Example Token Set JSON:

{
  "id_token": "xxx.yyy.zz",
  "access_token": "xxx.yyy.zzz",
  "expires_in": 1800,
  "token_type": "Bearer",
  "refresh_token": "xxxxxxxxx",
  "scope": ["email", "profile", "openid", "accounting.transactions", "offline_access"]
}

Custom Connections

Custom Connections are a Xero premium option used for building M2M integrations to a single organisation. A custom connection uses OAuth2.0's client_credentials grant which eliminates the step of exchanging the temporary code for a token set.

To use this SDK with a Custom Connection:

import { XeroClient } from 'xero-node';

const xero = new XeroClient({
  clientId: 'YOUR_CLIENT_ID',
  clientSecret: 'YOUR_CLIENT_SECRET',
  grantType: 'client_credentials'
});

const tokenSet = await xero.getClientCredentialsToken();
// save the tokenSet

const invoices = await xero.accountingApi.getInvoices('');

Because Custom Connections are only valid for a single organisation you don't need to pass the xero-tenant-id as the first parameter to every method, or more specifically for this SDK xeroTenantId can be an empty string.

Becuase the SDK is generated from the OpenAPI spec the parameter remains which requires you to pass an empty string to use this SDK with a Custom Connection.


App Store Subscriptions

If you are implementing subscriptions to participate in Xero's App Store you will need to setup App Store subscriptions endpoints.

When a plan is successfully purchased, the user is redirected back to the URL specified in the setup process. The Xero App Store appends the subscription Id to this URL so you can immediately determine what plan the user has subscribed to through the subscriptions API.

With your app credentials you can create a client via client_credentials grant_type with the marketplace.billing scope. This unique access_token will allow you to query any functions in appStoreApi. Client Credentials tokens to query app store endpoints will only work for apps that have completed the App Store on-boarding process.

// => /post-purchase-url

const xeroAppStoreClient = new XeroClient({
  clientId: process.env.CLIENT_ID,
  clientSecret: process.env.CLIENT_SECRET,
  grantType: 'client_credentials',
  scopes: ['marketplace.billing']
});

try {
  await xeroAppStoreClient.getClientCredentialsToken()
} catch(e) {
  console.log('ERROR: ', e)
}

const subscriptionRequest = await xeroAppStoreClient.appStoreApi.getSubscription(subscripionId)

console.log(subscriptionRequest.body)
{
  currentPeriodEnd: 2021-09-02T20:08:58.772Z,
  endDate: null,
  id: '03bc74f2-1237-4477-b782-2dfb1a6d8b21',
  organisationId: '79e8b2e5-c63d-4dce-888f-e0f3e9eac647',
  plans: [
     {
      id: '6abc26f3-9390-4194-8b25-ce8b9942fda9',
      name: 'Small',
      status: 'ACTIVE',
      subscriptionItems: [
        endDate: null,
        id: '834cff4c-b753-4de2-9e7a-3451e14fa17a',
        price: {
          id: '2310de92-c7c0-4bcb-b972-fb7612177bc7',
          amount: 0.1,
          currency: 'NZD'
        },
        product: Product {
          id: '9586421f-7325-4493-bac9-d93be06a6a38',
          name: '',
          type: 'FIXED'
        },
        startDate: 2021-08-02T20:08:58.772Z,
        testMode: true

      ]
    }
  ],
  startDate: 2021-08-02T20:08:58.772Z,
  status: 'ACTIVE',
  testMode: true
}

You should use the subscription data to provision user access/permissions to your application.

App Store Subscription Webhooks

In additon to a subscription Id being passed through the URL, when a purchase or an upgrade takes place you will be notified via a webhook. You can then use the subscription Id in the webhook payload to query the AppStore endpoints and determine what plan the user purchased, upgraded, downgraded or cancelled.

Refer to Xero's documenation to learn more about setting up and receiving webhooks or review this blogpost explaing webhooks using xero-node sdk.

https://developer.xero.com/documentation/guides/webhooks/overview/


API Clients

You can access the different API sets and their available methods through the following:

const xero = new XeroClient({
  clientId: 'YOUR_CLIENT_ID', // required
  clientSecret: 'YOUR_CLIENT_SECRET', // required
  redirectUris: [`http://localhost:${port}/callback`], // not used for client_credentials auth flow
  grantType: 'client_credentials', // only used for client_credentials auth flow
  scopes: 'openid profile email accounting.transactions offline_access'.split(" "), // not used for client_credentials auth flow
  state: 'returnPage=my-sweet-dashboard', // custom params (optional), not used for client_credentials auth flow
  httpTimeout: 3000, // ms (optional)
  clockTolerance: 10 // seconds (optional)
});

xero.accountingApi
xero.assetApi
xero.projectApi
xero.filesApi
xero.payrollAUApi
xero.payrollNZApi
xero.payrollUKApi

Helper Methods

Once you have a valid Token Set in your datastore, the next time you want to call the Xero API simply initialize a new client and refresh the token set. There are two ways to refresh a token

// you can refresh the token using the fully initialized client leveraging openid-client

import { XeroClient } from 'xero-node';

const xero = new XeroClient({
  clientId: 'YOUR_CLIENT_ID',
  clientSecret: 'YOUR_CLIENT_SECRET',
  redirectUris: [`http://localhost:${port}/callback`],
  scopes: 'openid profile email accounting.transactions offline_access'.split(" ")
});

await xero.initialize();

const tokenSet = getTokenSetFromDatabase(userId); // example function name

await xero.setTokenSet(tokenSet);

if(tokenSet.expired()){
  const validTokenSet = await xero.refreshToken();
  // save the new tokenset
}
// or if you already generated a tokenSet and have a valid (< 60 days refresh token),
// you can initialize an empty client and refresh by passing the client, secret, and refresh_token

import { XeroClient } from 'xero-node';

const tokenSet = getTokenSetFromDatabase(userId); // example function name

if(tokenSet.expired()){
  const xero = new XeroClient();
  const validTokenSet = await xero.refreshWithRefreshToken(client_id, client_secret, tokenSet.refresh_token)
  // save the new tokenset
}

A full list of the SDK client's methods:

method description
client.initialize Initializes the Xero Client with the provided configuration
client.buildConsentUrl Returns a url concatenated from the provided redirect uri, scope, and the issuer ( Xero identity authorize url)
client.apiCallback(callbackUrl) Leverages openid-client library to exchange temporary auth code for token set
client.disconnect(connectionId) Removes an individual tenant connection by connection ID
client.readTokenSet Returns token set currently set on the Xero Client
client.setTokenSet(tokenSet) Sets a specified token set on the Xero Client
client.refreshToken Leverages openid-client library to refresh token set currently set on the Xero Client and returns updated token set
client.revokeToken Revokes a users refresh token and removes all their connections to your app
client.formatMsDate(dateString) Takes a date string and returns it formatted as an MS Date
client.refreshWithRefreshToken(clientId, clientSecret, refreshToken) Refresh a token set without leveraging openid-client
client.getClientCredentialsToken Get a token set without user intervention via the client credentials grant type for custom connections only
client.updateTenants(fullOrgDetails: boolean = true) GET request to the /connections endpoint. Accepts a boolean to indicate whether or not to also make a GET request to the /organisations endpoint and map full org data to each connection object prior to returning the array of connections

Usage Examples

Accounting API

import { XeroClient, HistoryRecords, Invoice } from 'xero-node';

const xero = new XeroClient({
  clientId: 'YOUR_CLIENT_ID',
  clientSecret: 'YOUR_CLIENT_SECRET',
  redirectUris: [`http://localhost:${port}/callback`],
  scopes: 'openid profile email accounting.transactions offline_access'.split(" ")
});

await xero.initialize();

const tokenSet = getTokenSetFromDatabase(userId); // example function name

await xero.setTokenSet(tokenSet);

if(tokenSet.expired()){
  const validTokenSet = await xero.refreshToken();
  // save the new tokenset
}

await xero.updateTenants();

const activeTenantId = xero.tenants[0].tenantId;

// GET all Accounts
const getAccountsResponse = await xero.accountingApi.getAccounts(activeTenantId);

const accountId = getAccountsResponse.body.accounts[0].accountID

// GET one Account by ID
const getAccountResponse = await xero.accountingApi.getAccount(activeTenantId, accountId);

// CREATE an Invoice
const invoices = {
  invoices: [
    {
      type: Invoice.TypeEnum.ACCREC,
      contact: {
        contactID: contactId
      },
      lineItems: [
        {
          description: "Acme Tires",
          quantity: 2.0,
          unitAmount: 20.0,
          accountCode: "500",
          taxType: "NONE",
          lineAmount: 40.0
        }
      ],
      date: "2019-03-11",
      dueDate: "2018-12-10",
      reference: "Website Design",
      status: Invoice.StatusEnum.AUTHORISED
    }
  ]
};

const createdInvoicesResponse = await xero.accountingApi.createInvoices(activeTenantId, invoices)

const invoiceId = createdInvoicesResponse.body.invoices[0].invoiceID;

// CREATE a History Record
const historyRecords: HistoryRecords = {
  historyRecords: [
    {
      details: "This is a history record"
    }
  ]
};

const createdInvoiceHistoryResponse = await xero.accountingApi.createInvoiceHistory(activeTenantId, invoiceId, historyRecords);

// CREATE Attachment
const filename = "xero-dev.png";
const pathToUpload = path.resolve(__dirname, "../public/images/xero-dev.png");
const readStream = fs.createReadStream(pathToUpload);
const contentType = mime.lookup(filename);

const accountAttachmentsResponse = await xero.accountingApi.createInvoiceAttachmentByFileName(activeTenantId, invoiceId, filename, readStream, {
  headers: {
    'Content-Type': contentType
  }
});

SDK conventions

Querying & Filtering

const activeTenantId = 'XERO_TENANT_ID';
const ifModifiedSince: Date = new Date("2020-02-06T12:17:43.202-08:00");
const where = 'Status=="AUTHORISED" AND Type=="SPEND"';
const order = 'Reference ASC';
const page = 1;
const unitdp = 4;

const response = await xero.accountingApi.getBankTransactions(activeTenantId, ifModifiedSince, where, order, page, unitdp);

Note that you should set the query param to undefined instead of null if you wish to ignore a specific filter.

const purchaseOrders = xero.accountingApi.getPurchaseOrders(tenant.tenantId, null, null, '2021-01-01', '2021-04-25', null, 1);

// http://api-oauth2.xero.com/api.xro/2.0/PurchaseOrders?Status=&DateFrom=2008-01-01&DateTo=2021-04-25&order=&page=1
// "Status=&" is breaking the above query
// purchaseOrders will be an empty array

const purchaseOrders = xero.accountingApi.getPurchaseOrders(tenant.tenantId, undefined, undefined, '2021-01-01', '2021-04-25', undefined, 1);

// http://api-oauth2.xero.com/api.xro/2.0/PurchaseOrders?DateFrom=2008-01-01&DateTo=2021-04-25&order=&page=1
// params are omitted
// purchaseOrders array will have results now

Security

This repo leverages a certified OA2 and OIDC library called openid-client. For a deeper dive the repo's functionality, check out them directly https://github.com/panva/node-openid-client.

Preventing CSRF Using Xero-Node

When xero.buildConsentUrl is called we call openid-client authorizationUrl method, passing redirect_uri, scope, and state (if present) as arguments and returns a formatted url string made up from the given config. The user is then directed to the consentUrl to begin the auth process with Xero. When the auth process is complete Xero redirects the user to the specified callback route and passes along params including the state if it was initially provided. At this point openid-client takes over verifying params.state and check.state match if provided. If the state does not match the initial user's, the openid-client library throws an error:

RPError: state mismatch, expected user=1234, got: user=666

JWT Verification Using Xero-Node

JWT verification of both the access_token and id_token are baked into the openid-client library we leverage. When xero.apiCallback is called, openid-client validateJARM is triggered which also invokes validateJWT. If openid-client fails to validate the JWT signature it will throw an error.


Contributing

PRs, issues, and discussion are highly appreciated and encouraged. Note that the majority of this project is generated code based on Xero's OpenAPI specs - PR's will be evaluated and pre-merge will be incorporated into the root generation templates.

Please add tests for net new functionality and ensure existing test suite passes all tests.

npm test

Versioning

We do our best to keep OS industry semver standards, but we can make mistakes! If something is not accurately reflected in a version's release notes please let the team know.

Participating in Xeroโ€™s developer community

This SDK is one of a number of SDKโ€™s that the Xero Developer team builds and maintains. We are grateful for all the contributions that the community makes.

Here are a few things you should be aware of as a contributor:

  • Xero has adopted the Contributor Covenant Code of Conduct, we expect all contributors in our community to adhere to it
  • If you raise an issue then please make sure to fill out the github issue template, doing so helps us help you
  • Youโ€™re welcome to raise PRs. As our SDKs are generated we may use your code in the core SDK build instead of merging your code
  • We have a contribution guide for you to follow when contributing to this SDK
  • Curious about how we generate our SDKโ€™s? Have a read of our process and have a look at our OpenAPISpec
  • This software is published under the MIT License

For questions that arenโ€™t related to SDKs please refer to our developer support page.

xero-node's People

Contributors

andrew-connell avatar borisno2 avatar brucem1976 avatar bryanlloydtee avatar dannyvincent avatar davefinster avatar davidbanham avatar dependabot[bot] avatar dupski avatar eteroa123 avatar graceybr avatar guillegette avatar hnfmr avatar iamam34 avatar jordanwalsh23 avatar manisht72 avatar manisht72x avatar parisholley avatar philals avatar raghunath-s-s-j avatar rdemarco-xero avatar rettbehrens avatar sangeet-joy-tw avatar serknight avatar sidneyallen avatar timnz avatar tinovyatkin avatar tnzzz avatar twrayden avatar warp avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

xero-node's Issues

Tracking on LineItems not working

Have been trying to structure the JSON correctly to submit a TrackingCategory option on a LineItem as part of a .newInvoice() call, but can not seem to make anything work. Adhering to the Schemas makes it look like it expects a format like:

{
     ...
     LineItems: [{
        ...
        Tracking: [{
              TrackingCategory: {
                   Name: 'Region',
                   Option: 'Eastside',
                   TrackingCategoryID: (UUID),
              }
         }]
     }]
}

The request is being accepted and the invoice appears in Xero, but the Tracking information is missing, there are no errors or status messages being returned.

Not sure if the issue is just on my end with this problem, but I'd be happy to submit a PR adding to the documentation if I could get some help on this one.

I'm wondering if this is possibly linked to #22 ? Is it a limitation to be able to send Tracking?

add contact api

Hello,
Please help , how to add address in xero add contact api.

using without sessions (node on server)

Hi,
Thanks for this library. I have a client rendered app and a node backend, and I've gotten to a point where I can follow the example app to the point of getting a session authenticated.

However, I'm using firebase cloud functions and working from realtime database triggers, and so don't have anything the equivalent of an express session.

My question is can I call these helper methods and pass them the oauth token each time as an additional parameter, rather than assuming an authenticated session?

This may be documented somewhere, but as of yet I haven't been able to find it so i thought I'd ask.

Thanks

xero client does not initialize as private

The application.js does not initialize a private client properly (var xeroClient = new xero.PrivateApplication(config);). It looks like _consumerSecret contains the privatekey.pem.


var PrivateApplication = Application.extend({
constructor: function(config) {
console.log('-- ---- --- --- ---');
console.log(config);
Application.call(this, Object.assign({}, config, { type: 'private' }));
},
init: function() {
Application.prototype.init.apply(this, arguments);
console.log('arguments');
console.log(arguments)
var rsaPrivateKey = this.options.privateKey;
this.oa = new OAuth(
null,
null,
this.options.consumerKey,
rsaPrivateKey,

"1.0a",
null,
"RSA-SHA1",
null, { 'User-Agent': this.options.userAgent }
);
this.options.accessToken = this.options.consumerKey;
this.accessSecret = this.options.consumerSecret;
console.log('---- ---- ---- ---');
console.log(this);
}

});

oa:
2|CF | { _isEcho: false,
2|CF | _requestUrl: null,
2|CF | _accessUrl: null,
2|CF | __consumerKey: 'NEZT1VOJFPUYHNUS4107NPIHE0DWUT',
2|CF | consumerSecret: '-----BEGIN%20RSA%20PRIVATE%20KEY-----
%0AMIICXQIBAAKBgQCc9IfBmPpDFBpQn%2B5dqdy6m8I3XrCci8nD1YY9cTTVNzKGNYmI%0A2X0h3UiK1miXffzGoeVRdF3Oxp3BVxCOCRdRob4k%2BFeYpKC%2FWET9lyr%2F0Hi2MvOH%0A37VT5ezGjxcwYvkSZud0s4JYZqmAuGkj4a9zwMWhVUJ3vOZY9Qe5fROfewIDAQAB%0AAoGAOrTYBliy2t4tDZUUcmIzlnTmCxHW22cJ278FC%2FxI96tCsaJaxB4dSMpc9NlH%0Am8pa%2FuzVOkddQxdVMFjhb08FMkRgK5AZ6TwqvAR2bizqgewmoQLY7BeypwC2MScQ%0A52J852iSxZ6Y10Ad5vsnaURz0o6%2Be0uOcuNvF%2FO5%2FfypREkCQQDNHgRch0xwGN%2Bn%0A9Idf5dc9yUx0X0blZiob8FKTlwoYSBHbO2N2sgsk3ZUnWY9NAjLQb4I9WnnLEOkI%0Aacoa7f1NAkEAw%2BP6ois0TKJwoZLtd0VWdF9iIE871%2FP2vNik2DYsixUn4Yvw%2BVLI%0AoDQiXN7zi1w7vhUJnF%2BbQWGwRKfb%2FljL5wJAHqABtLL6xa3hDtmisL3HYlFA2gsz%0AsaKMXPmHysN9XVy9VcdLNXil005GK8bib3QJlDdh8bklgB05PelVpKmt7QJBAImB%0AmwoDMe8ph86jiLXUol%2BGJSdw9x0cT%2FM4UF7FiHcJHrmgtkMN7W8nqYdvqZ4F3JoN%0AwnVWV3GJ1WYcPr1IL7kCQQCVElTqboxrBbckERsT3Ws8JK%2FGQdlHPckqi4D4hS6z%0AZ2GPaVKE1mmj6MPXaxdW2D0q1YG%2FhPo%2BDTLXj%2FRpyQWr%0A-----END%20RSA%20PRIVATE%20KEY-----%0A',
2|CF | _privateKey: <Buffer 2d 2d 2d 2d 2d 42 45 47 49 4e 20 52 53 41 20 50 52 49 56 41 54 45 20 4b 45 59 2d 2d 2d 2d 2d 0a 4d 49 49 43 58 51 49 42 41 41 4b 42 67 51 43 63 39 49 ... >,
2|CF | _version: '1.0a',
2|CF | _authorize_callback: 'oob',
2|CF | _signatureMethod: 'RSA-SHA1',
2|CF | _nonceSize: 32,
2|CF | _headers:

BankTransactions function 'newBankTransactions' is not consistent

The function provided by the BankTransactions helper newBankTransactions creates a map of BankTransaction objects and the save() function needs to be called on each.

For efficiency, this should instead provide a saveAll() function that allows multiple BankTransactions to be saved in a single API call.

creditnote create issue

Hello Team,

I am creating credit note with creditnotenumber but it is creating a new creditnotenumber in xero ,below is the sample of creditsample, which i am posting to xero. please check and let me know the solution:

  var sampleCreditNote= { 
  Type: 'ACCRECCREDIT',
  Contact: { ContactID: 'f24bcab4-e610-4543-bfe1-b881a2bcdc35' },
  Date: '2017-09-20',
  CurrencyCode: 'AUD',
  CreditNoteNumber:'CRN000003',
  SentToContact: true,
  Status: 'SUBMITTED',
  LineItems: 
   [ { Description: '4 Jul 2007 AUD013915 FFS AV&#39;s &amp; SM Kelly',
       Quantity: 1,
       UnitAmount: 0,
       AccountCode: '010',
       TaxType: 'OUTPUT',
       TaxAmount: '0.00' },
     { Description: '1 Aug 2014 AUD000075 FFS Bruno Cuda[1]',
       Quantity: 1,
       UnitAmount: 0,
       AccountCode: '010',
       TaxType: 'OUTPUT',
       TaxAmount: '0.00' },
     { Description: '3 Jan 2017 AUD000152 FFS Alandale Fruits',
       Quantity: 1,
       UnitAmount: 0,
       AccountCode: '010',
       TaxType: 'OUTPUT',
       TaxAmount: '0.00' },
     { Description: '21 Mar 2017 AUD000179 FFS Imperial Produce Pty Ltd',
       Quantity: 1,
       UnitAmount: 0,
       AccountCode: '010',
       TaxType: 'OUTPUT',
       TaxAmount: '0.00' },
     { Description: 'desc',
       Quantity: 1,
       UnitAmount: 0,
       AccountCode: '010',
       TaxType: 'OUTPUT',
       TaxAmount: '0.00' },
     { Description: 'azva',
       Quantity: 1,
       UnitAmount: 0,
       AccountCode: '054',
       TaxType: 'OUTPUT',
       TaxAmount: '0.00' },
     { Description: 'ada',
       Quantity: 1,
       UnitAmount: 0,
       AccountCode: '054',
       TaxType: 'OUTPUT',
       TaxAmount: '0.00' },
     { Description: 'sb',
       Quantity: 1,
       UnitAmount: 0,
       AccountCode: '054',
       TaxType: 'OUTPUT',
       TaxAmount: '0.00' },
     { Description: 'sb',
       Quantity: 1,
       UnitAmount: 0,
       AccountCode: '054',
       TaxType: 'OUTPUT',
       TaxAmount: '0.00' },
     { Description: 'snsb',
       Quantity: 1,
       UnitAmount: 0,
       AccountCode: '010',
       TaxType: 'OUTPUT',
       TaxAmount: '0.00' },
     { Description: 'dfnd',
       Quantity: 1,
       UnitAmount: 0,
       AccountCode: '260',
       TaxType: 'OUTPUT',
       TaxAmount: '0.00' } ] }

please test it, I really need help.

Thankyou

Incorrect number of Contacts return from getContacts() with parameter 'IDs'

Hi,

Here is my environment

Node version: v6.11.1
npm version: v3.10.10
xero-node: v2.5.2

Based on API doc of Contacts, the GET method should be able to retrieve a specific set of contacts in a single call with parameter 'IDs'.

So it should be work like this

xeroClient.core.contacts.getContacts({
  IDs: "contact-uuid-1,contact-uuid-2"
});

But this call return all contacts to me.

So I tried the following call

xeroClient.core.contacts.getContacts({
  IDs: [
    "contact-uuid-1",
    "contact-uuid-2"
  ]
});

And it still return all the contacts.

Remove schemaobject.js and replace with 'schema-object' package on npm.

Just thinking about the reason for validating the objects returned from Xero against a schema.

Is it just to know if there has been an error?

Could there just be a check on the status code returned/validation key in the response, then then throw if not 2xx or if validation value != null ?

Eg.. The Accounts class could still (I think) have all it's functionality without having to maintain/create schemas.

Just wondering your thoughts. Cheers.

Issue on AWS Lambda, but works locally

Hello,

I tried to fetch a contact (XeroClient.core.contacts.getContact(ContactId) ) and it worked locally. I uploaded this code to AWS Lambda and I got the error below:

2017-10-19T19:35:05.722Z 9ac66835-b504-11e7-a7a9-bb98e4bdb519 SyntaxError: Unexpected token ... at exports.runInThisContext (vm.js:53:16) at Module._compile (module.js:373:25) at Object.Module._extensions..js (module.js:416:10) at Module.load (module.js:343:32) at Function.Module._load (module.js:300:12) at Module.require (module.js:353:17) at require (internal/module.js:12:17) at Object.<anonymous> (/var/task/node_modules/xero-node/lib/entity_helpers/accounting/organisations.js:3:20) at Module._compile (module.js:409:26) at Object.Module._extensions..js (module.js:416:10) at Module.load (module.js:343:32) at Function.Module._load (module.js:300:12) at Module.require (module.js:353:17) at require (internal/module.js:12:17) at /var/task/node_modules/xero-node/lib/core.js:43:27 at /var/task/node_modules/xero-node/node_modules/lodash/lodash.js:4944:15 at baseForOwn (/var/task/node_modules/xero-node/node_modules/lodash/lodash.js:3001:24) at /var/task/node_modules/xero-node/node_modules/lodash/lodash.js:4913:18 at Function.forEach (/var/task/node_modules/xero-node/node_modules/lodash/lodash.js:9359:14) at new Core (/var/task/node_modules/xero-node/lib/core.js:42:5) at Application (/var/task/node_modules/xero-node/lib/application.js:43:16) at new Application.extend.constructor (/var/task/node_modules/xero-node/lib/application.js:562:21) at module.exports.handler.client.core.contacts.getContact.then.data.catch (/var/task/lambda/notifications/request_test.js:18:15)

Unable to create invoice

Hello,

I am not able to create invoice. I am getting response like below:
{ Error: PUT call failed with: 400
at /Users/macmini/Documents/myds-node/node_modules/xero-node/lib/application.js:145:42
at passBackControl (/Users/macmini/Documents/myds-node/node_modules/xero-node/lib/oauth/oauth.js:406:25)
at IncomingMessage. (/Users/macmini/Documents/myds-node/node_modules/xero-node/lib/oauth/oauth.js:422:17)
at emitNone (events.js:91:20)
at IncomingMessage.emit (events.js:185:7)
at endReadableNT (_stream_readable.js:974:12)
at _combinedTickCallback (internal/process/next_tick.js:80:11)
at process._tickCallback (internal/process/next_tick.js:104:9)
data:
{ ErrorNumber: 10,
Type: 'ValidationException',
Message: 'A validation exception occurred',
Elements: [ [Object] ] } }

Please provide solution.

SentToContact not a Full Boolean its True or Unset

I don't know if this is best here, or if there is another way to give feedback for documentation to be updated. This is really for the API in general, and not specifically the node-js implementation, but it does impact things here.

The API Docs state that SentToContact is a Boolean.

This is half-true. It is set to "True" if the Invoice/Payable has been sent to the contact. It is not set at all if it hasn't been set.

As a result the following will not give you the expected results:

xero.core.invoices.getInvoices(
        {
            where: 'Type="ACCREC" AND SentToContact=false'
        }

And you instead need:

xero.core.invoices.getInvoices(
        {
            where: 'Type="ACCREC" AND SentToContact!=true'
        }

At a bare minimum it would be good if there were some examples in the sample app that showed how to do this so it could be looked up. Right now its not covered.

error response Handling

Hello,

when i am calling create contacts api with same data , i am getting response like:

{ Error: GET call failed with: 404
at self.oa.get.stream (/Users/macmini/Documents/myds-node/node_modules/xero-node/lib/application.js:327:36)
at passBackControl (/Users/macmini/Documents/myds-node/node_modules/xero-node/lib/oauth/oauth.js:406:25)
at IncomingMessage. (/Users/macmini/Documents/myds-node/node_modules/xero-node/lib/oauth/oauth.js:422:17)
at emitNone (events.js:91:20)
at IncomingMessage.emit (events.js:185:7)
at endReadableNT (_stream_readable.js:974:12)
at _combinedTickCallback (internal/process/next_tick.js:80:11)
at process._tickCallback (internal/process/next_tick.js:104:9)
data: { 'The resource you're looking for cannot be found': '' } }

I know, why it is coming but i don't want see any other information on terminal[mac] except data part['The resource you're looking for cannot be found']. please help me.

Multi item push fails

Multi item push fails with the following error:

Mongoose model 'error' event fired on 'Invoice' with error:
 entity.toXml is not a function TypeError: entity.toXml is not a function
    at /Users/joshuacalder/Development/xero-ucrm-sync/node_modules/xero-node/lib/entity_helpers/entity_helper.js:29:27
    at arrayMap (/Users/joshuacalder/Development/xero-ucrm-sync/node_modules/lodash/lodash.js:660:23)
    at Function.map (/Users/joshuacalder/Development/xero-ucrm-sync/node_modules/lodash/lodash.js:9571:14)
    at constructor.saveEntities (/Users/joshuacalder/Development/xero-ucrm-sync/node_modules/xero-node/lib/entity_helpers/entity_helper.js:28:29)
    at constructor.saveInvoices (/Users/joshuacalder/Development/xero-ucrm-sync/node_modules/xero-node/lib/entity_helpers/accounting/invoices.js:30:21)

Reversing payments

Is there a way to reverse payments on an invoice through this SDK? Tried to do the following:

    let paymentId = invoice.Payments[0].PaymentID
    let payment = await accountingClient.core.payments.getPayment(paymentId)
    payment.Status = 'DELETED'
    payment.save()

but that's returning a ValidationError: "Message":"Only Status accepted for updates."

Attachments with `&` in the file name can't be downloaded

When saving attachments if the filename for the attachment has an & in it, the attachment will not be able to be retrieved from Xero.

Workaround: modify the filename prior to uploading so that all ampersand characters are removed.

What is the proper way to use createdByMyApp for Invoices

The Xero API documentation lists "createdByMyApp" as an optional parameter.

However, I can't seem to figure out the proper syntax for using it. The following throws an error:

        xero.core.invoices.getInvoices({
            where: 'Status="DRAFT" AND createdByMyApp=true',
        })

The error:

{ ErrorNumber: 16,
     Type: 'QueryParseException',
     Message: 'No property or field \'createdByMyApp\' exists in type \'Invoice\'' } }
(node:20984) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: GET call failed with: 400

While the following doesn't do anything:

        xero.core.invoices.getInvoices({
            where: 'Status="DRAFT"',
            createdByMyApp: true
        })

How to void unallocated Credit Note

I'm getting Xero internal error while trying to Void unallocated credit note (that's possible via web). Tried POST to /CreditNotes/{creditNoteId} with { Status: 'VOIDED' } or { Status: 'DELETED' } in both cases got validation error as response with Xero internal error as text...
Is I'm doing something wrong or API?

Invalid Private Key PEM Swallows Error Rather than Returning the Error

I imagine this is true for any of the hashing in _createSignature, but specifically line 224-227 of lib/oauth/oauth.js will completely swallow the error thrown back from Crypto, rather than returning an error:

} else if (this._signatureMethod == "RSA-SHA1") {
        key = this._privateKey || "";
        hash = crypto.createSign("RSA-SHA1").update(signatureBase).sign(key, 'base64');

Specifically Crypto is expecting the RSA Key to be surrounded with:

-----BEGIN RSA PRIVATE KEY-----
-----END RSA PRIVATE KEY-----

This is relatively clear if you get the message from Crypto:
error:0906D06C:PEM routines:PEM_read_bio:no start line

As it is now though, you get no output and nothing happens. This isn't too helpful.

Reproduction Steps:

  1. Set your privateKey to a value that does not have the RSA Start Line Headers.
  2. Attempt to call a Xero function (e.g. I called core.contacts.getContacts).

NOTE: Nothing happens and no error is returned to explain why.

Full stack of the error that is lost (Line numbers might be slightly off since I added some debugging to find this. The original line number listed above should be the offending code:

Error: error:0906D06C:PEM routines:PEM_read_bio:no start line
    at Error (native)
    at Sign.sign (crypto.js:282:26)
    at exports.OAuth._createSignature (\node_modules\xero-node\lib\oauth\oauth.js:228:70)
    at exports.OAuth._getSignature (\node_modules\xero-node\lib\oauth\oauth.js:105:17)
    at exports.OAuth._prepareParameters (\node_modules\xero-node\lib\oauth\oauth.js:319:20)
    at exports.OAuth._performSecureRequest (\node_modules\xero-node\lib\oauth\oauth.js:329:34)
    at exports.OAuth.get (\node_modules\xero-node\lib\oauth\oauth.js:509:17)
    at getResource (\node_modules\xero-node\lib\application.js:313:25)
    at \node_modules\xero-node\lib\application.js:275:25
    at process._tickCallback (internal/process/next_tick.js:103:7)

AddressSchema / AddressDetailsSchema issue

on xero-node/lib/entities/shared.js you have:

module.exports.AddressSchema = Entity.SchemaObject({ Address: { type: AddressDetailsSchema, toObject: 'always' } });

However this additional object "Address" breaks the GET/POST with Xero. To get it to work I did:

module.exports.AddressSchema = AddressDetailsSchema;

Bypassing this extra Address object. I didn't sent a PR as I don't know if this breaks other components such as "employees" in the payroll. But definitely to create/update/retrieve Contacts, this was the way to make it work. Hope this helps.

Multi-Payment Put

@jordanwalsh23 I have been playing around with Payments this morning and just had a couple of questions happy to put in a PR to solve any of the issues listed:

  • Firstly I notice that payments use createPayment instead of what seams the standard, and documented newPayment, which is preferred? Happy to do a PR to either fix the Docs or bring the payment entity to align with the docs - assuming though createPayment might still need to be kept in case people are using it?
  • There is no current savePayments function to bulk save payments, I have got this working for me, happy to do a PR, probably just need to above question answered first though..

Thanks,
Josh

CI

I think @dupski (Briggs) has been looking into a CI tool.

@dupski or @jordanwalsh23 do you have any thoughts?

Then we could have the tool run against each PR

Attachments returning a 401 signature_invalid error

I'm trying to use the attachment endpoints which are in this wrapper, and I'm posting this with the understanding they're not properly supported yet โ€“ but thought someone might be able to help.

I can't seem to get around this error:

I've read the code and am posting like this:

      let stats = fs.statSync(`/tmp/myfile.pdf`);

       // upload the attachment
        let attachment = xeroClient.core.attachments.newAttachment({
            MimeType: 'application/pdf',
            FileName: `myfile.pdf`,
            ContentLength: stats.size
        })

        return attachment.save(`/Invoices/${invoice.response.Id}`, `/tmp/myfile.pdf`)

The return is consistently this:

{ Error: POST call failed with: 401
    at /user_code/node_modules/xero-node/lib/application.js:129:42
    at passBackControl (/user_code/node_modules/xero-node/lib/oauth/oauth.js:400:25)
    at IncomingMessage.<anonymous> (/user_code/node_modules/xero-node/lib/oauth/oauth.js:416:17)
    at emitNone (events.js:91:20)
    at IncomingMessage.emit (events.js:185:7)
    at endReadableNT (_stream_readable.js:974:12)
    at _combinedTickCallback (internal/process/next_tick.js:74:11)
    at process._tickDomainCallback (internal/process/next_tick.js:122:9)
  data: 
   { oauth_problem: 'signature_invalid',
     oauth_problem_advice: 'Failed to validate signature' } }

I've tried the endpoint using another wrapper (npm/xero) and I'm getting the same error, I'm wondering if neither support signing for attachments โ€“ or am I doing something wrong?

Invoice-contactid issue

Hello,

when i have blank contactID in invoice , i am not getting proper error in xero node:
i am receiving exception like this :(node:1014) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: Cannot read property '0' of undefined

please let me know , how can i get proper error in catch block. its very urgent issue,which i am facing.

Attachments content is undefined on PartnerApplication

When I use the Public Application and invoke the following code:

xeroClient.core.invoices.getInvoice(invoiceID)
        .then(function(invoice) {
            invoice.getAttachments()
                .then(function(attachments) {
                    //Get the reference to the attachment object
                    var myAttachment = attachments[0];

                    //Create a local writestream
                    var wstream = fs.createWriteStream('temp.pdf', { defaultEncoding: 'binary' });

                    wstream.on('finish', function() {
                        //Data has been written successfully
                        res.send('done');
                    });

                    wstream.on('error', function(err) {
                        console.log('data writing failed',err);
                        wstream.close();
                    });

                    myAttachment.getContent(wstream)
                        .catch(function(err) {
                            console.log('Some error occurred: ', err);
                });
        });

Everything works fine and the invoice's attachment is written locally.

However when I upgrade to a Partner Application. The file is empty. It's strange because I can log the file size and that is correct, it's just the actual file that is empty.

Maybe I'm missing something?

Get Invoice api issue

Hello,

I am not receiving any response while using 'Retrieving invoices by using Invoice numbers':
```
var myInvoiceNumbers = ['TC-001','INV000044'];
xeroClient.core.invoices.getInvoices({
params: {
InvoiceNumbers: myInvoiceNumbers.toString()
}
}).then(function(invoices) {
console.log(invoices);
invoices.forEach(function(invoice){
//do something useful
console.log(invoice); //ACCPAY
});
}).catch(function(err) {
//Some error occurred
console.log(err);
});

I am not getting any response . please let me know the solution.

log4js

I'm having the same issue as outlined here: NaturalNode/natural#320

Looks like log4js has a bunch of require statements but does not have the dependancies in package.json
image

Looks like log4js got a major overhaul since NZTim was working on the module and is now log4js-node but has the same npm name (log4js).

I tried updating to the latest version (2.2.0) but this broke the build as there was a/some API changes.

Making an Issue to track the bug. I have not done any active work on getting 2.2.0 to work in the project. Anyone else is welcome to give it a go.

Webpack output:

` ERROR in ./node_modules/log4js/lib/appenders/loggly.js
Module not found: Error: Can't resolve 'loggly' in 'C:\Users\phil.alsford\Desktop\project\node_modules\log4js\lib\appenders'
@ ./node_modules/log4js/lib/appenders/loggly.js 3:11-28
@ ./node_modules/log4js/lib/appenders ^./.*$
@ ./node_modules/log4js/lib/log4js.js
@ ./node_modules/xero-node/lib/logger.js
@ ./node_modules/xero-node/lib/index.js
@ ./src/server.ts

ERROR in ./node_modules/log4js/lib/appenders/mailgun.js
Module not found: Error: Can't resolve 'mailgun-js' in 'C:\Users\phil.alsford\Desktop\project\node_modules\log4js\lib\appenders'
 @ ./node_modules/log4js/lib/appenders/mailgun.js 34:14-35
 @ ./node_modules/log4js/lib/appenders ^\.\/.*$
 @ ./node_modules/log4js/lib/log4js.js
 @ ./node_modules/xero-node/lib/logger.js
 @ ./node_modules/xero-node/lib/index.js
 @ ./src/server.ts

ERROR in ./node_modules/log4js/lib/appenders/slack.js
Module not found: Error: Can't resolve 'slack-node' in 'C:\Users\phil.alsford\Desktop\project\node_modules\log4js\lib\appenders'
 @ ./node_modules/log4js/lib/appenders/slack.js 2:12-33
 @ ./node_modules/log4js/lib/appenders ^\.\/.*$
 @ ./node_modules/log4js/lib/log4js.js
 @ ./node_modules/xero-node/lib/logger.js
 @ ./node_modules/xero-node/lib/index.js
 @ ./src/server.ts

ERROR in ./node_modules/log4js/lib/appenders/smtp.js
Module not found: Error: Can't resolve 'nodemailer' in 'C:\Users\phil.alsford\Desktop\project\node_modules\log4js\lib\appenders'
 @ ./node_modules/log4js/lib/appenders/smtp.js 4:13-34
 @ ./node_modules/log4js/lib/appenders ^\.\/.*$
 @ ./node_modules/log4js/lib/log4js.js
 @ ./node_modules/xero-node/lib/logger.js
 @ ./node_modules/xero-node/lib/index.js
 @ ./src/server.ts

`

Get Invoice API returns throws error rather than returning 404 code.

Get Call fails with error:

This is the error response.

{ Error: GET call failed with: 404 at self.oa.get.stream (/home/keyur/Documents/Git/xeroAPI/node_modules/xero-node/lib/application.js:327:38) at passBackControl (/home/keyur/Documents/Git/xeroAPI/node_modules/xero-node/lib/oauth/oauth.js:406:25) at IncomingMessage.<anonymous> (/home/keyur/Documents/Git/xeroAPI/node_modules/xero-node/lib/oauth/oauth.js:422:17) at emitNone (events.js:91:20) at IncomingMessage.emit (events.js:188:7) at endReadableNT (_stream_readable.js:975:12) at _combinedTickCallback (internal/process/next_tick.js:80:11) at process._tickCallback (internal/process/next_tick.js:104:9) data: { 'The resource you\'re looking for cannot be found': '' } }

When calling newBankTransactions

When calling xeroClient.core.bankTransactions.newBankTransactions(bankTransactions) I am receiving the following error TypeError: xeroClient.core.bankTransactions.newBankTransactions is not a function

PDF download of invoices

I was already discombobulated after I learned that there is no way to send invoices per email via the API. (Are you sure this is not an absolute core functionality you should offer these days?)

Is there a way to at least download the branded invoice as a PDF via the API so that I can use a mail service to send the invoice?

Unexpected token o in JSON at position 0

I'm receiving the following error after trying to use the client to get all items. Here is the following error stack trace.

SyntaxError: Unexpected token o in JSON at position 0
    at Object.parse (native)
    at self.oa.get.stream (/user_code/node_modules/xero-node/lib/application.js:303:33)
    at passBackControl (/user_code/node_modules/xero-node/lib/oauth/oauth.js:406:25)
    at IncomingMessage.<anonymous> (/user_code/node_modules/xero-node/lib/oauth/oauth.js:422:17)
    at emitNone (events.js:91:20)
    at IncomingMessage.emit (events.js:185:7)
    at endReadableNT (_stream_readable.js:974:12)
    at _combinedTickCallback (internal/process/next_tick.js:80:11)
    at process._tickDomainCallback (internal/process/next_tick.js:128:9)

data = JSON.parse(data)

Nested items aren't being translated to their correct types

Currently for objects that have a nested schema (e.g. TaxRate -> TaxComponent, or Invoice -> Payments), the nested schema isn't being translated from the XML into it's appropriate type.

e.g.

//Tax Component Schema
var TaxComponentSchema = new Entity.SchemaObject({
    Name: { type: String, toObject: 'always' },
    Rate: { type: String, toObject: 'always' },
    IsCompound: { type: Boolean }
});

//Get TaxComponents Test
currentApp.core.taxrates.getTaxRates()
    .then(function(taxRates) {
        var taxRate = taxRates[0];
        _.each(taxRate.TaxComponents, function(taxComponent) {
                //This test fails as taxComponent.IsCompound is a 'String' not a 'Boolean'.
                expect(taxComponent.IsCompound).to.be.oneOf([true, false]);
        });
    });

Fix is to ensure that the marshalling respects the types for nested objects.

Issue with Contacts schema

I've lost some reasonable time on this.

On: lib/entities/accounting/contact.js
At: var ContactSchema = new Entity.SchemaObject

A lot of fields does not contain the 'toObject' property. I was trying to create a contact with EmailAddress and I could not until I've added the "toObject: 'hasValue'" to it. Looks like all fields requires this property.

Codestyle PRs

I just took a look for the library source code and wondering why the code-style is so strange, mixed and outdated. Your node version requirement in package.json is current LTS (6.0) and that's great, but platform/language features usage seems to be targeting something like node 0.12.

For example: https://github.com/XeroAPI/xero-node/blob/master/lib/application.js - you have const within function declarations, but missing use strict anywhere and top level variables declared as var.

Question is: will you accept refactoring pull requests? I'm particularly interesting in getting rid of lodash as it's huge stone age dependency...

getInvoices with pager does not include Tracking in LineItems

Node version: 6.10.2
npm version: 3.10.10

What I did

        xeroClient.core.invoices.getInvoices({
          where: 'Status!="PAID" AND Status!="VOIDED" AND Type=="ACCREC"',
          pager: {
            start: 1,
            callback: (err, response, cb) => {
              if (err) { throw err; }
              cb(response.data);
            },
          },
        })

What I got

[... 
  {... 
     LineItems: [...
       {...
         Tracking: [{}]
       }
     ]
  }
]

What I get on Xero API Preview for same request & invoice & line item

<Tracking>
    <TrackingCategory>
        <Name>Customer Contract</Name>
        <Option>***</Option>
        <TrackingCategoryID>***</TrackingCategoryID>
    </TrackingCategory>
    <TrackingCategory>
        <Name>Department</Name>
        <Option>Engineering</Option>
        <TrackingCategoryID>***</TrackingCategoryID>
    </TrackingCategory>
</Tracking>

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.