Coder Social home page Coder Social logo

oyvindkinsey / easyxdm Goto Github PK

View Code? Open in Web Editor NEW
2.4K 91.0 265.0 10.99 MB

A javascript library providing cross-browser, cross-site messaging/method invocation.

Home Page: http://easyxdm.net

License: MIT License

Shell 1.25% JavaScript 25.51% ActionScript 0.94% CSS 21.14% Batchfile 2.72% HTML 14.17% Perl 1.16% Python 0.39% XSLT 32.56% ASP.NET 0.16%

easyxdm's Introduction

easyXDM - easy Cross-Domain Messaging

easyXDM is a Javascript library that enables you as a developer to easily work around the limitation set in place by the Same Origin Policy, in turn making it easy to communicate and expose javascript API's across domain boundaries.

Warning This repository is not actively maintained as the services it provides is supported natively by all browsers.

Some of the goals for the project are that it should

  • be easy to use!!!
  • be self contained, no dependencies (Now requires Flash for the FlashTransport) (not counting JSON)
  • be light weight
  • be flexible
  • have good code quality (uses jslint etc)
  • have good documentation
  • be the best xdm-library in existence

How easyXDM works

At the core easyXDM provides a transport stack capable of passing string based messages between two windows, a consumer (the main document) and a provider (a document included using an iframe). It does this by using one of several available techniques, always selecting the most efficient one for the current browser.
For all implementations the transport stack offers bi-directionality, reliability, queueing and sender-verification.

Using JavaScript only (no Flash, Silverlight, extra html files etc) easyXDM provides the following browsers with stacks with latency of less than 15ms:

  • IE8+ - using the PostMessageTransport
  • Opera 9+ - using the PostMessageTransport (support for both Operas old standard and the HTML5 standard)
  • Firefox 1-2 - using the FrameElementTransport
  • Firefox 3+ - using the PostMessageTransport
  • Safari 4+ - using the PostMessageTransport
  • Chrome 2+ - using the PostMessageTransport

In browsers not mentioned here, and not supporting the postMessage API, the following transports will be used, depending on support and configuation:

  • FlashTransport - Requires Flash 6+ and the swf property to be configured and will load a single swf into the document that functions as a factory. The swf has been audited by Google Security researchers.
  • NameTransport - Requires an html-file (name.html) to be hosted on each of the two domain. The cache-directives in the file allows the transport to pass messages with speeds similar to postMessage without incurring extra HTTP-requests.
  • HashTransport - If no other transport can be used, then the HashTransport will be used.

How to use easyXDM

When using easyXDM you first load the consumer document and then let easyXDM load the provider. This is by default done in a hidden iframe, but you can also configure easyXDM to display the iframe in a specific container, and with a specific style attached.

The library provides two main object types that utilize this transport stack:

The easyXDM.Socket

.. is a thin wrapper around the transport stack and lets you send strings between the consumer and the provider.

To set up a simple Socket this is what you will need to add to the consumer

    var socket = new easyXDM.Socket({
        remote: "http://path.to/provider/", // the path to the provider
        onMessage:function(message, origin) {
            //do something with message
        }
    });

And this is what's needed for the provider

    var socket = new easyXDM.Socket({
        onMessage:function(message, origin) {
            //do something with message
        }
    });

Use this for sending the strings to the other end:

    socket.postMessage("hello world");

In addition the following config properties can be set for both consumer and provider

  • onReady - If you set this to a function, then this will be called once the communication has been established.
  • local {String} - To enable the NameTransport as a fallback, set this to point to the name.html file on the current domain.
  • swf {String} - To enable the FlashTransport for IE6/7 you need to point this towards your easyxdm.swf file. The swf must reside on one of the two domains (consumer and provider can use its own copy), or on a shared CDN used by both the consumer and provider.
  • swfNoThrottle {Boolean} - Set this to true if you want to have the swf/iframe placed visibly (20x20px top right corner) in order to avoid being throttled in newer versions of Flash
  • swfContainer {String || DOMElement) - Set this if you want to control where the swf is placed.

These properties can be set only on the consumer

  • lazy {Boolean} - If you set this to true then the iframe will not be created until the first use of the Socket
  • container {String || DOMElement} - Set this to an id or element if you want the iframe to be visible for interaction.
  • props {Object} - The key/value pairs of this object will be deep-copied onto the iframe. As an example, use props: {style: {border: "1px solid red"} } to set the border of the iframe to 1px solid red.
  • remoteHelper {String} - To enable the NameTransport as a fallback, set this to point to the name.html file on the provider.
  • hash {Boolean} - Whether to pass the setup data using the hash instead of using the query. This is mainly useful in scenarios where query arguments affects efficient caching or where the providers HTTP server does not support URL's with query parameters. Using the hash is not compatible with hash based history managers etc.

These properties can be set only on the provider

  • acl {String || String[]} Use this to only allow specific domains to consume this provider. The patterns can contain the wildcards ? and * as in the examples 'http://example.com', '*.foo.com' and '*dom?.com', or they can be regular expressions starting with ^ and ending with $. If none of the patterns match an Error will be thrown.

A socket can be torn down (removing the iframe, etc.) using

    socket.destroy();

The easyXDM.Rpc

... constructor lets you create a proxy object with method stubs and uses JSON-RPC to invoke these methods and return the responses.

The Rpc uses the same transport stack as the Socket, and so uses the same config properties.

To set up a simple Rpc this is what you will need to add to the consumer

    var rpc = new easyXDM.Rpc({
        remote: "http://path.to/provider/" // the path to the provider
    }, 
    {
        local: {
            helloWorld: function(successFn, errorFn){
                // here we expose a simple method with no arguments
                // if we want to return a response, we can use `return ....`,
                // or we can use the provided callbacks if the operation is async
                // or an error occurred
            }
        },
        remote: {
            helloWorld:{
                // here we tell the Rpc object to stub a method helloWorld for us
            }
        }
    });

Call the methods like this

    rpc.helloWorld(1,2,3, function(response){
        // here we can do something with the return value from `helloWorld`
    }, function(errorObj){
        // here we can react to a possible error
    });

And this is what's needed for the provider

    var rpc = new easyXDM.Rpc({},
    {
        local: {
            helloWorld: function(one, two, thre_args, successFn, errorFn){
                // here we expose a simple method with three arguments
                // that returns an object
                return {
                    this_is: "an object"
                };
            }
        },
        remote: {
            helloWorld:{
                // here we tell the Rpc object to stub a method helloWorld for us
            }
        }
    });

Call the methods like this

    rpc.helloWorld(); // easyXDM automatically changes it's behavior depending on the presence of callback methods for `success` and for `error`. 

The Rpc configurations local and remote properties can be left out if empty. Both properties can have multiple methods defined.

When calling the stubs you can provide up to two callback functions after the expected arguments, the first one being the method that will receive the callback in case of a success, and the next the method that will receive the callback in case of an error.

If an error occurs in the execution of the stubbed method then this will be caught and passed back to the error handler. This means that you in the body of the exposed method can use throw "custom error"; to return a message, or you can pass a message, and an optional object containing error data to the error callback. If the error handler is present, then this will be passed an object containing the properties

  • message {String} - The message returned from the invoked method
  • data {Object} - The optional error data passed back.

In addition to the local and remote properties, you can set the following

  • serializer {Object} - An object conforming with methods matching the standardized window.JSON object.

In order for easyXDM.Rpc to use JSON-RPC it needs access to functioning encode/decode methods for JSON, and this can be provided by setting the serializer. If not set easyXDM will try to use the native JSON object, and will even work with the faulty toJSON and evalJSON provided by earlier Prototype Js.

If you want to conditionally include Douglas Crockfords JSON2 library (or any other that will provide window.JSON) then you can add this directly after the script that includes easyXDM

    <script type="text/javascript">
        easyXDM.DomHelper.requiresJSON("http://path/to/json2.js");
    </script>

This will only include it if not natively supported.

An rpc object can be teared down (iframe removed etc) using

    rpc.destroy();

The shipped /cors/ interface

Since either end is free to use AJAX etc the Rpc object can be used to easily expose enable cross-domain AJAX. For this the library comes with a default /cors/index.html (/cors/) document that exposes a method request(object config, function successFn, function errorFn), where config can have the following properties:

  • url {string} - The url to request
  • method {string} - GET or POST. Default POST
  • headers {object} - A map of headers to apply - the defaults are "Content-Type": "application/x-www-form-urlencoded" and "X-Requested-With": "XMLHttpRequest". Set headers are added to the default, null values removed.
  • timeout {number} - the number of milliseconds before a timeout occurs. Default 10000 (10 seconds)
  • `data´ {object} - a map of the data to pass

If the request succeeds the success handler will be passed an object with the following properties

  • data {string} - the responseText
  • status {number} - The status of the request
  • headers {object} - a map of the returned headers

If the request fail the error handler will be passed an object with the following properties

  • data {string} - the responseText if available, or null
  • status {number} - The status of the request
  • message {string} - A friendly message explaining the error

This is how you can use it:

    var rpc = new easyXDM.Rpc({
		remote: "http://foo.bar/cors/"
	},
    {
        remote: {
			request: {}
		}
    });

	rpc.request({
		url: "/resource/x/y/z/",
		method: "POST",
		data: {foo: "bar", bar: "foo"}
	}, function(response){
		alert(response.data);
	});

easyXDM.noConflict

If you want two or more instances of easyXDM to run on the same page, you can put your instance into a namespace using easyXDM.noConflict method. This method returns control of easyXDM global object to the other library and returns an instance of itself.

This is useful if you embed your code on the page and cannot guarantee that it does not already define window.easyXDM.

It also takes a single argument, a string representation of the namespace. We need it to get access to the instance in the parent window (when using SameOriginTransport).

Example:

	// Let's assume we already have an instance of easyXDM on the page, but
	// we need to load another one and put it under PROJECT.easyXDM. Here is
	// how you do it.
	var PROJECT = { easyXDM: easyXDM.noConflict("PROJECT") };

For more information

There are several examples and demos available through the main website, and in the documentation.

Tests

For development a test suit is used - you can run this here:

License

easyXDM is distributed under the MIT license. Please keep the exisisting headers.

Attribution

Main developer Øyvind Sean Kinsey - [email protected], @okinsey, http://kinsey.no

The following has contributed to the project

easyxdm's People

Contributors

benvinegar avatar bjornblomqvist avatar brainpicture avatar chebur avatar clarketus avatar comp615 avatar craveytrain avatar daniel-marynicz avatar dzrhythm avatar eligrey avatar gjcourt avatar j1anb1n avatar jakobo avatar kennethkufluk avatar methyl avatar mjpizz avatar nikuph avatar oyvindkinsey avatar pronebird avatar seanfeld avatar tilgovi avatar valueof avatar winglian 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  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

easyxdm's Issues

Buffer calls prior to onReady

Calls made prior to the onReady method being fired should be buffered.
This means that they will not generate errors, and that they will be executed once the transport is ready.

url is undefined or null inside `resolveUrl(url)`

Just downloaded easyXDM-2.4.7.99 - and created a simple socket based on the example showed on the documentation just changing the location of the "name.html" for some reason I get an error inside the function resolveUrl since url is null.

quick fix:

url = (url)? url.replace(reDoubleSlash, "$1/") : '';

easyXDM adds extra scroll on right-to-left pages

If page has body[dir=rtl] or CSS rule direction=rtl, the use of hidden easyXDM iframe adds extra scroll to the page because left=-2000px should be right=-2000px on those pages.

The patch was extracted from our internal easyXDM fork at Disqus.

'hidden' iframe creates blank space at the bottom of page on Firefox 3.5

the hidden frame created by easyXDM.DomHelper.createFrame() can cause a couple of inches of space to be added at the end of the page in Firefox 3.5 (and possibly others..). although it's marked as 'position: absolute' and 'left: -2000px', firefox does its normal layout trick with absolutely positioned elements and leaves them where they would be if static (unless you tell it otherwise). so, it's been moved off to the left of the page, but is still positioned at the end of the page..

long explanation, easy fix! just add 'top: 0px' to the frame's style.

feature to pass status to onReady function

In the case where the application wants to specially handle easyXDM errors, a status flag could be passed to onReady, or a separate onFail callback could optionally be specified. This way it's up to the host application to decide what to do with error cases.

Automatic detection of DOMContentReady

easyXDM should in order to not generate errors due to trying to initialize easyXDM objects prior to the DOM being ready, delay all DOM operations until the DOMContentReady event has occurred.

easyXDM should also expose a method whenReady(fn [,context]) for enqueuing functions for execution once the DOM is ready.

Expand the widgets example

Create a library with an API for hosting widgets using easyXDM.
This library should implement key features from projects like open social.
The advantage we have is that we dont need any of the server support that eg. open social requires.

xhr.html serialization

the query param serialization routine in xhr.html should try to match those used in the big libraries... so that it is easier for developers to make easyXDM a drop-in replacement for the AJAX libraries they are already using....

Safari 3 timing issues with onload in init function

Line 1232:
un(callerWindow, "load", callerWindow.loadFn);
callerWindow seems to be undefined.

Changing the code (below) fixes the JS error on instantiate, however, XDM communication does not function. (wish Safari had Firebug so I could provide a better report)
onLoad: function(){
// Remove the handler
setTimeout(function(){
un(callerWindow, "load", callerWindow.loadFn);
easyXDM.Fn.set(config.channel + "_load", _onLoad);
_onReady();
},0);
}

Dependency on window.open()

Some of the fallback transports use window.open to get a reference to existing frames.
While this doesn't produce any popups, it will often invoke the popupblocker.

The use of window.open should therefor be avoided.

Access Control headers not case-insensitive in CORS

I haven't tested anything, but it's clear from your CORS code that you require Access-Control-Allow-Origin, etc. to have initial capitals. Please use xhr.getResponseHeader() instead, which is case-insensitive.

Access Control Lists

The provider should be able to enforce a list of allowed [protocol]://[domain] patterns that are allowed to interact with the provider. The patters should support wildcards.

Document and version the protocols used

This would make it easier to decide on compatiblities between versions of the library.
Up untill now the only change in protocol was in easyXDM.Transport.PostMessageTransport where we with 1.5 included an initial {channel}-ready message.

Set initial iframe style when script is loading

I use easyXDM on my project to resize iframe dynamically.
When I load easyXDM, it load the embedded iframe automacally.
But the style of the iframe is not good.
Are there any way to set some iframe styling when loading the iframe with easyXDM.
How can I set intial styling of embedded iframe??

lazy initialization of the transports

Using lazy:true in the transports configuration, one should be able to flag that one wants the initialization of the transports to be delayed until needed.

Chrome 8 support

Hi,

I tried out Chrome 8 beta and it seems that the RPC communication is not working properly there. Anyone else having trouble with chrome 8 beta, or anyone that can confirm that they got it working under chrome 8 beta?

remote location port handling (implicit SSL)

When using port numbers on remote urls for RPC, the library fails to handle expression handling given that the target origin would be https://foo.bar:443 and the provider window location provides https://foo.bar even though domains match correctly. I am working on a patch for this, but have also resolved this issues within my current project to remove the port numbers from the origin which we are using. (non ssl site with iframe communication to a ssl protected provider). The messages are received by the RPC handler, but rejected and not executed.

This can be reproduced by using a remote https provider url with :443

Line #1546 in easyXDM.debug.js

if (origin == targetOrigin && event.data.substring(0, config.channel.length + 1) == config.channel + " ")

Work with XHR headers for remote ajax requests

When performing a remote ajax request (which causes easyXDM to open up a remote iframe), there is no way to provide custom headers to the XHR request nor is there any way to receive the response headers when the request completes.

Also, it would be nice to be able to set some kind of XHR timeout which would fail the request if the server was not responding back within a specified period of time.

opera 9.50 falls with easyXDM

In Opera 9.50 easyXDM falls

function: easyXDM.stack.PostMessageTransport

isHost = false (embedded side)

It fall in ("postMessage" in window.parent) check
or
in callerWindow.postMessage(config.channel + "-ready", targetOrigin);

on(window, "message", _window_onMessage);
callerWindow = ("postMessage" in window.parent) ? window.parent : window.parent.document;
callerWindow.postMessage(config.channel + "-ready", targetOrigin);                
setTimeout(function(){
   pub.up.callback(true);
}, 0);

You can download opera 9.50 at http://www.opera.com/browser/download/?os=windows&ver=9.50&local=y

Error in RpcBehavior

A return value that is 'falsy' will be treated as no return at all (an hence an error)

This means that any Rpc methods designed to return a boolean would just have their success handler called on 'true' values. For 'false' no handlers would be invoked.

ability to develop from local file

I would like to be able to
do development on a local file on my machine... without a webserver.
but there is an error when trying to open a file in a browser when the
URL starts with "file:///"... (you have a regex that is checking
"http://" on line 181 of the debug file). would be nice if I could
just work locally on a file and make those cross-browser calls... I
wouldn't need to run a web-server locally, or have to FTP files to a
server to check them...

crossdomain.xml

One should be able to flag that easyXDM should only allow request according to the content of crossdomain.xml.

easyXDM does not work on wordpress

Hey guys, ive been writing a widget that people can include on their own website, and it uses a resizing iframe, which is why I am using easyXDM

if the host website of my widget is a wordpress blog, the website intercepts the 'p' get parameter and causes the widget resizing not to work.

ive fixed this issue in my fork of easyXDM at http://github.com/clarketus/easyXDM
commit: 7eee23e

by renaming the p get parameter to xdm_p.

I suggest you guys should either include my change to fix this issue, or look at renaming the get parameters using your own naming. perhaps start them all with 'xdm_' ?

Thanks :)

Oliver Clarke

Ability to split remote and local parts of RPC code to minimize code size

Is it possible somehow to split remote and local parts of RPC in order to include remote part of easyXDM in consumer and local part in subscriber. This would help to reduce overall download size when website is using merged js files with all dependencies. Without this it requires to download same code twice for each page which uses RPC.

The example code doesn't work in IE9

The example code in EasyXDM (example/index.html) doesn't work in IE9. It seems like that IE9 doesn't allow "window.opener" to access the hidden iframe in consumer page.

Create a logo for the library

The library should have a proper logo that can be used to identify it.

The word 'easyXDM' should be clearly recognizable, and a variant should exist with a fitting slogan.
Examples:

  • Cross-domain scripting made easy
  • Cross-document messaging made easy
  • ...

If used with a symbol, then this should/could indicate communication back and forth across a boundry or something indicating that the boundary is being 'erased'

A way to append an iframe instead of inserting it before a container

Right now, the only way to attach an iframe to a container is to insert it before the first element in a container. We needed to append an iframe to a container and we solved it by introducing a config option append. However, I can see how other users may want to attach an iframe in different ways so I am trying to come up with some generic solution. Ideas?

resolveUrl issues

  • An empty URL is perfectly valid and should resolve to the current URL.
  • There are just so many things wrong with the URL parsing.
  • Looks like it only supports http[s].

It'd be best to just use the DOM's internal URL resolver.

var link = document.createElement("a");
link.href = someUnresolvedURL;
// link.href is now the resolved URL

For example, see my implementation in mumbl.

Wrong parameter passed to cors call error handler

The documentation for the cors interface states:
If the request fail the error handler will be passed an object with the following properties [...] data [...] status [...] message

However, in my implementation (relying on easyXDM-2.4.10.103) this doesn't appear to be the case. While the success handler works correctly, the error handler is in fact only passed an Object with two attributes: code and message. The code is -32099 (defined in easyXDM.js, line 2347), while the message is invariably INVALID_STATUS_CODE (which I guess is coming from cors/index.html, line 145).
This happens with all the requests that return a status different than 2xx. I guess it's related to the check in cors/index.html, line 143.

To reproduce the bug, just follow the instructions contained in http://easyxdm.net/wp/2010/03/17/cross-domain-ajax/ and add an error handler by duplicating the success handler. Then perform a call that's supposed to return a 404 or a 500.
I may add (since variable useAccessControl seems to play a role into this) that I'm using access control to restrict calls to trusted origins.

IE7 Issue with frames (frameset)

Our site uses frameset. I tried to establish communication between 2 frames from opened from different domains within a single frameset. The test case works great in Safari, FF but in IE7 it does not work as expected: when frame is opened, instead of several redirects within frameset (by the way, I noticed it for every transport method, is it required?) it opens up one of the frames in a new instance of IE. Is there any way to suppress opening up new window in a process?

success() and error() callbacks dropped in window.opener example

In Internet Explorer, attempting to pass success and error callbacks (using RPC) in the window.opener example fails. Unfortunately, this is due to the way IE mangles functions when passed between window contexts: http://www.eggheadcafe.com/software/aspnet/33887956/ie-678-javascript-crosswindow-typeof-bug.aspx

The result is calling window.opener.proxy(..., function success() {}) results in the success() call being treated as a parameter instead of the callback. The offending code is Lines #77 and #79 https://github.com/oyvindkinsey/easyXDM/blob/master/src/stack/RpcBehavior.js#L77 where the typeof operator is used.

Unfortunately, the fix for this isn't easy, elegant, or really even good. IE's simply broken in how it reports these objects. Included in this bug (followup post) is a link to a branch with an isCallableMethod() fix, which works by comparing toString() implementations against the native Function object's calls.

easyXDM not creating iframe in firefox of MAC os

Hi,
I am trying to resize iframe based on its content using easyXDM.
I followed the concepts of

http://easyxdm.net/wp/2010/03/17/resize-iframe-based-on-content/

easyXDM is not creating iframe in firefox browser in MAC OS. It is
working fine in other browsers in MAC OS.

I have one more problem in Safari browser in Windows . It is creating
iframe but given props are not applying. I need set width and height
of the iframe using props.

You can check my code in the following URL

http://phone.invox.com/widgetconfig/mcwidget8/test.html

Please help me
Thanks in advance
Ramesh

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.